accept 返回的 socket fd 必须交由独立线程处理,否则主线程阻塞会导致新连接被丢弃;因默认阻塞模式下 recv 会卡住线程,单线程无法并发服务多客户端,listen 队列溢出引发 connect 超时或 connection refused。

accept 返回的 socket fd 必须立刻交给独立线程处理,否则阻塞主线程导致新连接被丢弃。
为什么不能在主线程里直接 recv 多个客户端?
因为默认 socket 是阻塞模式:recv 会卡住当前线程,直到数据到达或连接关闭。如果主线程一边 accept 一边 recv,它只能服务一个客户端,其余连接请求会在 listen 队列里堆积,超限时被内核丢弃(表现为客户端 connect 超时或 Connection refused)。
-
listen的backlog参数不是无限队列,Windows 默认通常为 5,Linux 一般 capped at 128 - 即使提高
backlog,不及时accept也会触发 SYN queue 溢出,客户端收不到 SYN+ACK - 单线程轮询
recv所有已连接 socket 效率极低,且无法应对长连接空闲场景
std::thread 启动处理函数时必须传入有效 socket 句柄和地址信息
常见错误是在线程函数里访问已被主线程释放的栈变量,比如把 sockaddr_in 地址结构体按值传入但没深拷贝,或传递局部 int clientSock 后主线程立即 closesocket —— 导致子线程调用 recv 时返回 WSAENOTSOCK 或静默失败。
- 正确做法:用
std::make_shared包裹SOCKET和sockaddr_in,确保生命周期跨线程安全 - 不要在线程函数开头就
closesocket,应等recv返回 0(对端关闭)或负值(错误)后再清理 - Windows 下
SOCKET是无符号整数类型,但它是内核句柄,不是普通 int,避免赋值给int后做算术运算
多线程共享资源时,send 不是线程安全的
多个线程并发调用同一个 socket 的 send,可能造成数据交错(如 A 线程发 "hello"、B 线程发 "world",接收端收到 "hewolrllod")。这不是 TCP 协议问题,而是 Winsock API 本身不保证同一 socket 上多线程 send 的原子性。
- 解决方案一:每个客户端连接只由一个线程负责读写(推荐),即“1 连接 ↔ 1 线程”模型
- 解决方案二:用
std::mutex保护该 socket 的send调用,但注意锁粒度——别把整个业务逻辑包进锁里,否则吞吐下降明显 - 绝对不要用全局锁保护所有 socket 的
send,这等于退化成单线程
Windows 下必须显式调用 WSAStartup,且不能在子线程里重复初始化
WSAStartup 是进程级初始化,只需在主线程调用一次;但很多初学者误以为每个线程都要初始化,结果第二次调用返回 WSASYSNOTREADY 或静默失败,后续 socket 操作全不可用。
- 检查返回值:
WSAStartup成功返回 0,失败返回非零错误码,必须判断 - 对应地,程序退出前调用一次
WSACleanup,不要在线程退出时调用 - 若使用 C++11
std::thread,确保主线程完成WSAStartup后再创建任何网络线程
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











