Winsock2与system启动命令的异常问题排查
线程执行顺序与句柄继承导致的阻塞问题
#include <Winsock2.h> #include <stdint.h> #include <thread> const uint16_t ANY_VALID_PORT = 12345; SOCKET sock = INVALID_SOCKET; void listenPort() { sockaddr_in addr {}; int addrSize = sizeof(addr); accept(sock, (SOCKADDR*)&addr, &addrSize); } // #define CORRECT_SEQUENCE int main() { WSADATA wsaData = { 0 }; WSAStartup(MAKEWORD(2, 2), &wsaData); sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in clientService {}; clientService.sin_family = AF_INET; clientService.sin_port = htons(ANY_VALID_PORT); clientService.sin_addr.s_addr = INADDR_ANY; bind(sock, (SOCKADDR*)&clientService, sizeof(clientService)); listen(sock, 2); #ifdef CORRECT_SEQUENCE system("start cmd"); std::thread listening { listenPort }; #else std::thread listening { listenPort }; system("start cmd"); //could be virtually any program #endif closesocket(sock); listening.join(); WSACleanup(); }
问题现象
- 预期行为:主程序正常结束,cmd在独立窗口持续运行。
- 实际行为:未定义
CORRECT_SEQUENCE宏时,主程序必须等待新启动的cmd关闭后才会结束;定义该宏时,程序运行符合预期。 - 细节:未定义宏时,即使主进程调用了
closesocket,线程中的accept也不会返回,程序阻塞在listening.join()处。
原因分析
核心是Windows句柄继承机制和线程执行时机的共同作用:
句柄继承特性:Windows下,
system("start cmd")启动的新进程会默认继承父进程的所有可继承句柄,包括主进程创建的监听socket句柄。socket内核对象的生命周期由所有关联句柄的引用计数决定,只有当所有句柄都被关闭时,socket才会被销毁。未定义宏时的执行流程:
- 线程先启动,
accept立即进入阻塞状态,此时socket处于正常监听状态。 - 随后调用
system("start cmd"),cmd进程继承了该socket的句柄。 - 主进程调用
closesocket仅关闭自己持有的句柄,但cmd仍持有socket句柄,socket内核对象不会被销毁,accept会一直阻塞等待连接。 - 主进程的
join()必须等待线程结束,而线程要等accept返回,只有当cmd关闭释放socket句柄后,socket才会销毁,accept才会返回错误,线程结束,主进程才能退出。
- 线程先启动,
定义宏时的执行流程:
- 先调用
system("start cmd"),cmd继承socket句柄,但此时线程尚未启动。 - 线程启动后,主进程紧接着调用
closesocket,此时主进程的socket句柄被关闭。 - 线程中的
accept尝试使用已关闭的socket句柄,会立即返回错误(INVALID_SOCKET),线程正常结束。 - 主进程的
join()无需等待,直接完成退出,而cmd进程持有的socket句柄会在cmd运行期间一直存在,但不影响主进程的退出。
- 先调用
补充说明
即使启动的不是cmd而是其他程序(比如仅含while(1)的空程序),只要它继承了socket句柄,就会出现同样的问题——因为socket内核对象的生命周期被继承的句柄延长了,导致accept无法因socket销毁而返回。
内容的提问来源于stack exchange,提问作者Dmitry
相关产品推荐
相关产品推荐

