4. TIME_WAIT 状态详解:它为什么赖着不走?

说到端口复用,TIME_WAIT 绝对是个绕不开的话题。我见过不少运维同事,一看到 netstat 里 TIME_WAIT 一大堆就慌了,觉得是系统出问题了。其实不然,TIME_WAIT 是 TCP 协议设计者故意留下的一个状态。它存在,是有道理的。

但问题来了——它确实会卡住端口。你想复用端口?TIME_WAIT 不消失,端口就占着。今天我们就把它彻底讲透。

4.1 TIME_WAIT 是怎么产生的?

先看一个最典型的场景:客户端主动发起关闭连接。

客户端(主动关闭方)         服务器(被动关闭方)
    FIN_WAIT_1  ──── FIN ────>  CLOSE_WAIT
    FIN_WAIT_2  <─── ACK ────  CLOSE_WAIT
    TIME_WAIT   <─── FIN ────  LAST_ACK
    TIME_WAIT   ──── ACK ────>  CLOSED
    2MSL 后关闭

看到没?主动关闭的一方,在发送完最后一个 ACK 之后,不会立刻进入 CLOSED,而是进入 TIME_WAIT。这个状态会持续 2MSL(Maximum Segment Lifetime)的时间。

为什么要等?说白了就两个原因:

  • 保证最后一个 ACK 能到达对方——如果 ACK 丢了,对方会重发 FIN,你还在 TIME_WAIT 就能重新响应。
  • 防止旧连接的报文干扰新连接——网络里可能还有延迟的旧数据包,等 2MSL 确保它们都消亡了。

我个人习惯把这个过程叫做「TCP 的体面退场」。嗯,虽然有点慢,但确实体面。

4.2 2MSL 到底有多久?

这取决于操作系统。我整理了一张表,方便你对照:

操作系统 MSL 值 TIME_WAIT 持续时间 备注
Linux 30s(硬编码) 60s 可通过 tcp_fin_timeout 间接影响
Windows 120s(默认) 240s 注册表可调,但一般不推荐
FreeBSD 30s 60s 与 Linux 类似
macOS 15s 30s 相对较短

Linux 的 MSL 是 30 秒,写死在 include/net/tcp.h 里:

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT state, about 60 seconds */

注意看,它直接定义的是 60 秒,而不是 2*30。所以 Linux 下 TIME_WAIT 就是 60 秒,雷打不动。

一个小技巧: 你可以用 sysctl net.ipv4.tcp_fin_timeout 查看当前值。虽然这个参数主要影响 FIN_WAIT_2,但有些场景下调整它也能间接缓解 TIME_WAIT 堆积。

4.3 TIME_WAIT 对端口复用的影响

这才是今天的重点。你想想看,一个端口被 TIME_WAIT 占着,新的连接想用同一个端口(尤其是客户端端口),就得等 60 秒。高并发场景下,这简直是灾难。

我曾在项目中遇到过一个问题:一个反向代理服务器,每秒要建立几千个连接到后端。结果跑着跑着,客户端端口全被 TIME_WAIT 占满了,新连接报 Cannot assign requested address。当时排查了半天,最后发现就是 TIME_WAIT 在作祟。

具体来说,TIME_WAIT 对端口复用的影响体现在三个方面:

  1. 端口耗尽——客户端端口范围有限(默认 32768-60999),TIME_WAIT 堆积会导致无端口可用。
  2. 连接失败——新连接尝试绑定已被 TIME_WAIT 占用的端口时,会返回 EADDRINUSE 错误。
  3. 数据混淆风险——如果不等 2MSL 就复用端口,旧连接的延迟报文可能被新连接误收。
注意: 服务端监听端口(比如 :80、:443)不受 TIME_WAIT 影响,因为监听端口不参与主动关闭。TIME_WAIT 只卡主动关闭方的临时端口。

4.4 核心逻辑:一张图看懂

下面这张 SVG 图,把 TIME_WAIT 的产生、持续时间和影响串起来了。我建议你多看几遍,理解了这个流程,后面讲优化方案就顺了。

TIME_WAIT 状态核心逻辑图 TIME_WAIT 状态核心逻辑 主动关闭连接 发送最后一个 ACK 进入 TIME_WAIT 持续 2MSL(60s) 目的 1:保证最后一个 ACK 可达 防止对方重发 FIN 目的 2:防止旧报文干扰新连接 等待网络中延迟报文消亡 影响:端口被占用 → 新连接 EADDRINUSE 关键结论 TIME_WAIT 是 TCP 可靠性的保障,但也是端口复用的最大障碍 优化思路:缩短等待时间 / 启用端口重用 / 避免主动关闭

4.5 避坑指南:我踩过的几个坑

讲完理论,说点实战中容易翻车的地方。

坑一:误以为 TIME_WAIT 是异常

我曾经有个同事,看到服务器上几万个 TIME_WAIT,二话不说就重启了服务。结果业务中断了 5 分钟,TIME_WAIT 该在还在。其实 TIME_WAIT 是正常状态,只要端口没耗尽,就不用管它。

坑二:盲目调小 tcp_fin_timeout

有人觉得把 tcp_fin_timeout 从 60 改成 10 就能解决问题。但注意,这个参数影响的是 FIN_WAIT_2,不是 TIME_WAIT。Linux 的 TIME_WAIT 时长是硬编码的,改这个没用。

坑三:忽略 net.ipv4.ip_local_port_range

端口范围默认是 32768-60999,总共才 28232 个端口。如果每秒建 1000 个连接,每个连接 TIME_WAIT 60 秒,那最多支撑 470 个并发连接就会耗尽。我建议你根据业务量,适当扩大端口范围。

4.6 小结:记住这三句话

  • TIME_WAIT 是主动关闭方的事——服务端监听端口不受影响,别瞎紧张。
  • 2MSL 是硬约束——Linux 下就是 60 秒,想绕过得用 SO_REUSEADDR 或 tcp_tw_reuse(后面章节会讲)。
  • 端口耗尽才是真问题——TIME_WAIT 本身不可怕,可怕的是它把端口池占满了。

嗯,这一章的内容就到这里。理解了 TIME_WAIT 的本质,后面讲优化方案你才能明白为什么那么做。端口复用不是无脑开参数,而是要在可靠性和性能之间找到平衡点。