如何使用C++实现高性能的TCP网络通信?(Socket编程)

4次阅读

select并发下性能差因每次调用需全量拷贝 fd 集合并线性遍历,o(n) 复杂度致千连接即延迟上升;应改用 epoll_wait,o(1) 返回就绪 fd,且无 1024 限制。

如何使用C++实现高性能的TCP网络通信?(Socket编程)

为什么 select 在高并发下会拖慢 TCP 服务?

因为 select 每次调用都要把整个 fd 集合从用户态拷贝到内核态,且内核要线性遍历所有 fd 判断就绪状态——连接数一过千,延迟就明显上升。

实操建议:

立即学习C++免费学习笔记(深入)”;

  • linux 上优先用 epoll_wait,它只返回就绪的 fd,不遍历全集,时间复杂度从 O(n) 降到 O(1) 平均情况
  • 避免在循环里反复调用 FD_SET 构造 fd_set;epollepoll_ctl 增删一次就够了
  • select 最大支持 fd 数通常为 1024(受 FD_SETSIZE 限制),而 epoll 只受限于系统内存和 rlimit

如何避免 recv 阻塞或读不全导致粘包?

TCP 是字节流协议,recv 不保证一次返回一个完整业务包;直接按固定长度读,遇到网络抖动或小包合并就会出错。

实操建议:

立即学习C++免费学习笔记(深入)”;

  • 不要依赖单次 recv 读完全部数据;用循环 + 缓冲区拼接,直到收到预期长度或遇到分隔符
  • 应用层加协议头,比如前 4 字节存 payload 长度(用 ntohl/htonl 处理字节序),再按需读取
  • 设置 socket 为非阻塞模式(fcntl(fd, F_SETFL, O_NONBLOCK)),配合 epoll 使用,避免单连接卡住整个事件循环

send 返回值小于请求长度时,为什么不能直接丢弃剩余数据?

这是 TCP 发送缓冲区满的正常信号,不是错误。Linux 内核只把能塞进缓冲区的数据拷走,返回实际字节数;剩余部分必须由你手动缓存、重试,否则消息就丢了。

实操建议:

立即学习C++免费学习笔记(深入)”;

  • 检查 send 返回值:等于 0 表示对端关闭;小于请求长度说明要重发;-1 且 errno == EAGAINEWOULDBLOCK 表示缓冲区满,需等待可写事件
  • 为每个连接维护发送队列(如 std::deque<:vector>></:vector>),在 epoll 收到 EPOLLOUT 后继续发送
  • 避免在单次 send 前把大 buffer 全部 memcpy 到临时空间——高频小包场景下,内存拷贝开销会吃掉性能优势

为什么线程直接共享 socket fd 容易出问题?

socket fd 本身是进程级资源,多个线程调用 send/recv 不会崩溃,但竞争条件会让逻辑混乱:比如两个线程同时 recv,谁读到哪段数据完全不可控。

实操建议:

立即学习C++免费学习笔记(深入)”;

  • 一个 socket fd 只由一个线程负责 I/O;用 epoll + 单 Reactor 模型最稳妥
  • 需要并行处理业务逻辑?把解包后的任务投递到线程池,但收发和协议解析仍在 I/O 线程完成
  • 真要多线程收发(如某些 udp 场景),得用 SO_REUSEPORT 让内核分流连接,每个线程 bind 自己的 socket,而不是共享同一个 fd

真正卡住性能的往往不是底层 syscall,而是缓冲区管理方式、协议解析是否零拷贝、以及事件驱动逻辑有没有隐式阻塞。这些细节没对齐,换再快的 I/O 多路复用也没用。

text=ZqhQzanResources