说说Tcp断开连接时的Time_Wait状态

前段时间码代码,关于网络编程的,分为了服务器和客户端。因为要验证业务逻辑的正确性,所以需边改边测,时不时也会重启下服务器或客户端。那正巧有一次,当我改好了服务器的代码然后重启的时候,程序给报异常了,说端口怎么怎么样了,我心想没有其他程序占用这个端口吧,然后马上再重启了一次还是不行。因为当时急着要解决其他的问题,所以只好暂时把服务器监听的端口给改了,接着程序给运行起来了,过了好一会儿,解决验证了其他问题后,我决定再来看看刚刚端口占用的问题,于是把服务器端口给改了回来,运行,咦?可以了。这是怎么回事,当时我百思不得其解~

后来因为前段时间在看《Tcp/Ip详解》中第18章——Tcp连接的建立和断开的时候,里面讲到了主动断开时的TIME_WAIT状态,我才明白当时重启服务器后为什么会报端口占用的问题。先来看看连接断开时何时出现TIME_WAIT?首先必须了解断开连接时的4次握手,如图:

我们把TCP正常关闭连接的状态变化,转化成文字描述如下:

  1. 发起方更改状态为FIN_WAIT_1,关闭应用程序进程,发出一个TCP的FIN段;
  2. 接收方收到FIN段,返回一个带确认序号的ACK,同时向自己对应的进程发送一个文件结束符EOF,同时更改状态为CLOSE_WAIT,发起方接到ACK后状态更改为FIN_WAIT_2;
  3. 接收方关闭应用程序进程,更改状态为LAST_ACK,并向对方发出一个TCP的FIN段;
  4. 发起方接到FIN后状态更改为TIME_WAIT,并发出这个FIN的ACK确认。ACK发送成功后(2MSL内)双方TCP状态变为CLOSED。

从上面的描述我们知道了TIME_WAIT状态出现的时机,而TIME_WAIT状态称为2MSL等待状态,这里提到了一个关键字MSL(Maximum Segment Lifetime),它是任何报文段被丢弃钱在网络内的最长时间。所以对于MSL值的处理简而言之概括是,当Tcp一端执行主动关闭,到收到对方的FIN后就进入了TIME_WAIT状态,然后执行发回ACK,接着整个Tcp连接就必须要在这个状态停留2倍MSL的时间!

这又有一个问题的提出:为什么TIME_WAIT状态要让TCP连接停留2MSL(max segment lifetime)时间。原因如下:

  1. 防止上一次连接中的包,迷路后重新出现,即第一个连接的重复报文到达,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)
  2. 可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。

所以,正是基于此,Tcp规定了一个连接在TIME_WAIT状态期间,定义这个连接的插口(客户端IP/Port,服务器IP/Port)不能被使用。这个连接只能在2MSL结束后才能被使用。那这样的话,假如我们终止一个客户端程序,并立即重启它,则这个新的客户端程序就不能使用相同的本地端口。但这不会带来什么问题,因为客户端使用的系统随机分配的本地端口,其实是并不关心这个端口号是什么。然而要是服务器主动关闭进入TIME_WAIT,然后再重启它就有可能出错!因为服务器的端口是‘熟知’的,而这个端口是出于上一个2MSL连接的一部分。所以在重新启动服务器程序前,它需要等待在1~4分钟左右。

最后小结了一大堆,到这儿相信都应该明白了本文开篇为什么立马重启服务器会出现端口占用的问题,而许久过后再开启就正常的答案了吧。由此得出结论,要码好网络编程,《Tcp/Ip协议详解》是圣经,定要学好它!许多当时摸不着头脑的问题,或许看了书中的某些章节,疑惑就迎刃而解了。