TIME_WAIT
TCP四次挥手
首先先从TCP的四次挥手说起,下面给出TCP四次挥手展示图:
TCP连接终止时,主机1先发送FIN报文,主机2进入COLSE_WAIT状态,并发送一个ACK应答,同时,主机2通过read调用获得EOF,并将此结果通知应用程序进行主动关闭操作,发送FIN报文。主机1在接收到FIN报文后发送ACK应答,此时主机1进入了TIME_WAIT状态。
主机1在TIME_WAIT停留持续时间是固定的,是最长分节生命周期MSL(maximum segment lifetime)的两倍,一般称之为2MSL。和大多数BSD派生的系统一样,Linux系统里有一个硬编码的字段,名称为TCP_TIMEWAIT_LEN,其值为60秒。也就是说,Linux系统停留在TIME_WAIT的时间为固定的60秒。过了这个时间之后,主机1就进入CLOSED状态。
首先需要明确的一点是:只有发起连接终止的一方会进入TIME_WAIT状态。
TIME_WAIT的作用
我们来分析一下,为什么主机1不直接进入CLOSED状态,而要停留在TIME_WAIT状态。
- 第一点:这样做的目的是为了确保最后的ACK能够让被动关闭方主机2接收,从而帮助其正常关闭。
TCP在设计的时候,做了充分的容错性设计,比如,TCP假设报文会出错,需要重传。在这里,如果图中主机1的ACK报文没有传输成功,那么主机2就会重新发送FIN报文。
如果主机1没有维护TIME_WAIT状态,而是直接进入CLOSED状态,它就失去了当前状态的上下文,只能回复一个RST操作,从而导致被动关闭方出现错误。
现在主机1知道自己处于TIME_WAIT的状态,就可以在接收到FIN报文之后,重新发出一个ACK报文,使得主机2可以进入正常的CLOSED状态。
- 第二点:该点与连接'化身'和报文迷走有关系,为了让旧连接的重复分节在网络中自然消失。
我们知道,在网络中,经常会发生报文经过一段时间才能到达目的地的情况,产生的原因多种多样的,如路由器重启,链路突然出现故障等等。如果迷走报文到达时,发现TCP连接四元组(源IP,源端口,目的IP,目的端口)所代表的连接不复存在,那么很简单,这个报文自然丢弃。
我们考虑这样一个场景,在原连接中断后,又新建了一个原连接的"化身",说是化身其实是因为这个连接和原先的连接四元组完全相同,如果迷失报文经过一段时间也到达,那么这个报文会被认为是连接"化身"的一个TCP分节,这样就会对TCP通信产生影响。
所以,TCP就设计出了这么一个机制,经过2MSL这个时间,足以让两个方向上的分组都被丢弃,使得原来连接的分组在网络中都自然消失,再出现的分组一定都是新化身所产生的。
- 重点注意:
2MSL的时间是从主机1接收FIN后发送ACK开始计时的;如果在TIME_WAIT时间内,因为主机1的ACK没有传输到主机2,主机1又接收到了主机2重发的FIN报文,那么2MSL时间将重新计时。道理很简单,因为2MSL的时间,目的是为了让旧连接的所有报文都能自然消亡,现在主机1重新发送了ACK报文,自然需要重新计时,以便防止这个ACK报文对新可能的连接化身造成干扰。
TIME_WAIT的危害
过多的TIME_WAIT的主要危害有两种:
- 第一是内存资源占用,这个危害目前看来并不是很严重,基本可以忽略。
- 第二是对端口资源的占用,一个TCP连接至少消耗一个本地端口。要知道,端口资源也是有限的,一般可以开启的端口为32768~61000,也可以通过net.ipv4.ip_local_port_range指定,如果TIME_WAIT状态过多,会导致无法创建新连接。