
博客目录
简单来说,CLOSE_WAIT 状态表示你的应用程序(本地端点)已经收到了对端发来的连接终止请求(FIN 包),但你的应用程序自己没有正确地调用 close()
方法来发送最终的 ACK 并完全关闭连接。

1. TCP 连接终止的"四次挥手"回顾
要理解 CLOSE_WAIT,必须先知道 TCP 是如何正常关闭连接的。这个过程被称为"四次挥手":
- FIN_WAIT_1 : 主动关闭方(比如客户端)调用
close()
,发送一个FIN
包给被动关闭方(比如服务器),然后进入此状态。 - CLOSE_WAIT : 被动关闭方(服务器)收到
FIN
包后,立即回复一个ACK
,然后进入CLOSE_WAIT
状态。- 关键就在这里 :此时,TCP 告诉你的应用程序:"对方已经不会再发送数据了,但你还可以继续发送未发送完的数据"。
CLOSE_WAIT
状态本质上是一个等待应用程序自己来处理关闭的状态。
- 关键就在这里 :此时,TCP 告诉你的应用程序:"对方已经不会再发送数据了,但你还可以继续发送未发送完的数据"。
- LAST_ACK : 被动关闭方(服务器)的应用程序也调用
close()
后,它会发送一个FIN
包给主动关闭方,然后进入LAST_ACK
状态,等待最后一个ACK
。 - TIME_WAIT : 主动关闭方(客户端)收到
FIN
包后,发送最终的ACK
,然后进入TIME_WAIT
状态(等待 2MSL 时间以确保对方收到 ACK)。之后,双方连接完全关闭。
2. CLOSE_WAIT 状态的含义和问题
CLOSE_WAIT 状态是"四次挥手"中的一个正常中间状态,但它应该是一个短暂的状态。
问题在于:如果系统中存在大量持续不退的 CLOSE_WAIT 连接,这绝对是一个 bug 的信号。
它意味着:
你的应用程序(处于被动关闭的一方)在收到对方的 FIN 包并回复 ACK 后,没有执行下一步的关闭操作(即没有调用 close()
来发送自己的 FIN 包)。
这会导致:
- 连接资源泄漏:每个 TCP 连接都会占用一个文件描述符(File Descriptor)和一定的内存资源。这些未被关闭的连接会持续占用这些资源。
- 文件描述符耗尽:当泄漏的连接数量达到进程或系统规定的文件描述符上限时,应用程序将无法建立新的网络连接、无法打开文件,导致服务完全不可用。
- "僵尸"连接:对端已经认为连接关闭了,但你这一端还维持着一个半开半闭的连接。
3. 为什么会出现 CLOSE_WAIT?(根本原因)
根本原因总是出现在应用程序代码层面,而不是网络或对端问题。常见原因包括:
-
代码 Bug(最常见):
- 未正确释放连接资源 :你的代码在使用完 Socket(或网络连接)后,没有在
finally
块或合适的生命周期回调函数中调用close()
方法。例如,发生了异常,导致跳过关闭语句。 - 逻辑错误:在某些分支或错误处理路径上,忘记了关闭连接。
- 未正确释放连接资源 :你的代码在使用完 Socket(或网络连接)后,没有在
-
应用程序设计问题:
- 连接池配置不当:如果使用了数据库或 HTTP 连接池,连接池可能没有正确配置"空闲连接检查"或"泄漏检测"机制,导致池中的连接没有被及时回收和关闭。
- 长周期操作阻塞:应用程序在收到 FIN 后,可能还在执行一个非常耗时的操作,迟迟没有走到关闭那一步代码。
-
资源竞争或死锁:
- 极少数情况下,应用程序可能因为死锁而无法执行到关闭连接的代码。
4. 如何诊断和解决?
诊断步骤:
-
确认问题:
- 在 Linux 上使用命令
netstat -anop | grep CLOSE_WAIT
或ss -o state close-wait
来查看所有处于 CLOSE_WAIT 状态的连接及其对应的进程 PID。 - 观察这些连接的数量是否随时间持续增长。
- 在 Linux 上使用命令
-
定位代码:
- 根据
netstat
或ss
命令找到的 PID,确定是哪个应用程序进程的问题。 - 审查该应用程序的代码,重点关注网络 I/O 操作的部分 ,查找所有打开
Socket
、Connection
的地方,确保每一个都有对应的close()
/disconnect()
/release()
操作,并且这些操作被放在finally
块或类似的确保执行的上下文中。
- 根据
解决方案:
-
修复代码:
-
确保资源被释放 :这是最根本的解决办法。使用
try-with-resources
(Java)、using
(C#)、defer
(Golang)或try...finally
等语言特性,保证无论是否发生异常,连接都能被关闭。 -
示例 (Java):
java// 错误示范:如果发生异常,socket可能不会被关闭 Socket socket = new Socket(host, port); // ... 使用 socket socket.close(); // 正确示范:使用 try-with-resources,确保自动关闭 try (Socket socket = new Socket(host, port)) { // ... 使用 socket } catch (IOException e) { // ... 异常处理 } // socket 会自动在此处被关闭,即使发生异常
-
-
检查第三方库/连接池:
- 如果你使用了连接池(如 HikariCP, Apache DBCP, HTTP 客户端池等),检查其配置。确保设置了:
maxLifetime
:连接的最大存活时间。idleTimeout
:空闲连接超时时间。leakDetectionThreshold
:泄漏检测阈值(如果支持)。
- 如果你使用了连接池(如 HikariCP, Apache DBCP, HTTP 客户端池等),检查其配置。确保设置了:
-
重启服务(临时措施):
- 在紧急情况下,重启出现问题的应用程序可以立即释放所有被占用的连接和文件描述符,恢复服务。但这只是临时绕过问题,根本的代码 bug 仍然存在,之后还会复发。
总结
状态 | 含义 | 问题 | 解决方向 |
---|---|---|---|
CLOSE_WAIT | 被动关闭方已收到 FIN,但应用未调用close() |
应用程序资源泄漏(文件描述符耗尽) | 检查应用程序代码,确保正确关闭所有网络连接 |
TIME_WAIT | 主动关闭方已发出最后 ACK,等待网络包消散 | 占用端口资源,但这是 TCP 协议的正常行为 | 通常无需解决,或调整系统参数优化 |
记住一句口诀:CLOSE_WAIT 是你自己的问题,TIME_WAIT 是别人的问题。 遇到 CLOSE_WAIT 堆积,不要怀疑对端或网络,立刻去检查你的代码。
觉得有用的话点个赞
👍🏻
呗。❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄
💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍
🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