TCP/IP 三次握手


网络连接状态

网络连接状态非常重要这里既包含三次握手中的也包括四次断开中的,所以要熟悉。

状态
说明
LISTEN 首先服务器需要打开一个socket进行监听,监听来自远方TCP端口的连接请求
SYN_SENT 表示主动连接,客户端能通过应用程序调用connect()函数进行active open。于是客户端TCP发送一个SYN以请求建立一个连接,之后状态为SYN_SEND,表示已发送一个SYN到服务器端,等待SYN 1+ACK 0响应。
SYN_RECV 服务器端收到客户端的SYN 1+ACK 0,然后状态变为SYN_RECV。表示服务器收到了客户端发来的SYN,然后自己也响应了给客户端一个SYN 1+ACK 1,然后等待客户端确认。 这时候客户端过来的连接(属于半连接状态)被放在一个SYN队列里面,SYN泛洪***也是这样的,就是服务器响应了SYN+ACK之后,客户端就不在发送ACK了,然后继续发送SYN,直到把服务器的最大连接数量耗尽。 半连接队列长度是由内核参数tcp_max_syn_backlog来决定的。
ESTABLISHED 代表一个打开的连接,客户端收到服务器发送的SYN 1+ACK 1,就变为这个状态,然后向服务器发送ACK,如果服务器收到这个ACK,那么它也变为这个状态。这个状态就是表示连接以及建立,正在或即将传输数据。服务器收到ACK以后就会把半连接从上面提到的SYN队列中删除,然后放到ACCEPT队列中,这时这个半连接的状态就变成了ESTABLISHED。
FIN_WAIT1 主动关闭端(可以是服务器也可以是客户端)应用程序调用了close,于是其TCP发出FIN主动关闭请求,也就是四次断开的第一次,之后就进入了FIN_WAIT1状态,等待远程主机的ACK请求。
CLOSE_WAIT
被动关闭端(可以是服务器有可以是客户端)收到了对方发来的FIN后,进入该状态,然后发出ACK+1以回应FIN请求(它的接收也作为文件结束符传递给上层应用程序)。这个状态实际上是说客户端告诉服务器我没有请求或者数据要发送了,等待看看服务器或者说是进程还有没有数据要发送,如果有则继续发送,如果没有的话,就发送反向关闭指令。

如果服务器大量连接是这个状态就要去查看程序,很有可能是程序设计的问题。

FIN_WAIT2 主动关闭端收到ACK+1后,就进入的FIN_WAIT2状态,也就等服务器是否还有数据发来,如果服务器没有数据了,那么服务器就发送的反向关闭指令。也就是反向关闭连接指令FIN1+ACK1。实际上是告诉客户端我的数据发送完了,可以关闭连接了。
LAST_ACK 被动关闭端,发送反向结束连接请求FIN 1+ACK 1,然后进入LAST_ACK状态,等待主动关闭端发送ACK。
TIME_WAIT
主动关闭端收到FIN 1 +ACK 1后,并进入TIME_WAIT状态,然后发送ACK+1,等待一段时间(2MSL)以确保服务器收到了ACK+1,然后自己进入CLOSED状态。这个阶段主要是客户端为了再次确认一下服务器是否可以关闭连接,因为网络毕竟是不可靠的。对于服务器有大量TIME_WAIT这个问题通常调整sysctl来解决。

CLOSING 比较少见,表示等待远程TCP对连接中断的确认。
CLOSED
被动关闭端在收到ACK包以后,就进入closed状态,连接结束。

UNKNOWN 未知的Socket状态

三次握手过程

客户端:发送SYN=1请求建立连接,此时客户端进入SYN_SENT状态等待服务器响应

服务器:收到客户端的SYN=1后发送ACK=1,SYN=1表示收到建立连接请求,然后自己进入SYN_RECV状态进行等待客户端的最后确认

客户端:收到服务器发来的SYN=1,ACK=1然后发送ACK=1表示收到之前确认,然后自己进入ESTABLISHED状态表示自己处于连接建立状态

服务器:收到客户端的ACK以后则自己进入ESTABLISHED状态,此时双方都处于连接建立状态,之后进行数据传送。

