socket链接的关闭close和shutdown的区别_TIME_WAIT和CLOSE_WAIT什么时刻出现_如何处理

TCP主动关闭连接 
appl: close(),  --> FIN     FIN_WAIT_1 //主动关闭socket方,调用close关闭socket,发FIN                         
                <-- ACK     FIN_WAIT_2 //对方操作系统的TCP层,给ACK响应。然后给FIN                  
                <-- FIN                  
                --> ACK     "TIME_WAIT"   -- 2MSL timeout -->CLOSED
                                        //TIME_WAIT,防止ACK没有给到对方
注意:close时,如果TCP发送队列中还有数据,那么将会发送RST包而不是FIN包。
参看:http://rdc.taobao.com/blog/cs/?p=1055
另外:对于Linux下来说,无论是FIN还是RST,应用层read将会返回0,可以认为对方请求关闭链接,调用close关闭fd即可。
TCP被动关闭连接 
            <-- FIN     "CLOSE_WAIT"   //被动方,收到对方的FIN,处于CLOSE_WAIT状态                  
                --> ACK                    //被动方的TCP层,给ACK响应  
                appl: close(),  --> FIN     LAST_ACK      
                                         //被动方调用close,从CLOSE_WAIT转到LAST_ACK
                                         不调close,将一直在CLOSE_WAIT状态。                 
                 <-- ACK         --> CLOSED  
tcp是全双工::
   close()会关闭读写
   shutdown()可以选择性的关闭读、写或读写
 
  主动关系SOCKET链接的一方,会进入TIME_WAIT(作用是防止最后一个ACK包丢失)
   TIME_WAIT的时间会非常长,因此server尽量减少主动关闭连接。
 
close和shutdown的区别:
   int close(int sockfd);   

   close(fd)调用会将描述字的引用计数减1,只有当socket描述符的引用计数为0时,才关闭socket,即发送FIN包,因此,在fork()模式中,父进程在accept()返回后,fork()子进程,由子进程处理connfd,而父进程将close(connfd);由于connfd这个socket描述符的引用计数不为0,因此并不引发FIN,所以就没有关闭和客户端的连接。  

  int shutdown(int sockfd, int howto);    
  // howto: SHUT_RD, SHUT_WR, SHUT_RDWR  
  shutdown()则不管socket描述符的引用计数,而直接发生FIN,因此会直接关闭链接。 
  shutdown()可控制read/write两个方向的管道。 
   SHUT_RD     shutdown(sockfd, SHUT_RD);后,来自对端的数据都被确认,然后悄然丢弃。       
   SHUT_WR     half close状态。  
close()引发的4次交互:(这里的close是client发起的) 
            client                             server  
               FIN_WAIT_1   ---- FIN M ------>       
                                       //(Server端操作系统的TCP层(网络协议栈)响应ACK包)           
               FIN_WAIT_2   <---- ACK M+1----   CLOSE_WAIT
                                       //(这里必须调用close,才能从CLOSE_WAIT到LAST_ACK)                  
               TIME_WAIT    <------ FIN N -----  LAST_ACK 
                                        //(TIME_WAIT有一个重要的作用就是防止最后一个ACK丢失)           
                            ------- ACK N+1 ---->  CLOSE  
 

        

 TIME_WAIT 是主动关闭链接时形成的等待2MSL时间,约4分钟。

         主要是防止最后一个ACK丢失。  由于time_wait的时间会非常长,因此server端应尽量减少主动关闭连接

 

CLOSE_WAIT是被动关闭链接是形成的 ,

        按状态机,我方收到FIN,则由TCP实现发送ACK,因此进入CLOSE_WAIT状态。

        但如果我方不执行close(),就不能由CLOSE_WAIT迁移到LAST_ACK,则系统中会存在很多CLOSE_WAIT状态的连接。

此时,可能是系统忙于处理读、写操作,而未将已收到FIN的连接,进行close。此时,recv/read已收到FIN的连接socket,会返回0。

 

大量TIME_WAIT和CLOSE_WAIT的存在,会产生怎样的影响?

        内核维护更多的状态。收到ip包,做hash运算,hlist冲突的概率更大。 

