TIME_WAIT 是主动关闭方发出最后一个 ACK 之后进入的 2MSL 等待。它不是多余的仪式:一边给可能丢失的最后 ACK 留补救机会,一边等旧连接的滞留报文从网络里彻底消散。它是协议设计的正常一环,真正要警惕的是短连接风暴下的端口耗尽和被误用的内核参数。

它解决什么问题

四次挥手的最后一步,主动关闭方回完 ACK 为什么不能立刻关门走人?因为此刻有两件事还没有着落:

  1. 最后一个 ACK 可能丢在路上。被动方收不到确认,会超时重传 FIN;若主动方已经彻底消失,这个重传的 FIN 将无人应答。
  2. 旧连接的报文可能还在网络里游荡。TCP 连接靠四元组(源 IP、源端口、目的 IP、目的端口)唯一标识,旧连接刚关就用同一四元组开新连接,滞留的旧报文可能被当成新连接的数据收下。

这两个问题都没有别的机制兜底,于是协议安排主动关闭方在原地等一段足够长的时间——这就是 TIME_WAIT。

核心原理

理由一:让最后一个 ACK 有补救机会

主动方 A                     被动方 B
| <---------- FIN -------- |
| ---------- ACK ---X 丢失 | B 收不到确认,超时后重传 FIN
| A 在 TIME_WAIT 中守候 |
| <-------- 重传 FIN ----- |
| ---------- ACK --------> | B 终于收到确认,进入 CLOSED

如果 A 发完 ACK 立即进入 CLOSED,B 重传的 FIN 到达时本端已无连接状态,只能回 RST——B 会感知为连接被异常重置。A 停留在 TIME_WAIT,才能从容地再补一个 ACK。

等待时长定为 2MSL。MSL(Maximum Segment Lifetime)是报文段在网络中的最大生存时间;最坏情况下,ACK 在路上走一个 MSL 才被判丢失,B 重传的 FIN 又花一个 MSL 到达 A——两个 MSL 正好覆盖一个来回。注意 Linux 并未动态计算,而是把 TIME_WAIT 时长写死为 60 秒(内核常量 TCP_TIMEWAIT_LEN)。

理由二:让旧连接的报文彻底消散

假设旧连接 client:50000 → server:443 里有一个服务端的报文绕了远路迟迟未到。连接关闭后客户端立刻复用 50000 端口发起新连接,此时旧报文抵达,若其序列号恰好落在新连接的接收窗口内,就会被误收——新旧数据就此混淆。

旧连接  client:50000 -> server:443   一个 SEQ=301 的段滞留网络
关闭后立即复用同端口建立新连接
滞留的旧段此时抵达,若落入新连接窗口则可能被误收

TIME_WAIT 占住四元组 2MSL,等所有滞留报文自然寿终,新连接才允许使用同一四元组,从根源上隔离了这种污染。序列号是 32 位会回绕,不能单靠它永久区分新老报文;时间戳、随机 ISN 等手段能提供额外保护,但都不是 TIME_WAIT 的完全替代品。

深入机制

大量 TIME_WAIT 的真正危害:端口耗尽

TIME_WAIT 条目本身占用的内存很小,堆积的真正风险在临时端口耗尽。客户端每新建一条外连都要占用一个本地临时端口,而该端口要等 TIME_WAIT 结束才能复用。可用端口数除以 TIME_WAIT 时长,就是对同一目标 IP:Port 新建短连接的速率上限:

默认端口范围 32768~60999,约 2.8 万个
2.8 万 ÷ 60 秒 ≈ 每秒 470 条新连接(对同一目的地址)

超过这个速率,connect 就会报 Cannot assign requested address。网关在 NAT 场景下更敏感:大量内网连接被映射到有限的公网端口上,TIME_WAIT 还会占用 NAT 设备的会话表项。反过来,如果连接分散到不同目的地址、或服务端以长连接为主,几万个 TIME_WAIT 也可能完全无害——它是正常现象还是故障前兆,取决于新建连接的速率和集中度。

tcp_tw_reuse 与 tcp_tw_recycle 的边界

