0%

tcp的TIME_WAIT和CLOSE_WAIT

TIME_WAIT 是客户端(主动发起方)的状态,在发送第四次挥手后进入的一个状态。服务器也有可能出现TIME_WAIT,服务器也有可能是断开连接的主动发起方。

1. TIME_WAIT

1.1 为什么等待 2MSL?

  1. MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

    MSL 与 TTL 的区别: MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。TTL 的值一般是 64,Linux 将 MSL 设置为 30 秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了。

    可以看到 2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若客户端第四次挥手 在一个 MSL 内丢失,这样被动方重发的 第三次挥手会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。

  2. 在 Linux 系统里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。

    2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。

1.2 为什么需要 TIME_WAIT?

那么客户端为什么进入 TIME_WAIT 而不是直接关闭?保证「被动关闭连接」的一方,能被正确的关闭。

从 RFC 793 对 TIME_WAIT 状态的定义中,我们可以发现该状态的另一个重要作用,等待足够长的时间以确定远程的 TCP 连接接收到了其发出的终止连接消息 FIN 对应的 ACK

TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.

1. 保证「被动关闭连接」的一方,能被正确的关闭;

因为客户端发送挥手④的时候,有可能失败。

如果客户端直接关闭,服务器重发挥手③,客户端处于关闭不响应,服务器无法释放资源。

2. 防止相同四元组再次收到

在关闭一个 TCP 连接后,马上又重新建立起一个相同的 IP 地址和端口之间的 TCP 连接,后一个连接被称为前一个连接的化身,那么有可能出现这种情况,前一个连接的迷途重复报文在前一个连接终止后出现,从而被误解成从属于新的化身。

为了避免这个情 况, TIME_WAIT 状态需要持续 2MSL,因为这样就可以保证当成功建立一个 TCP 连接的时候,来自连接先前化身的重复报文已经在网络中消逝。

1.3 TIME_WAIT 过多危害?

  • 第一是占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等;
  • 第二是占用端口资源,端口资源也是有限的。

如果服务端出现大量的 TIME_WAIT 状态的 TCP 连接,就是说明服务端主动断开了很多 TCP 连接。问题来了,什么场景下服务端会主动断开连接呢?

  1. HTTP 没有使用长连接
  2. HTTP 长连接超时
  3. HTTP 长连接的请求数量达到上限

1.4 如何优化

《UNIX网络编程》一书中却说道:TIME_WAIT 是我们的朋友,它是有助于我们的,不要试图避免这个状态,而是应该弄清楚它。

如果服务端要避免过多的 TIME_WAIT 状态的连接,就永远不要主动断开连接,让客户端去断开,由分布在各处的客户端去承受 TIME_WAIT。

打开 sysctl.conf 文件,修改以下几个参数:

1
2
3
net.ipv4.tcp_tw_recycle = 1	 #表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
net.ipv4.tcp_tw_reuse = 1 # 重新使用TIME_WAIT状态的连接。
net.ipv4.tcp_timestamps = 1 # 需要打开对 TCP 时间戳的支持。

2. CLOSE_WAIT

CLOSE_WAIT 状态是「被动关闭方」才会有的状态,而且如果「被动关闭方」没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。

2.1 原因

当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。

当服务端出现大量 CLOSE_WAIT 状态的连接的时候,通常都是代码的问题,这时候我们需要针对具体的代码一步一步的进行排查和定位,主要分析的方向就是服务端为什么没有调用 close。

3. 异常关闭

3.1 客户端断电

客户端出现故障指的是客户端的主机发生了宕机,或者断电的场景。也就是服务端的 TCP 连接将一直处于 ESTABLISH 状态,占用着系统资源。

为了避免这种情况,TCP 搞了个保活机制。这个机制的原理是这样的:

定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。

在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:

1
2
3
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
  • tcp_keepalive_time=7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活动,则会启动保活机制
  • tcp_keepalive_intvl=75:表示每次检测间隔 75 秒;
  • tcp_keepalive_probes=9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接。

也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接。

3.2 服务端崩溃

TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。

4. 头脑风暴

  • TIME_WAIT 1. 保证「被动关闭连接」的一方,能被正确的关闭;2. 防止相同四元组再次收到。

  • 服务器TIME_WAIT过多原因,说明是服务器是主动方。确认http keepalive使用,超时,数量限制。可以调整linux参数不建议。

  • CLOSE_WAIT 服务端的程序没有调用 close 函数关闭连接。

5. 参考资料

可以加首页作者微信,咨询相关问题!