`CLOSE_WAIT` 状态过多是一个非常典型的 **网络资源泄漏问题**,它表明你的应用程序“收到了对方的关闭请求(FIN)”,但自己 **迟迟没有调用 `close()` 来关闭本地 socket**。 --- ## 🔍 什么是 `CLOSE_WAIT`? 我们先从 TCP 四次挥手说起: ``` A (你的程序) B (对端,如服务器) ---- FIN ---> // B 主动关闭连接 <--- ACK ---- (A 进入 CLOSE_WAIT) ---- ACK ---> // A 回应 ACK,准备关闭 <--- FIN ---- ---- ACK ---> (A 进入 LAST_ACK) ``` 👉 当你收到对端发来的 `FIN` 包后,TCP 协议栈会将该 socket 置为 `CLOSE_WAIT` 状态。 ### ✅ 正常行为: - 你的程序应该尽快调用 `close(sockfd)` 或 `shutdown()` 完成本地关闭 - 这样才会发出自己的 `FIN` 包,进入 `LAST_ACK`,最终释放连接 ### ❌ 异常行为(就是你现在的问题): > 如果你不调用 `close()`,这个 socket 就会 **永远停留在 `CLOSE_WAIT` 状态** 导致: - 文件描述符(fd)不释放 → 最终触发 `Too many open files` - 内存占用增长 - 新连接无法建立 --- ## 📊 `CLOSE_WAIT` 过多说明什么问题? | 含义 | 解释 | |------|------| | ✅ 对端已正常关闭连接 | 表示通信是正常的,对方主动断开了 | | ❌ 本端未调用 `close()` | 你的代码中漏掉了 `close(socket_fd)` | | ⚠️ 存在资源泄漏 | 每个 `CLOSE_WAIT` 都占用一个 fd 内核结构体 | | 🔁 可能是循环创建连接未关闭 | 如定时任务、心跳检测、HTTP 客户端等场景 | --- ## ✅ 如何查看 `CLOSE_WAIT` 状态的连接? ```bash # 查看指定进程的所有网络连接状态 sudo lsof -p 51179 | grep CLOSE_WAIT ``` 输出示例: ```text app 51179 user 3u IPv4 123456 0t0 TCP 192.168.1.10:5678->192.168.1.1:80 (CLOSE_WAIT) app 51179 user 4u IPv4 123457 0t0 TCP 192.168.1.10:5679->192.168.1.1:80 (CLOSE_WAIT) ... ``` 统计数量: ```bash sudo lsof -p 51179 | grep CLOSE_WAIT | wc -l ``` 如果这个数字持续增长 → 肯定存在 **socket 泄漏** --- ## 💡 常见原因与解决方案 ### ❌ 原因 1:`socket()` 后忘记 `close()` ```c int sockfd = socket(AF_INET, SOCK_STREAM, 0); connect(sockfd, ...); write(sockfd, "...", ...); // 忘记 close(sockfd); ← 泄漏! ``` ✅ **修复方法:确保所有路径都关闭** ```c int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) return -1; if (connect(...) < 0) { close(sockfd); // 错误时也要关 return -1; } write(...); close(sockfd); // 使用完立即关闭 return 0; ``` --- ### ❌ 原因 2:异常分支或 `return` 前未关闭 ```c int send_request() { int sockfd = socket(...); if (some_error()) { return -1; // ❌ 忘记 close(sockfd) } if (another_error()) { return -1; // ❌ 同样没关 } close(sockfd); return 0; } ``` ✅ **修复方法:使用 `goto cleanup` 统一释放** ```c int send_request() { int sockfd = -1; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) goto cleanup; if (some_error()) { goto cleanup; } if (another_error()) { goto cleanup; } // 正常流程 close(sockfd); return 0; cleanup: if (sockfd >= 0) close(sockfd); return -1; } ``` --- ### ❌ 原因 3:短连接频繁发起,但未及时关闭 比如你在一个定时器里每秒访问一次 API: ```c while (1) { http_get("http://api.example.com/data"); sleep(1); } ``` 而 `http_get()` 内部每次创建新 socket 但未关闭。 ✅ **解决方法:** - 确保 `http_get` 中 `close()` 被调用 - 或改用 **长连接 / 连接复用**(Keep-Alive) --- ### ❌ 原因 4:多线程中某个线程持有 socket 但未关闭 例如: - 主线程创建 socket 并传给子线程处理 - 子线程处理完忘了 `close()` - 或线程提前退出未清理资源 ✅ **解决方法:** - 明确所有权:谁创建谁负责关闭 - 使用 `pthread_cleanup_push/pop` 注册清理函数 - 或使用智能指针思想(C++)或 RAII 模式 --- ## ✅ 如何预防 `CLOSE_WAIT` 泄漏? | 方法 | 说明 | |------|------| | ✅ 所有 `socket()` 配对 `close()` | 最基本原则 | | ✅ 使用 `strace` 跟踪系统调用 | 看是否有 `socket()` 无 `close()` | | ✅ 使用 `lsof` 监控 fd 数量变化 | 判断是否泄漏 | | ✅ 设置超时机制 | `setsockopt(... SO_RCVTIMEO ...)` 防止阻塞导致无法执行 `close()` | | ✅ 使用 `valgrind --track-fds=yes` | 检测 fd 泄漏(开发阶段) | --- ## 🧪 示例:用 `valgrind` 检测 fd 泄漏 ```bash valgrind --tool=memcheck \ --track-fds=yes \ ./app ``` 输出示例: ``` ==51179== FILE DESCRIPTOR 10: leaked from file.c:45 ==51179== at socket (syscall-template.S:84) ==51179== by create_connection (file.c:45) ==51179== by main (main.c:100) ``` 直接定位到哪一行打开了未关闭的 fd! --- ## ✅ 总结 | 项目 | 内容 | |------|------| | `CLOSE_WAIT` 含义 | 对端已关闭,我方尚未调用 `close()` | | 是否危险 | 是!会导致 fd 泄漏、`Too many open files` | | 根本原因 | 代码中缺少 `close(socket_fd)` | | 排查工具 | `lsof`, `netstat`, `strace`, `valgrind` | | 修复策略 | 所有路径确保 `close()`,使用 `goto cleanup` 统一释放 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值