一般来说,tcp正常关闭需要四个包。比如a和b关闭连接,a先给b发一个fin,b会进行确认ack,然后b也会发出fin,当a接受到这个fin,并发出最后一个ack后,就会处于time_wait状态。这个时间长短跟操作系统有关,一般会在1-4分钟,也就是两倍的数据包(2msl)最大生存时间。
当处于TIME_WAIT状态时,该连接所用的本地端口不能被重新使用,除非在bind前设置了SO_REUSEADDR选项;即使通过设置SO_REUSEADDR选项,重新使用仍处于2MSL等待的本地端口,也不能给处于TIME_WAIT状态的连接发起新的化身。
TIME_WAIT状态有两个存在理由:(1)可靠地终止TCP全双工连接。如果网络是不可靠的,无法保证a最后发送的ACK报文会一定被b收到,因此b处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,因此a必须维护状态信息,以允许它重新发送最终的ack。要是a维护状态信息,它将响应一个RST,该连接将被b解释成一个错误。(2)允许老的重复分节在网络中消逝。我们假设在12.106.32.254的1500端口和206.168.112.219的21端口之间有一个TCP连接。我们关闭这个连接,过一段时间后在相同的IP和端口之间建立另一个连接。后一个连接成为前一个连接的化身,因为它们的IP地址和端口都相同。TCP必须防止来自某个连接的老的重复分组在连接已终止后再现,从而被误解为属于同一个连接的某个新的化身。为做到这一点TCP将不给处于TIME_WAIT状态的连接发起新的化身。既然TIME_WAIT的状态持续时间是2MSL,这就足以让某个方向上的分组最多存活MSL秒被丢弃,另一个方向上的应答最多存活MSL秒也被丢弃。通过实施这个规则,我们就能保证每成功建立一个TCP连接时,来自该连接先前化身的老的重复分组都已经在网络中消逝。
我们可以通过设置SO_LINGER套接口选项,避免主动关闭端进入TIME_WAIT状态。
struct linger{
int l_onoff; /*0=off,nonzero=on*/
int l_linger; /*linger time,POSIX specifies units as seconds*/
}
我们把l_onoff置为非0且l_linger为0,那么当close某个连接时TCP将终止该连接。也就是说TCP将丢弃套接字发送缓冲区和接收缓冲区中的数据,并发送一个RST给对端,连接的状态被置为CLOSED,而没有通常的四分组连接终止序列。这么一来避免了TIME_WAIT状态,然而存在以下可能性:在2MSL秒内创建该连接的另一个化身,导致来自刚被终止的连接上的旧的重复分节被不正确地递交到新的化身上。
参考资料:
1.《UNIX网络编程 VOL.1》2.7节
2.《UNIX网络编程 VOL.1》7.5.6节