大量CLOSE_WAIT堆积说明应用层未正确关闭连接,即对端已发FIN并收到ACK后,本端未调用close()或shutdown(),常见于异常分支遗漏、RAII失效、recv返回0后未及时关闭、多线程fd误用及epoll未处理EPOLLRDHUP等场景。

为什么CLOSE_WAIT大量堆积说明应用层没正确关闭连接
CLOSE_WAIT 是 TCP 四次挥手的中间状态,表示对端已发 FIN、本端收到并确认(ACK),但本端还没调用 close() 或 shutdown(SHUT_WR)。服务器上大量 CLOSE_WAIT 不是内核问题,而是你的 C++ 代码里某处漏掉了 socket 关闭逻辑——常见于异常分支、资源管理疏忽或 RAII 失效。
检查 socket 关闭是否被异常跳过
最典型场景:读写过程中抛出异常、提前 return、goto 跳出作用域,导致 close(fd) 没被执行。尤其注意以下位置:
- 在
recv()返回 0(对端关闭)后,未立即close(),反而继续循环或等待 - 使用
std::unique_ptr管理 socket 但没提供自定义 deleter(默认只 delete 指针,不调用close) - 多线程中,一个线程 close 了 fd,另一个线程还在用该 fd 调用
send()或recv(),引发错误但未触发清理 - 使用
epoll_wait()后,对就绪事件处理时未区分EPOLLIN和EPOLLRDHUP,忽略对端 FIN 导致漏关
用 lsof 和 ss 快速定位残留 socket 归属
别只看 netstat——它可能缓存旧状态。优先用实时性更强的工具确认哪些 fd 还活着、属于哪个进程线程:
ss -tan state close-wait | head -20
输出中关注 Recv-Q 列:非 0 表示内核还有未读数据,应用层可能卡在 read 阻塞或忽略 EAGAIN;PID/Program 列可定位到具体进程。再结合:
立即学习“C++免费学习笔记(深入)”;
lsof -p <PID> | grep CLOSE_WAIT
确认这些 socket 是否对应你代码里已“丢失引用”的连接对象(比如 map 中 key 存在但 value 已析构,或 shared_ptr 引用计数为 0 但 fd 没 close)。
RAII 封装 socket 时必须显式 close,不能依赖析构
C++ 中不能假设析构函数一定会执行(如 longjmp、abort、信号中断、或对象位于未初始化内存)。即使用了 std::shared_ptr<int> 包 socket fd,也必须配自定义 deleter:
auto sock_deleter = [](int* fd) {
if (fd && *fd >= 0) {
::close(*fd);
*fd = -1;
}
};
std::shared_ptr<int> sock_ptr(new int(client_fd), sock_deleter);更推荐封装成类,把 close() 放进析构,并在 move 构造/赋值中置空原 fd:
- 禁止裸 int fd 在多个作用域间传递
- 所有 socket 创建后立刻绑定到 RAII 对象,避免“先创建后包装”间隙
- 在
handle_read()中检测recv()返回 0 后,立即调用对象的shutdown_and_close(),而非仅标记“待关闭”
真正难排查的,往往是那个在 signal handler 里被跳过的 close,或者日志里根本没打印的析构调用——得靠 strace -e trace=close,shutdown -p <PID> 实锤是否真调用了系统调用。


















