bigywater.tech
网络协议 · 41

TIME_WAIT:为什么要等 2MSL,大量堆积怎么办

TIME_WAIT 为什么要等 2MSL?谁会留下它?大量堆积会怎样?这篇量出本机 TIME_WAIT 的真实时长,让同一个源端口马上重连被拒绝,把临时端口真的用光,最后看 SO_LINGER 0 能不能绕开它。

31.3 秒
TIME_WAIT 实测持续时间(MSL 15 秒,2MSL = 30 秒)
15,696
连续建立再关闭的短连接数,之后临时端口耗尽,新连接报 Can't assign requested address
500 → 0
同样 500 个连接,正常 close() 留下 500 个 TIME_WAIT,SO_LINGER 0 留下 0 个

时长

实测 · 本机执行输出
  net.inet.tcp.msl: 15000
  net.inet.tcp.fin_timeout: 60000
  客户端(端口 51963)close 之后 0.0 秒第一次看到 TIME_WAIT,31.4 秒时这一行消失——TIME_WAIT 持续约 31.3 秒 = 2 × MSL(2 × 15 s = 30 s)

本机 MSL 15 秒,TIME_WAIT 持续 2MSL,实测约 31 秒。

作用

实测 · 本机执行输出
  同一个源端口 51999、同一个服务器,不设 SO_REUSEADDR:绑定失败:Address already in use
  同一个源端口 51999、同一个服务器,设 SO_REUSEADDR:绑定成功,connect 失败:Address already in use

占住四元组,防止旧连接的迟到包混进新连接:TIME_WAIT 期间同一个源端口不能连同一个服务器(即使设了 SO_REUSEADDR 也不行)。

谁留下

实测 · 本机执行输出
  客户端先 close:TIME_WAIT 里,服务端一侧(本地端口 = 服务端口)0 个,客户端一侧(本地是临时端口)500 个
  服务端先 close:TIME_WAIT 里,服务端一侧(本地端口 = 服务端口)500 个,客户端一侧(本地是临时端口)0 个
  所以“服务器上有大量 TIME_WAIT”往往是因为服务器主动关闭了连接(如 HTTP 短连接 Connection: close、超时踢人);客户端的 TIME_WAIT 则会占用客户端的临时端口

谁先 close 谁留下:服务器主动关闭,TIME_WAIT 在服务器;客户端主动关闭,TIME_WAIT 在客户端并占用临时端口。

堆积的后果

实测 · 本机执行输出
  连续建立 / 关闭短连接:成功 15696 次后第 15697 次失败:[Errno 49] Can't assign requested address(用时 5.8 秒)
  此刻 netstat 里和这个服务端口有关的 TIME_WAIT 有 15696 个(临时端口范围一共 16384 个)
  失败之后立刻再试 200 次:成功 0 次、失败 200 次——端口快用完时,随机挑到已被占用的端口就失败,所以不是“一个都没剩”才失败,而是越到后面失败越频繁
  不再建立新连接,等 TIME_WAIT 过期:31 秒后只剩不到 50 个(最早的一批是 5.8 秒前建立的;每个 TIME_WAIT 持续约 30 秒)

客户端短连接太多时,临时端口会被 TIME_WAIT 占满,新连接报 Can't assign requested address,要等 TIME_WAIT 过期才恢复。

绕过

实测 · 本机执行输出
  客户端 正常 close():建立并关闭 500 个连接后,和这个服务端口有关的 TIME_WAIT 有 500 个
  客户端 SO_LINGER(开,0 秒)后 close():建立并关闭 500 个连接后,和这个服务端口有关的 TIME_WAIT 有 0 个
  linger 0 把 TIME_WAIT 省掉了,但代价是:对方没读完的数据会丢,迟到的旧包也可能被新连接收到;更常见的正确做法是让客户端主动关闭 / 用连接池 / 调大临时端口范围

SO_LINGER 0 用 RST 代替 FIN,不留 TIME_WAIT,但会丢未读数据。更好的做法是复用连接。

分步演示

TIME_WAIT
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。全部在 lo0 上做;临时端口耗尽实验用掉了大约 16000 个端口,实验结束后等待 TIME_WAIT 过期;TIME_WAIT 的数量按 netstat 里与服务端口相关的行统计;耗尽时失败的次数与具体个数每次略有不同
    • 没有验证:真实网络上 TIME_WAIT 对 NAT 设备或负载均衡的影响;net.inet.tcp.msl 能否调小(需要 root);TIME_WAIT 的第二个作用(确保最后一个 ACK 丢失时对方重传的 FIN 能得到再次确认)没有单独演示;Linux 的 tcp_tw_reuse / 时间戳复用在 macOS 上没有对应项;失败时随机选端口的细节
    京ICP备2026067502号-1京公网安备11010502063487号