tcp三次握手,四次挥手,短连接time_wait问题(附有案例分析)

1.TCP三次握手图:

2.TCP四次挥手图:

 

 

 

 

  ①第一次:主机1(可以使客户端,也可以是服务器端),设置SeqrAck,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了; 

  ②第二次:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,AckSeq加1,同时进入CLOSE_WAIT状态;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求;

  ③第三次:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;

  ④第四次:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。

 

注:MSL是指Max Segment Lifetime,即数据包在网络中的最大生存时间。每种TCP协议的实现方法均要指定一个合适的MSL值,如RFC1122给出的建议值为2分钟,又如Berkeley体系的TCP实现通常选择30秒作为MSL值。这意味着TIME_WAIT的典型持续时间为1-4分钟。

 

2.TCP四次挥手过程中通信双方状态解析:

  • FIN_WAIT_1: 其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动方)

  • FIN_WAIT_2:实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连接。(主动方)

  • CLOSE_WAIT表示在等待关闭。当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。(被动方)

  • LAST_ACK: 被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)

  • TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN WAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方),一旦出错了,其实会从FIN_WAIT_2状态直接进入关闭状态,并不会有time_wait状态, 例如http://mxdxm.iteye.com/blog/895187!

  • CLOSED: 表示连接中断。

3.为什么要有TIME_WAIT这个状态?

    为什么主动关闭的一端不直接进入closed状态,而是要先进入time_wait并且停留两倍的MSL时长呢?这是因为TCP是建立在不可靠网络上的可靠协议。如果主动关闭的一端收到被动关闭一端的发出的FIN包后,返回ACK包,同时进入TIME_WAIT,但是由于网络的原因,主动关闭一端发送的ACK包可能会延迟,从而触发被动关闭一方重传FIN包,这样一来一回极端情况正好是2MSL。如果主动关闭的一端直接close或者不到两倍MSL时间就关闭,那么被动关闭发出重传FIN包到达,可能出现的问题是:旧的连接不存在,系统只能返回RST包;新的TCP连接已经建立,延迟包可能会干扰新连接。这都可能导致TCP不可靠。(所以需要旧的连接一直保持time_wait 2MSL时间

 

主动关闭的一方在发送最后一个 ack 后就会进入 TIME_WAIT 状态 停留2MSL(max segment lifetime)时间这个

是TCP/IP必不可少的,也就是“解决”不了的。也就是TCP/IP设计者本来是这么设计的

主要有两个原因:


  1. 防止上一次连接中的包,迷路后重新出现,影响新连接
(经过2MSL,上一次连接中所有的重复包都会消失)


  2. 可靠的关闭TCP连接
在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发
fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以
主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。

TIME_WAIT 并不会占用很大资源的,除非受到攻击。

还有,如果一方 send 或 recv 超时,就会直接进入 CLOSED 状态

 

5.TIME_WAIT太多怎么解决?

     修改/etc/sysctl.conf :

   net.ipv4.tcp_tw_reuse = 1  #表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

   net.ipv4.tcp_tw_recycle = 1   #表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。net.ipv4.tcp_timestamps 开启时,net.ipv4.tcp_tw_recycle开启才能生效,。

   net.ipv4.tcp_timestamps = 1     #表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

   net.ipv4.tcp_fin_timeout = 2      #用来设置保持在FIN_WAIT_2状态的时间

保存后sysctl -p生效

 

################################ 案例分析 ##############################

 

情景:客户端连续登录两次服务器,服务器应用把第一个socket断掉,客户端利用失效的第一个socket发送消息给服务器。

服务器利用抓包工具tcpdump能抓到数据包,但是服务器应用socket无法接收到数据!

 

原因剖析:服务器应用主动断开socket的时候,客户端其实就能监听到socket断掉了,如果客户端自己不断开socket,那么

就会进入close_wait状态,众所周知,只要你的应用有close_wait状态,那么肯定是程序代码出了问题,如下图分析:

 

  我们都知道tcp是全双工的,假如我们把上图左侧作为服务器,右侧作为客户端,服务器主动断开socket,给客户端发送FIN报文,但是

客户端一直不断开socket,所以客户端一直处于close_wait状态。这时候socket其实已经失效了,这时客户端继续给服务器发消息,发的

第一条FIN报文服务器机器是能接收到数据包的,但是由于应用已经断开socket,所以应用socket是接收不到数据的。所以说客户端进入

close_wait后,他发的第一条消息是正确的,然后就处于断开状态。后面哪怕持续发信息也没任何作用,哪怕不报错。

  正常操作应该是一方主动断开socket之后,另外一方捕捉到然后立即正确的被动断开socket(一旦程序代码出错就会处于close_wait状态),

最终主动断开socket的一方进入time_wait状态,整个tcp四次挥手才算完美完成!

 

我们可以从上图看出,主动断开socket的一方,最终都会进入time_wait状态,这是因为它需要处于这个状态来防止,ack丢失后,另外一方

重复发FIN。需要等待2msl的时间。

 

PS:tcp长连接其实不是需要考虑time_wait状态,耗尽句柄资源的问题,因为服务器主动断开socket的情景不多。 

 

分析工具:

ubuntu下安装抓包工具tcpdump

sudo apt-get install wireshark 或 tcpdump

 

监听端口数据包:

sudo tcpdump tcp port 8007

 

 

查看端口连接状态

lsof -i:8007

 

 

查看tcp所有状态

netstat -an|awk '/tcp/ {print $6}'|sort|uniq -c

 

 

查看tcp单个状态

netstat -an | grep CLOSE_WAIT | wc -l 

 

posted @ 2018-11-07 17:09  天之草  阅读(2971)  评论(0编辑  收藏  举报