三次握手的目的:是为了告诉对方SEQ然后服务器回复SEQ+1,这样发送端就知道包没有丢;另外握手的目的是交换信息,比如:

MSS:最大传输包(不含TCP/IP头),MMS+包头就是MTU,如果MTU过大传输就会卡死。

SACK_PERM:是否支持Selective ack(用户优化重传效率),比如客户端发送5个包给服务器,中途丢了2号包,服务器回复的时候只能回复2,表示2号前面的都收到了,请求重传2号包,可是客户端并不知道2后面的345是否收到没有,如果支持SACK的话,那么服务器请求重传2的时候就可以同时告诉345已经收到,这样客户端只需要重传2,如果没有SACK机制,那么客户端就会重传2345,这样效率就低了。

WS:窗口计算指数

半连接和全连接

未完成连接队列:客户端发送SYN到服务器,服务器正在等待完成三次握手,此时就会把客户端发起的这个连接请求放在该队列里,也就是sync队列。这个队列由net.ipv4.tcp_max_syn_backlog参数决定, 系统默认2048,服务器端口状态为 SYNC_RCVD。

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

已完成连接队列:已经完成握手的连接从SYN队列移动到这个队列,也就是accept队列,默认128(其实这个队列最终的大小是由SOMAXCONN和使用listen函数传入参数的两者取最小值决定的),服务器端口状态为ESTABLISHED,在Linux内核2.4.25之后在/etc/sysctl.conf中

net.core.somaxconn = 128直接修改。

cat /proc/sys/net/core/somaxconn

TCP的三次握手第一步服务器收到客户端的SYN后,把该请求放在半连接队列中,之后回复SYN+ACK,当客户端收到这个信号并发送ACK之后并且服务器正常收到和处理后就把该请求从半连接队列移动到ACCEPT队列,进入这个队列才能从Listen变成accept。

比如syn泛洪***就是针对syn队列的,***方不同的建立连接,但是只做连接的第一步,当***者收到SYN+ACK后直接丢弃,导致受***的服务器上这个队列满了然后其他正常请求就无法进入。

常见问题:客户端在发送完最后一个ACK之后服务器端如果收到正常情况下应该把该链接从SYNC队列移动到ACCEPT队列,如果ACCEPT队列满了,默认服务器丢弃不会响应,所以从客户端角度来看三次握手已经完成,但服务器没有响应这个链接,这种情况经常出现在服务器同时收到很多链接请求的时候。如何确定这个问题?使用如下命令:

netstat -s | egrep "listen|LISTEN"

如果出现:

xxxxx times the listen queue of a socket overflowed(全连接队列溢出次数)

xxxxx SYNs to LISTEN sockets ignored (半连接队列溢出次数)

这两个值有时你会看到一样多,但是通常半连接溢出次数会大于等于全连接溢出次数。就说明可能会有这个问题。因为如果这个数值一直在增加那么就要注意了。如果想再次确认,那么你需要修改内核参数

echo ‘1‘ > /proc/sys/net/ipv4/tcp_abort_on_overflow

该参数默认为0,参数含义看后面。修改之后客户端再次发起连接就会收到reset信号,如果抓包收到这个信号,就证明服务器端的accept队列满了,你需要进行调整。比如JAVA中默认socket的backlog值大小是50.

ss -lnt

Send-Q:表示LISTEN端口上的全连接队列最大为多少

Recv-Q:为全连接队列当前使用了多少

全连接队列大小取决于:min(backlog, somaxconn),前一个是在socket创建时传入的(listen函数),somaxconn是OS级别的参数,这个somaxconn的含义请查看后面的内涵参数说明

半连接队列大小取决于:/proc/sys/net/ipv4/tcp_max_syn_backlog 这个内核参数

Nginx默认的accept队列是511,而且是多个进程同时监听一个端口;Tomcat的accept队列是100,默认短连接。

# 查看Accept队列溢出情况,如果当前没有溢出则没有任何返回值
netstat -s | grep TCPBacklogDrop

思考:

