TIME_WAIT 和 CLOSE_WAIT 有什么区别?大量出现怎么办?
简化版
两个都是四次挥手中的状态,但角色相反:TIME_WAIT 出现在主动关闭方,是发完最后 ACK 后等 2MSL 的正常状态;CLOSE_WAIT 出现在被动关闭方,是收到对方 FIN、回了 ACK 后、等自己 close() 的状态。大量 TIME_WAIT 通常是短连接太多(正常,可调优);大量 CLOSE_WAIT 几乎一定是代码 bug——收到 FIN 却没调用 close()。
详细版
TIME_WAIT(主动关闭方):
- 发出最后一个 ACK 后进入,等待 2MSL 才
CLOSED; - 是正常且必要的状态,作用是「保证末尾 ACK 送达」+「让旧连接报文消散」(详见「四次挥手」那道题);
- 大量出现常见于高并发短连接的客户端/主动关闭方(如爬虫、频繁调用下游的服务)。
CLOSE_WAIT(被动关闭方):
- 收到对方 FIN、自动回了 ACK 后进入,等待自己的应用调用
close()发出 FIN; - 会一直停在 CLOSE_WAIT,直到应用
close(); - 大量堆积 = 应用收到了 FIN 却没有 close 连接,是典型的连接泄漏 bug。
完整版教学
一、用角色记住谁在哪一侧
死记状态名容易混,抓住「谁主动关」就清楚了:
- 主动关闭方(先发 FIN 的一方):经历
FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED。TIME_WAIT 在主动方。 - 被动关闭方(后关的一方):经历
CLOSE_WAIT → LAST_ACK → CLOSED。CLOSE_WAIT 在被动方。
这样对照记:主动关的等 2MSL(TIME_WAIT),被动关的等自己 close(CLOSE_WAIT)。
二、TIME_WAIT 为什么必要,又为什么会成问题
必要性(2MSL 的两个作用):
- 保证最后的 ACK 能到达对方——万一丢了,对方会重发 FIN,此时还在 TIME_WAIT 就能重新回 ACK;
- 让本次连接的旧报文在网络中彻底过期,避免串扰到复用同一四元组的新连接。
为什么会大量出现:TIME_WAIT 要保持 2MSL(通常 1~4 分钟)。如果一个服务频繁地主动关闭大量短连接(比如每次请求都新建 + 主动关一个到下游的连接),这些连接会堆积成成千上万个 TIME_WAIT,占用端口和内存,甚至导致端口耗尽(本地端口不够用,新连接建不了)。
三、大量 TIME_WAIT 怎么办
它是正常状态,思路是「减少 + 复用」而非「消灭」:
- 根治:改用长连接(连接池复用),减少频繁的建连/关连——这是最有效的办法;
net.ipv4.tcp_tw_reuse = 1:允许复用 TIME_WAIT 状态的连接给新的出站连接(需配合时间戳),安全有效;- 适当扩大本地端口范围(
ip_local_port_range); - 让服务端由客户端主动关闭:把 TIME_WAIT 的负担转移到客户端(谁主动关,TIME_WAIT 就在谁那边)。
⚠️ 老办法
tcp_tw_recycle在 NAT 环境下会导致丢包,已在新内核移除,不要用。
四、大量 CLOSE_WAIT:几乎一定是 bug
CLOSE_WAIT 和 TIME_WAIT 的排查方向完全相反——大量 CLOSE_WAIT 基本就是代码问题。
它意味着:对方已经关了连接(发来 FIN,你的内核自动回了 ACK),但你的应用程序迟迟没有调用 close() 去发出自己的 FIN。连接就卡在 CLOSE_WAIT,占着文件描述符不放,越积越多,最终 fd 耗尽、无法接受新连接。
常见原因:
- 异常路径没有
close()(没放进finally/ try-with-resources); - 连接池没有正确回收、归还连接;
- 阻塞在某处,代码根本走不到 close。
排查:用 netstat/ss 统计 CLOSE_WAIT 数量和对应进程,然后去代码里找「哪条路径漏了 close」。
五、常见误区
- ❌ 把 TIME_WAIT 和 CLOSE_WAIT 记反——TIME_WAIT 在主动关闭方,CLOSE_WAIT 在被动关闭方。
- ❌ 想「消灭」TIME_WAIT——它是可靠关闭的必要机制,应减少短连接、开 tcp_tw_reuse,而非粗暴关闭。
- ❌ 用
tcp_tw_recycle优化——NAT 下会丢包,已废弃。 - ❌ 大量 CLOSE_WAIT 去查对端——它在自己这侧,是自己没 close,查自己的代码。
六、常见误区与追问
| 考点 | 正确口径 |
|---|---|
| TIME_WAIT | 主动关闭方等待 2MSL |
| CLOSE_WAIT | 被动关闭方已收到 FIN,等待应用 close |
| 大量问题 | TIME_WAIT 多看连接模式,CLOSE_WAIT 多看应用泄漏 |
active close -> FIN_WAIT -> TIME_WAIT
passive close -> CLOSE_WAIT until app close()
TIME_WAIT 是协议设计的一部分,CLOSE_WAIT 长期堆积更像应用没有把连接关掉。
- 误区:TIME_WAIT 一定是服务器问题。 谁主动关闭谁进入 TIME_WAIT,客户端和服务端都可能出现。
- 误区:CLOSE_WAIT 可以靠调内核参数解决。 CLOSE_WAIT 主要是应用未及时 close,根因通常在代码或线程阻塞。
- 误区:TIME_WAIT 没有任何价值。 它保证最后 ACK 可重传,并避免旧连接报文污染新连接。
- 追问:大量 TIME_WAIT 怎么处理? 优先使用连接复用、减少短连接,必要时再谨慎调整端口和内核参数。
- 追问:大量 CLOSE_WAIT 如何定位? 查看进程连接、线程栈、连接池释放逻辑和应用是否处理 EOF/异常。
- 追问:2MSL 是什么? MSL 是报文最大生存时间,2MSL 覆盖报文和其响应在网络中的最长往返残留。
七、加强记忆
TIME_WAIT 在主动关闭方(发完末 ACK 等 2MSL,正常且必要),CLOSE_WAIT 在被动关闭方(收到 FIN 等自己 close)。大量 TIME_WAIT 多因短连接过多,用长连接/连接池、tcp_tw_reuse 缓解(别用 tcp_tw_recycle);大量 CLOSE_WAIT 几乎必是代码没 close() 的连接泄漏 bug,去查自己的代码。