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 对端口复用的影响体现在三个方面:
- 端口耗尽——客户端端口范围有限(默认 32768-60999),TIME_WAIT 堆积会导致无端口可用。
- 连接失败——新连接尝试绑定已被 TIME_WAIT 占用的端口时,会返回 EADDRINUSE 错误。
- 数据混淆风险——如果不等 2MSL 就复用端口,旧连接的延迟报文可能被新连接误收。
4.4 核心逻辑:一张图看懂
下面这张 SVG 图,把 TIME_WAIT 的产生、持续时间和影响串起来了。我建议你多看几遍,理解了这个流程,后面讲优化方案就顺了。
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 的本质,后面讲优化方案你才能明白为什么那么做。端口复用不是无脑开参数,而是要在可靠性和性能之间找到平衡点。