如果客户端发出ACK之后刚好服务器ACCEPT队列满了,也就是客户端认为连接成功建立而实际上服务器端连接没有准备好,而这时客户端认为建立好了而强行发送数据会怎么办呢?客户端发送之后肯定会得不到响应,因为服务器丢弃了,然后客户端认为丢失所以进行重传,一定次数之后客户端认为异常,然后一直到超时最后断开。

关于Backlog

TCP连接客户端connect()返回并不代表TCP连接成功,有可能是服务器接收队列满了,系统会丢弃后续的ACK请求,
客户端以为建立了连接,然后就执行后续操作,然后就等待到超时。服务器则会等待ACK超时,会重传SYN。

TCP队列的一些问题

  1. 客户端通过connect向服务器发出SYN包,客户端会维护一个socket等待队列,而服务器则会维护一个SYN队列
  2. 此时是半连接状态,如果socket等待队列满了,服务器则会丢弃,而客户端会返回超时。只要客户端没有收到SYN+ACK,3秒后客户端会再次发送,然后依然没有收到,9秒后再继续发送。
  3. 半连接SYN队列长度由tcp_max_syn_backlog决定
  4. 当服务器收到客户端SYN后,会返回SYN+ACK包,客户端的TCP协议栈会唤醒socket等待队列,发出connect调用
  5. 客户端返回ACK后,服务器会进入一个新的叫做accept的队列,这个队列长度为min(backlog,somaxconn)默认情况下somaxconn是128,表示最多有129的ESTAB的连接等待accept(),而backlog的值由int listen(int sockfd,int backlog)中的第二个参数指定,其含义是设置listen()函数最多允许多个网络连接同时处于挂起状态,大部分平台都是511
  6. 当accept队列满了之后,即是客户端继续向服务器发送ACK包,也不会得到响应,此时服务器通过tcp_abort_on_overflow来决定如何返回,0表示直接丢弃,1表示发送RST通知;客户端则会分别返回read timeout或者connection reset by peer。从上面可以看到有2个队列,一个保存SYN_SEND以及SYN_RECV,另外一个accept队列保存ESTAB的状态。

比如客户端通Nginx通信,Nginx立即返回ACK,但是3秒后才返回响应数据,Nginx同后端通信,发送SYN请求等待3秒后端才响应,就可能是backlog值设置过小,导致accept queue溢出,SYN被丢弃导致3s重传。

关于ss命令中Recv-Q和Send-Q的含义?

这两个指标在不同场景含义不同。一个是状态处于LISTEN状态、一个是非LISTEN的其他状态。

LISTEN状态

这里的含义就是上面说的Recv-Q是当前全连接队列使用量;Send-Q是当前对应进程SOCKET套接字最大blacklog的数量,也就是全连接队列最大长度

非LISTEN状态

Recv-Q:数据已经接收到本地缓存,还有多少没有被程序取走,单位bytes

Send-Q:要发送的数据有多少还在本地缓冲区对方未确认,如果不是0可能是本地发送数据过快或者对方接收数据过慢,单位bytes

上述两个值在非LISTEN状态下都应该保持0或者瞬间不为0,如果长期不为0则可能有问题。

# 统计各种状态的值
netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}‘
# 统计特定进程的TCP状态
netstat -ntap | grep ‘3141‘ | awk ‘{++S[$6]} END {for(a in S) print a, S[a]}‘

Linux内核中的TCP/IP参数

tcp_abort_on_overflow 默认为0

TCP全连接队列也就是accept队列满了之后如何处理,默认是0,也就是丢弃,可以改为1,表示如果队列满了这时候有客户端建立连接则发送一个reset包给客户端,表示废除这个握手。

net.core.netdev_max_backlog 默认为128