tcp_tw_reuse 允许新外连在安全条件下复用 TIME_WAIT socket:它依赖 TCP 时间戳保证新连接的序号一定大于旧连接的遗留报文,因此只对主动发起外连的一侧有意义,且要求对端也开启时间戳。较新内核默认值为 2(仅放行 loopback),全局开启需要显式置 1,是否真有必要应以端口耗尽的实测证据为准。

tcp_tw_recycle 则是反面教材:它按对端 IP 缓存时间戳并快速回收 TIME_WAIT,在 NAT 场景下会把同一出口 IP 后不同主机的合法报文当作过期报文丢弃,造成大面积连接异常,Linux 4.12 已将其移除。网上”两个参数一起开”的老配置不要照抄。另一个常见误区:tcp_fin_timeout 管的是孤儿连接 FIN_WAIT_2 的超时,改它缩不了 TIME_WAIT。

工程实践

先量化、再定位、最后才动参数:

# 各 TCP 状态数量统计
ss -ant | awk 'NR>1 {c[$1]++} END {for (s in c) print s, c[s]}'

# TIME_WAIT 主要集中在哪些远端
ss -ant state time-wait | awk 'NR>1 {print $5}' | sort | uniq -c | sort -nr | head

# 本地临时端口范围
cat /proc/sys/net/ipv4/ip_local_port_range

定位时先问”谁在主动关闭”——TIME_WAIT 一定落在主动关闭方。服务端上大量出现,要查是否服务端在主动断短连接;客户端或网关上大量出现,则重点看短连接风暴、keep-alive 没开、连接池被频繁重建。治理按优先级来:首选应用层减少短连接(HTTP keep-alive、连接池复用);确有端口压力时扩大 ip_local_port_range;最后再考虑 tcp_tw_reuse 这类内核参数。

顺带区分一对常被混淆的状态:TIME_WAIT 是主动方在等 2MSL,属于协议设计的正常等待;CLOSE_WAIT 是被动方已收到 FIN、回完 ACK,但本端应用迟迟不 close()——大量堆积基本是代码泄漏连接,排查方向完全不同。

面试视角

为什么主动关闭方要等 2MSL?

两个原因:一是最后一个 ACK 可能丢失,留在 TIME_WAIT 才能应答对端重传的 FIN,避免对方收到 RST 误判为异常关闭;二是等旧连接的滞留报文全部过期消散,防止同一四元组的新连接误收旧数据。2MSL 恰好覆盖”ACK 走一趟、重传 FIN 回一趟”的最坏来回。

TIME_WAIT 出现在哪一端?

主动关闭方。谁先发 FIN 谁进 TIME_WAIT,与客户端/服务端角色无关——服务端主动断连,TIME_WAIT 就堆在服务端。

大量 TIME_WAIT 怎么处理?

先确认是否真有害:危害主要是对同一目的地址高频建连导致临时端口耗尽。治理顺序是应用层连接复用优先,其次扩大端口范围,内核参数(如 tcp_tw_reuse)放最后,且要有实测依据。

tcp_tw_recycle 为什么被废弃?

它按对端 IP 缓存时间戳做快速回收。NAT 出口后多个内网主机共享一个公网 IP,各自时间戳不一致,后到主机的合法报文会被当成过期报文丢弃,导致连接大面积异常,因此 Linux 4.12 将其移除。

小结

  • TIME_WAIT 是主动关闭方发出最后 ACK 后的 2MSL 守候:补救丢失的 ACK、隔离旧连接报文,Linux 固定 60 秒。
  • 它本身是正常状态;真正的风险是短连接高频集中导致临时端口耗尽(约”可用端口数 ÷ 60 秒”的建连速率上限)以及 NAT 会话表占用。
  • 治理优先级:连接复用 > 扩大端口范围 > 谨慎使用 tcp_tw_reuse;tcp_tw_recycle 已被内核移除,tcp_fin_timeout 与 TIME_WAIT 无关。
  • TIME_WAIT 多先查谁在主动关闭,CLOSE_WAIT 多先查应用有没有释放连接。