【问题解析】Socket已经关闭了,但是端口还处于listening状态?

最近在上手一个QT项目的时候,在windows下用C++实现了一个服务器功能,开启50000-50008端口进行TCP监听,用来和另一个工具进行交互,服务器要求能动态创建和删除。但是在实际测试的时候发现我的服务器有的时候删不掉,即使socket已经关闭了,但是端口还是处于LISTENING的状态。

最开始的时候我的排查思路一直在线程资源没有回收掉上,下断点发现线程确实已经退出了,socket也关闭掉了,处于无效值状态了。也尝试在套接字的选项上进行了一些修改,结果都无济于事。我甚至一度开始怀疑windows下socket使用上是不是有什么隐藏的机制。

由于这个问题的出现很有规律,是客户端再向我的服务器的50001-50008端口发送消息之后,才会导致所有端口关不掉,我就将关闭服务器的代码不断地切换位置,想确认一下是到哪个位置才关闭不掉了。发现是客户端向我的服务器发送消息之后,我的服务器会开启tftpd32进程来准备传输数据,而我是通过子进程的方式创建的tftpd32进程,瞬间恍然大悟了。我将创建的方式修改为了单独进程的方式,果然可以顺利关闭掉端口了。

那整个问题就变成了 "为什么我开了服务器之后,创建一个子进程,这时候咋在主进程里面就关闭不掉服务器了?"

核心根因:Windows套接字内核引用共享机制

Windows 系统中,Socket 套接字本质是内核对象,并不是单纯的进程私有资源。 当在主进程创建 TCP 监听 Socket(50000~50008)后,通过子进程方式启动 tftpd32 时:

  1. Windows 默认继承父进程所有内核句柄(Socket、文件句柄、网络句柄等);

  2. TCP 监听 Socket 句柄,被 tftpd32 子进程隐性继承;

  3. 此时,同一个监听端口的 Socket 内核对象,同时存在两个进程引用:主进程 和 tftpd32子进程

为什么关 Socket、关线程都没用?

  1. 在主进程中:正常关闭 Socket 句柄、销毁服务对象、退出监听线程; 仅仅只关闭了「主进程」的 Socket 引用;

  2. 但 tftpd32 子进程依然持有该 Socket 内核句柄;

  3. Windows 端口释放机制:只有占用端口的所有进程,全部关闭 Socket 句柄,端口才会解除 LISTENING 状态; 只要还有任意一个进程持有该监听 Socket 句柄,内核就会保留端口监听,端口永久占用、无法释放。 这就是看到的现象: 主进程 socket 已关闭、线程已销毁、变量置空,但端口依然挂在 LISTENING,删不掉服务。

为什么「改为独立进程」就正常了?

我修改了进程启动方式,脱离子进程继承机制、以独立进程启动 tftpd32:

  • 关闭了句柄继承特性;
  • tftpd32 进程不会继承父进程的 TCP 监听 Socket 句柄;
  • 主进程关闭 Socket 时,该内核对象唯一引用被释放;
  • 内核正常回收端口, LISTENING 状态解除,服务可正常销毁。

补充:QT / Windows 解决方案

  1. 创建子进程时,禁用句柄继承 Windows 创建子进程时,设置句柄不可继承,从根源杜绝;

  2. 临时屏蔽网络句柄继承

  3. 独立进程启动第三方工具

总结

Windows 下 Socket 是内核句柄,子进程默认继承父进程所有网络句柄;tftpd32 子进程偷占了监听端口 Socket,主进程关句柄没用,端口被内核锁定,改为独立进程、关闭句柄继承即可彻底解决。

相关推荐
无限进步_1 小时前
【Linux】进程基础:task_struct、fork 与查看进程
linux·运维·服务器
咸鱼翻身小阿橙1 小时前
Qt Quick QML 登录界面代码学习报告
开发语言·qt·学习
小夏子_riotous1 小时前
Kubernetes学习路径——3. Kubernetes 1.25 高可用集群部署实战:从 Docker 到 Calico 全链路详解
linux·运维·学习·docker·容器·kubernetes·centos
bukeyiwanshui1 小时前
20260512 docker笔记
linux·运维·笔记·docker·容器
计算机安禾1 小时前
【c++面向对象编程】第9篇:友元(friend):破坏封装的“特权”——真的有害吗?
java·c++·log4j
黑贝是条狗1 小时前
Excel批量处理工具
linux·运维·excel
长沙红胖子Qt1 小时前
项目实战:Qt(cpu趋近于零消耗)获取windows的cpu使用率和内存占用率
qt·cpu使用率·内存使用率
buhuizhiyuci2 小时前
【QT-百日筑基篇】功法有些小成,开始进行打怪升级-QT的实践第一课,创建Hello World的几种方法
开发语言·qt
实心儿儿2 小时前
Linux —— 进程间通信 - 匿名管道
linux·运维·服务器