表示当每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许发送到队列的数据包最大数目。就是说当接口接收包的速度比内核处理的快时,那么多出来的数据包要存放到队列中,那么这个队列最大可以放多少个呢?就是这个参数设置的。
net.ipv4.tcp_max_orphans
用于设定系统中最多允许存在多少TCP套接字不被关联到任何一个用户文件句柄上,如果超过这个值,那么没有与用户文件句柄关联的TCP套接字就会被复位,同时给出警告信息。这个值主要是为了防止DOS***。一般在系统内存比较大的情况下可以调大。
net.ipv4.tcp_max_syn_backlog
用于记录尚未收到客户端ACK信息的连接请求最大值。内存比较多可以设置大一点。也就是半连接的队列,表示服务器收到了客户端的SYN包同时服务器也发送了ACK+SYN,但是还没有收到客户端返回的ACK包,此时连接处于SYN_RECV状态,当服务器收到客户端的ACK包时,则删除该半连接条目,服务器进入ESTABLISHED状态,这时候把该连接放入Accept队列。修改这个值可以增加更多的网络连接,但是过大容易受到SYN泛洪***。
net.core.somaxconn
表示用于调节系统同时发起的TCP连接数,一般为128,当高并发的情况下,如果这个值比较小,就会导致连接超时或者重传现象。Nginx服务器中定义的NGX_LISTEN_BACKLOG默认是511,所以需要调整这个参数。当服务器收到ACK包之后,就会进入一个叫accept的队列这个队列的最大长度就是由这个参数决定的。表示最多可有多少个ESTAB的连接等待accept()。这个值表示已客户端和服务器已完成三次握手的已建立连接的队列大小。
net.ipv4.tcp_timestamps
该参数用于设置时间戳,可以避免序列号重复,在一个端口速率比较大的网卡下,遇到重复的序列号的概率还是比较大的。如果设置为0表示禁用对TCP时间戳的支持。默认情况下,系统是允许重复的。但是对于Nginx来说还是建议关闭。
net.ipv4_tcpsynack_retries
用于设置内核放弃TCP连接之前向客户端发送ACK+SYN包的数量,也就是重试次数。这个参数主要影响三次握手中的第二次,也就是服务器向客户端发送SYN+前一个SYN的ACK。一般设置为1,表示内核放弃连接之前发送一次SYN+ACK包。比如客户端发来SYN,然后服务器回复ACK+SYN,这时候客户端断线了,之后会怎么办呢?服务器会进行重发ACK+SYNC,Linux中默认重试5次,每次时间间隔为上一次的一倍,1s-2s-4s-8s-16s之后再等一个32s如果还没有客户端响应,则服务器断开这个连接。
net.ipv4.syn_retries

参数和上一个类似,这是这次是设置内核放弃建立连接之前发送SYN包的数量。也建议设置为1.

 net.ipv4.tcp.syncookies

修改此参数可以有效防范syn flood***。原理是在TCP服务器收到SYN包后,***者就下线,这样默认服务器需要等待63秒之后才会断开这个连接(中间服务器要重试几次),这样服务器的SYN队列很快就满了。这个参数的目的就是为了解决这个问题,当SYN队列满了,服务器根据预源端口、目的IP和时间戳生产一个序列号(可以叫做cookie)发送出去,如果是***者它是不会响应的,如果是真实请求则会返回这个cookie,然后服务器根据这个Cookie来建立连接就算你不在SYN队列中也可以。默认为0,1表示开启。对于连接请求很大的服务器不要开启这个参数,因为它并不严谨。你应该设置三个参数来变相解决这个问题:net.ipv4_tcpsynack_retries、net.ipv4.tcp_max_syn_backlog和tcp_abort_on_overflow也就是,也就是减少重试次数、增大SYN队列长度和如果处理不过来就拒绝。

net.ipv4.tcp_tw_reuse

表示开启重用。允许将TIME_WAIT状态的sockets重新用于新的TCP连接,因为大量处于TIME_WAIT状态很浪费资源,占用文件描述符,默认为0,表示关闭,设置为1表示开启;
net.ipv4.tcp_tw_recycle
表示开启TCP连接中TIME_WAIT sockets的快速回收,默认为0,表示关闭。设置为1表示开启。
net.ipv4.tcp_fin_timeout 
表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN_WAIT-2状态的时间。默认为2MSL。不建议修改,如果要修改可以根据实际情况而定。
net.ipv4.tcp_keepalive_time
TCP keepalive心跳包机制,用于检测连接是否已经断开,这个值就是设置检测频率的。表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。
net.ipv4.ip_local_port_range = 1024 65000 
表示用于向外连接的端口范围。缺省情况下很小,改为1024到65000。
net.ipv4.tcp_max_tw_buckets = 5000
表示系统同时保持TIME_WAIT套接字状态的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000,改为5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于Squid,效果却不大。此项参数可以控制TIME_WAIT套接字的最大数量,避免Squid服务器被大量的TIME_WAIT套接字拖死。

