TIME_WAIT和CLOSE_WAIT状态解析

什么是TIME-WAIT和CLOSE-WAIT ?

  众所周知,由于socket是全双工的工作模式,一个socket的关闭,是需要四次握手来完成的:
  1)主动关闭连接的一方,调用close();协议层发送FIN包 ;
  2)被动关闭的一方收到FIN包后,协议层回复ACK;然后被动关闭的一方,进入CLOSE_WAIT状态,主动关闭的一方等待对方关闭,则进入FIN_WAIT_2状态;此时,主动关闭的一方等待被动关闭一方的应用程序,调用close操作 ;
  3)被动关闭的一方在完成所有数据发送后,调用close()操作;此时,协议层发送FIN包给主动关闭的一方,等待对方的ACK,被动关闭的一方进入LAST_ACK状态;
  4)主动关闭的一方收到FIN包,协议层回复ACK;此时,主动关闭连接的一方,进入TIME_WAIT状态;而被动关闭的一方,进入CLOSED状态 ;
  5)等待2MSL时间,主动关闭的一方,结束TIME_WAIT,进入CLOSED状态 ;
  通过上面的一次socket关闭操作,可以得出以下几点:
  1)主动关闭连接的一方 – 也就是主动调用socket的close操作的一方,最终会进入TIME_WAIT状态 ;
  2)被动关闭连接的一方,有一个中间状态,即CLOSE_WAIT,因为协议层在等待上层的应用程序,主动调用close操作后才主动关闭这条连接 ;
  3)TIME_WAIT会默认等待2MSL时间后,才最终进入CLOSED状态;
  4)在一个连接没有进入CLOSED状态之前,这个连接是不能被重用的!

TIME_WAIT有什么用?

  socket其实就是一个五元组,包括:源IP, 源端口, 目的IP, 目的端口, 类型(TCP or UDP)。这个五元组,即标识了一条可用的连接。比如说,如果本地出口IP是110.122.144.166,那么你的浏览器在连接某一个Web服务器,例如百度的时候,这条socket连接的五元组可能就是:[110.122.144.166:45678, tcp, 110.88.92.104:80] ,
源IP为你的出口IP地址 110.122.144.166,源端口为随机端口 45678,目的IP为百度的某一个负载均衡服务器IP 110.88.92.104,端口为HTTP标准的80端口。如果这个时候,你再开一个浏览器,访问百度,将会产生一条新的连接:[110.122.144.166:43678, tcp, 110.88.92.104:80] , 这条新的连接的源端口为一个新的随机端口 43678。
  如果来做个类比的话,TIME_WAIT的出现,对应的是你的程序里的异常处理,它的出现,就是为了解决网络的丢包和网络不稳定所带来的其他问题:
  1)防止前一个连接【五元组,这里继续以 110.122.144.166:45678, tcp, 110.88.92.104:80 为例】上延迟的数据包或者丢失重传的数据包,被后面复用的连接【前一个连接关闭后,此时你再次访问百度,新的连接可能还是由110.122.144.166:45678, tcp, 110.88.92.104:80 这个五元组来表示,也就是源端口凑巧还是45678】错误的接收(异常:数据丢了,或者传输太慢了),参见下图:

  SEQ=3的数据包丢失,重传第一次,没有得到ACK确认;
  如果没有TIME_WAIT,或者TIME_WAIT时间非常端,那么关闭的连接【110.122.144.166:45678, tcp, 110.88.92.104:80 的状态变为了CLOSED,源端口可被再次利用】,马上被重用【对110.88.92.104:80新建的连接,复用了之前的随机端口45678】,并连续发送SEQ=1,2 的数据包;
  此时,前面的连接上的SEQ=3的数据包再次重传,同时,seq的序号刚好也是3(这个很重要,不然,SEQ的序号对不上,就会RST掉),此时,前面一个连接上的数据被后面的一个连接错误的接收;
  2)确保连接方能在时间范围内,关闭自己的连接。其实,也是因为丢包造成的,参见下图:

  主动关闭方关闭了连接,发送了FIN;
  被动关闭方回复ACK同时也执行关闭动作,发送FIN包;此时,被动关闭的一方进入LAST_ACK状态;
  主动关闭的一方回去了ACK,主动关闭一方进入TIME_WAIT状态;
  但是最后的ACK丢失,被动关闭的一方还继续停留在LAST_ACK状态;   此时,如果没有TIME_WAIT的存在,或者说,停留在TIME_WAIT上的时间很短,则主动关闭的一方很快就进入了CLOSED状态,也即是说,如果此时新建一个连接,源随机端口如果被复用,在connect发送SYN包后,由于被动方仍认为这条连接【五元组】还在等待ACK,但是却收到了SYN,则被动方会回复RST;
  造成主动创建连接的一方,由于收到了RST,则连接无法成功;
  所以,这里看到了,TIME_WAIT的存在是很重要的,如果强制忽略TIME_WAIT,还是有很高的机率,造成数据混乱,或者短暂性的连接失败。那么,为什么说TIME_WAIT状态会是持续2MSL(2倍的max segment lifetime)呢?这个2MSL,是RFC 793里定义的,这个定义,更多的是一种保障(IP数据包里的TTL,即数据最多存活的跳数,真正反应的才是数据在网络上的存活时间),确保最后丢失了ACK,被动关闭的一方再次重发FIN并等待回复的ACK,一来一去两个来回。内核里,写死了这个MSL的时间为:30秒(RFC里建议的MSL其实是2分钟,但是很多实现都是30秒),所以TIME_WAIT的即为1分钟。