答案是否定的
1我们回顾下使用wireshark的抓包
1.1. 服务器未开 客户端尝试连接
1.2 建立连接然后关闭,断开的时候时候有时候三次握手有时候四次握手
1.3. 建立连接,交互一次然后断开
根据wireshark的包,四会握手的第二步 被动断开的一方收到FIN(第一次握手)后要发送ACK。但是抓的包中有时候会没有这一步。
我们看一下一般的书中TCP四次挥手的状态迁移图
当被动断开的一方发送ACK的时候,被动断开的
对于这个图大家很熟悉。
按照这个图的顺序说明会误导大家以为断开一个TCP总是这样的。其实按照RFC793的描述将主动断开的一方和被动断开的一方分开描述会更清晰。
2. TCP断开连接
2.1 主动断开的一方
①首先发送FIN告诉对方我要关闭连接,自己进入到状态FIN_WAIT_1此时开始不再处理和发送应用层用户的数据。
③收到对方对FIN的ACK后自己进入到FIN_WAIT_2,这个时候依然可以接收对方的数据。如果收不到对方的ACK会再次发送之前包含FIN在内的消息。进入FIN_WAIT_2后就等待对方关闭,因为已经确认到对方收到自己的FIN了。
⑤收到对方的FIN说明被断开的一方也要没有数据发送并关闭连接了,此时ACK对方的FIN进入TIME_WAIT。然后等2msl连接关闭。
– 需要注意的是,对方ACK自己的FIN后,并不会立即发送FIN而是在应用层关闭连接后才会发送FIN
2.2被动断开的一方
②收到网络中传来的FIN,自己ACK这个FIN然后进入到CLOSE_WAIT状态。
④通知应用层要关闭连接(从这个角度上理解为什么断开比建立连接多了一次handshake,因为被断开一方还有余下的数据要发送)这个时候等待用户来回应CLOSE,在发送FIN之前可以发送自己余下的数据。发送FIN后进入LAST_ACK。
⑧等待对方ACK自己的FIN完成关闭。如果对方没确认会再次发送FIN。
2.3 断开过程的一些解释
(1)所以谁发送FIN谁就不再发送数据了。
(2)发送ACK的时候,ack是收到的req的值+1。因此第3和第4步的ack都是101。第四步并不是102。
(3)发送的seq是对方发送的ack所以第5步的seq是101而是不是102。
(4)第2步后主动断开放进入FIN_WAIT是等待对方的FIN。此时不可以发送数据但是可以接收数据。因此FIN意味着告诉对方“我没有多余的数据要发送”。这也是为什么断开比建立连接多一次handshake的原因。因为发送FIN后还可以接收数据。
(5)被断开的一方收到FIN后,给对方发送ACK表示收到,自己状态为CLOSE_WAIT。此时TCP连接处于半关闭状态。被断开的一方仍然可以发送数据。这时候由上一层协议应用层来决定是否要发送余下的数据。
3. 为什么四次挥手?
我们看下断开连接的部分
3.1 实际中还会遇到的同时断开的情况和三次握手的情况
3.1.1 同时断开,同时发送FIN
同时断开的情况上面这张图是rfc793官方图。大家可以看到FIN_WAIT_1后还可能先收到FIN而不是ACK。why?因为两端可以同时关闭。同时收到对方的FIN然后同时确认直接进入closing->time_wait->closed流程。
3.12 三次握手,发送FIN后收到(FIN,ACK)
这张图还缺少一个状态转换就是FIN_WAIT_1直接收到**(FIN,ACK)后到达TIME_WAIT**。这种情况是被断开的一端没有数据要发送直接发送了ACK和FIN。这种情况通过抓包发现很常见。也就是四个过程也变为了三次握手。
这个还可能跟TCP的阻塞控制有关。
3.2 对于出现大量的CLOSE_WAIT
java 1042 root 42u IPv4 826784 0t0 TCP iZ2zei0nwllapkwklisoncZ:38250->100.100.18.22:squid (CLOSE_WAIT)
这个阶段对于被断开的一方还没有发送FIN连接处于半连接状态。但是如果CLOSE_WAIT的应用层并没有进close(这是个主动操作)不进入LAST_ACK怎么办?
这种问题的出现举个往往是程序员的代码有问题。出现异常没有写关闭连接。
但是依然可以vim /etc/sysctl.conf设置超时时间,参数是net.ipv4.tcp_keepalive_time默认是2个小时。
net.ipv4.tcp_keepalive_time=7200
3.3 出现大量的FIN_WAIT_2
主动断开端此时等待FIN处于半连接状态。对于主动断开的一端而言,FIN_WAIT_1到FIN_WAIT_2是通过重发解决肯定回到FIN_WAIT_2但是FIN_WAIT_2到TIME_WAIT如果收不到对方的FIN怎么办?此不能重发,这个地方是通过超时解决
对于被断开的一端而言,这种勤快TIME_WAIT(2msk)和LAST_ACK可以设置超时时间net.ipv4.tcp_fin_timeout
[root@iZ2zei0nwllapkwklisoncZ data]# /sbin/sysctl -a | grep timeout
net.ipv4.tcp_fin_timeout = 60
其中net.ipv4.tcp_fin_timeout就是FIN_TIME_WAIT2的超时时间可以在
vim /etc/sysctl.conf设置。
3.4 出现TIME_WAIT(2msl)出现,以及TIME_WAIT这个状态的意义,msl是多长时间
TIME_WAIT状态,必须在此状态上停留两倍的msl时间,等待2msl时间主要目的是怕最后一个 ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态 时两端的端口不能使用,要等到2msl时间结束才可继续使用。当连接处于2msl等待阶段时任何迟到的报文段都将被丢弃。如果出现大量的TIME_WAIT。可以设置vim /etc/sysctl.conf来缩短msl的时间。总之TIME_WAIT到和LAST_ACK到达CLOSED之前收发两端的端口都是不可用的。会占用系统资源。
一个msl在rfc1112的建议是2分钟,只是建议值
The TCP specification [TCP:1] arbitrarily
assumes a value of 2 minutes for MSL
另外介绍个内核参数net.ipv4.tcp_max_tw_buckets = 5000能容纳多少个TIME_WAIT超过后清除,以防止系统拖死。
3.5 LAST_ACK收不到ACK如何关闭
会重发FIN,这时候分两种情况
(1)主动断开的一方还在TIME_WAIT状态中。这时候会发过来ACK, 被断开的一方收到后顺利从LAST_ACK进入到CLOSED。
(2)主动断开的一方经过了2msl已经CLOSED了这时候会返回RST。被断开的一方收到后也会进入CLOSED状态。总之LAST_ACK总会进入到CLOSED状态不需要超时机制。
4. 其他的内核参数
# 能容纳多少个TIME_WAIT超过后清除,以防止系统拖死
net.ipv4.tcp_max_tw_buckets = 5000
#对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃
net.ipv4.tcp_syn_retries=6
#net.ipv4.tcp_synack_retries=2
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_orphan_retries=3
#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间
net.ipv4.tcp_fin_timeout=60
#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_max_syn_backlog = 4096
#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭
net.ipv4.tcp_syncookies = 1
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
net.ipv4.tcp_tw_reuse = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
net.ipv4.tcp_tw_recycle = 1
##减少超时前的探测次数
net.ipv4.tcp_keepalive_probes=5
##优化网络设备接收队列
net.core.netdev_max_backlog=3000
(2019年10月14日)补充(未完)
大量出现TIME_WAIT和CLOSE_WAIT的原因
TIME_WAIT 主动发起断开的一方会进入的状态,往往是自己请求太多(例如爬虫类服务)。
CLOSE_WAIT的原因,应用层程序有问题没有处理好对方的关闭请求(socket子线程close后父线程还在继续监听等也会造成)