原文地址:https://www.cnblogs.com/yunxizhujing/p/9381433.html

时间: 2024-10-19 21:58:09

TCP/IP 三次握手的相关文章

TCP/IP 三次握手,四次断开

TCP/IP 三次握手,四次断开 一.TCP报文格式                     TCP/IP协议的详细信息参看<TCP/IP协议详解>三卷 本. 下面是TCP报文格式图: 图1-1 TCP报文格式 上图中有几个字段需要重点介绍下:1.序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记.2.确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效(Ack=Seq+1).3.标志位:共6个,即URG.ACK.PSH.R

TCP/IP三次握手和HTTP过程(转)

1.TCP连接 手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接.TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上. 建立起一个TCP连接需要经过“三次握手”: 第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认: 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_

TCP/IP三次握手与四次挥手的正确姿势

A 理解TCP/IP三次握手与四次挥手的正确姿势https://www.cnblogs.com/lms0755/p/9053119.html B 四次挥手过程理解 https://blog.csdn.net/qq_38950316/article/details/81087809 C TCP三次握手四次挥手详解http://www.cnblogs.com/zmlctt/p/3690998.html 原文地址:https://www.cnblogs.com/kelelipeng/p/1021678

TCP/IP三次握手与四次挥手(转)

一.TCP报文格式        TCP/IP协议的详细信息参看<TCP/IP协议详解>三卷本.下面是TCP报文格式图: 图1 TCP报文格式 上图中有几个字段需要重点介绍下:        (1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记.        (2)确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1.        (3)标志位:共6个,即URG.ACK.PSH.RST.SYN.F

TCP/IP 三次握手和四次握手

三次握手建立连接: 第一次握手:客户端发送syn包(seq=x)到服务器,并进入SYN_SEND状态,等待服务器确认: 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(seq=y),即SYN+ACK包,此时服务器进入SYN_RECV状态: 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手. 握手过程中传送的包里不包含数据,三次握手

TCP/IP 三次握手 四次断开

所谓的三次握手,就是指客户端和服务器端建立 TCP 连接.访问过程中会发送三个包来确认,并建立连接. 第一步:客户端会发送请求包,包内包含 SYN 信号,SYN 标记位置为1(还可以是其他的位置),并且随机产生一个随机值 seq(用来后面的检验),将这些发送给服务端. 第二步:服务端收到 SYN 信号后,会发送一个确认信息给客户端,包内包含 SYN 的标志位 SYN=1 ,以及确认标志位 ACK=1 ,检验位 ack=J+1(客户端发送的随机数+1),本机生成的随机数 seq=K. 第三步:客户

TCP/IP三次握手和HTTP过程

1.TCP连接 手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接.TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在"无差别"的网络之上. 建立起一个TCP连接需要经过"三次握手": 第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认: 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+A

TCP/IP三次握手和四次挥手

(一)TCP三次握手 TCP(Transmission Control Protocol) 传输控制协议 TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接: 位码即tcp标志位,有6种标示: SYN(synchronous建立联机)发起一个新连接. ACK(acknowledgement 确认)确认序号有效 PSH(push传送)接收方应该尽快将这个报文交给应用层 FIN(finish结束)释放一个连接 RST(reset重置)重置连接 URG(urgent紧

tcp ip三次握手链接和四次挥手断开

一 三次握手目的是为了建立连接... 1 核心的就是client端和service端,进行数据"报文" 交换 2 报文,目的是互相通知,确认链接 或 过程描述:::::: 1 首先Client端发送连接请求报文, 2 Server段接受连接后回复ACK报文,并为这次连接分配资源. 3 Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源, 这样TCP连接就建立了. 二 数据socket传输 1 不断的往某ip 的某端口,写入数据 2 ip服务器上不断的监听该端口