理解Time-Wait

What is it?

TIME-WAIT状态的主要作用在于TCP连接的拆除阶段。拆除一个TCP连接通常需要交换4个segment,如下图所示:

image

Host1上的应用程序关闭了自己这一端的连接,使得TCP向Host 2发送了一个FIN。Host 2对这个FIN进行ACK,并将这个FIN作为一个EOF传送给应用程序(假设Host 2上的应用程序有一个挂起的read操作)。经过一段时间后,Host 2上的应用程序close掉自己那一端的连接,这样TCP会向Host 2发送一个FIN,Host 1会以一个ACK应答。

此时,Host 2关闭连接,并释放资源。从Host 2的角度来看,连接已经不复存在了。但是,Host 1还没有关闭连接,而是会进入TIME-WAIT状态,并在这个状态停留2MSL(2 Maximum Segment Lifetime)。

MSL(最大分段寿命)是segment被丢弃之前在网络中可以存活的最大时长。

等待了2MSL之后,Host 1也将TCP连接关闭,并释放资源。

注意

关于TIME-WAIT状态,需要注意以下3点:

1. 通常,只有主动关闭TCP连接的那一端会进入TIME-WAIT状态。

主动关闭表示发送第一条FIN的那一端。另一端称为被动关闭。有一种可能是同时关闭,即两端同时关闭连接,并向网络发送FIN,在这种情况下,就认为两个应用程序都进行了主动关闭,两端都会进入TIME-WAIT状态。

2. RFC将MSL定义为2分钟,根据这个定义,连接会在TIME-WAIT状态停留4分钟。但实际上,各实现均有不同,一般TIME-WAIT状态持续时间在1分钟到4分钟之间。

3. 如果连接处于TIME-WAIT状态时有segment到达,就重启2MSL的定时器。

为什么需要TIME-WAIT?

使用TIME-WAIT状态主要有两个目的:

1. 维护连接状态

It maintains the connection state in case the final ACK, which is sent by the side doing the active close, is lost, causing the other side to resend its FIN.

如果主动关闭连接的那端发送的最后一条ACK丢失,那么另一端会重新发送FIN(注意,这两者一来一回最长的时间正好是2MSL),如果此时主动关闭方已经关闭了连接的话(也就是说没有TIME-WAIT的话),那么主动方收到对方重送的FIN后,由于TCP已经没有这条连接的记录了,会发送RST(重置)给对端,对端会将此标记为error,因而无法正常关闭连接。但是,如果主动方处于TIME-WAIT状态,那么它就仍然有这条TCP连接的记录,就可以对对端重送的FIN进行正确的相应。

这也就解释了为什么连接在TIME-WAIT状态收到一个segment时,需要重启2MSL的定时器。如果最后一条ACK丢失,对端重传FIN,处于TIME-WAIT状态的这一端会再次对FIN进行ACK,并且重启定时器以防这一条ACK也丢失。

2. 为了耗尽网络中所有基于此连接的“走失的segment”提供时间。

It provides time for any ‘stray segments’ from the connection to drain from the network.

IP数据报在网络上传输时,难免会发生丢失或延迟,当然,TCP会有一些机制来保证对没有被即时ACK的segment进行重传。

如果没有TIME-WAIT状态,并且,这些延迟的或者被重传的segment在连接被关闭后到达,TCP只是将这些segment丢弃并以RST回应,当RST到达另一端时,由于这台Host中也没有这条TCP连接的记录了,所以RST会被丢弃,在这种情况下不会产生问题。但是,如果在这两台主机上用同样的端口号建立了一条新连接,那么之前那个TCP连接中延迟的或者被重传的segment的sequence number可能正好落在新连接的接收窗口中(receive sliding window),那么这部分的数据就会被接收,从而对新连接造成破坏。

TIME-WAIT状态确保了在原有连接的所有segment从网络中消失之前,不会再次使用原来用过的socket对(两个IP地址以及相应的端口号),因此TIME-WAIT状态在提供TCP可靠性方面发挥了重要的作用。

TIME-WAIT 暗杀

不幸的是,TIME-WAIT状态可以被提前终止,这被称为TIME-WAIT暗杀。有以下两种情况:

1. RFC 793指出,当一条连接处于TIME-WAIT状态并收到一个RST时,应该立即将连接关闭。

假设,现在有一条连接处于TIME-WAIT状态,并且之前一个延迟的或者被retransmission的segment到达,而这个segmentTCP无法接收(例如序列号在接受窗口之外),TCP会以一个ACK相应,说明它所期待的序列号(就是对等实体的FIN之后的序列号)。但对等实体中已经没有这个TCP连接的记录了,所以会以一个RST来进行ACK。当这个RST回到处于TIME-WAIT状态的那一端时,会使连接立即关闭——TIME-WAIT状态被暗杀了。

幸运的是,对TCP进行修改,使其忽略TIME-WAIT状态下的RST即可,某些协议栈进行了这样的修改。

2. 另一种TIME-WAIT暗杀的方式是有意为之的。

即使应用程序是TCP连接的主动关闭方,程序员也可以使用套接字选项 SO_LINGER 迫使连接立即关闭,而不经过TIME-WAIT状态。有时,会推荐使用这种可疑的方式使得服务器跳出TIME-WAIT状态,这样就可以在服务器重启或者崩溃后快速重启。但是,健壮的应用程序永远都不应该干扰TIME-WAIT状态,因为这是TCP可靠机制的重要组成部分。

通常,应用程序关闭一条连接时,即使TCP发送缓冲区中仍然有数据要发送,close调用也会立即返回,虽然,TCP还是会尝试着发送未发送出去的数据,但是应用程序并不知道是否发送成功了。为了防止这个问题的发生,可以设置套接字选项SO_LINGER(通过填写linger结构体,并调用setsockopt来设置套接字的SO_LINGER选项)。

image

如果成员l_onoff为零,那么linger选项将被关闭,其行为与默认行为相同——close调用立即返回,内核继续尝试发送所有未发送的数据。

如果l_onoff非零,其行为取决于l_linger的值。如果l_linger非零,就将l_linger作为内核等待挂起的数据被发送出去并被确认(ACK)而逗留的时间间隔。也就是说,close在所有数据都发送完毕,或者时间间隔到期之前是不会返回的。

如果l_linger时间到了,仍然有未发送的数据,close就返回EWOULDBLOCK,所有未发送的数据都可能丢失。如果数据都发送出去了,close就返回0。

当然,以这种方式使用SO_LINGER,能保证的也只是数据到达了对方的TCP接收缓冲区中,不能保证数据被对面的应用程序读走。

最后如果l_onoff非零,l_linger为零,TCP连接会被丢弃。也就是说,向对等实体发送一个RST(表明连接已经不存在),不经过TIME-WAIT状态就立即关闭连接。这就是所谓的有意的TIME-WAIT暗杀。

 

posted @ 2015-08-14 21:52  Acjx  阅读(885)  评论(0编辑  收藏  举报