Skip to content

路线 B|C++ Linux 游戏服务端 ​

B2|Network:Socket、TCP、事件循环与协议 ​

适用基础: B1、Stage07 目标: 实现一个能处理多连接、明确消息边界和超时的 TCP 服务。


一、真实问题:TCP 为什么会“半包” ​

一次 send 不保证一次 recv,TCP 是有序字节流而非消息队列。必须自己设计消息边界,处理半包、粘包、短写、断开、超时和客户端不可信输入。

二、Socket 生命周期 ​

text
socket → bind → listen → accept
→ read/write → shutdown → close

监听 fd 和连接 fd 的错误处理不同。每次系统调用都检查返回值、errno 和 EINTR;关闭连接时清理 session、缓冲区和定时器。

TCP 建连和关闭 ​

三次握手同步双方初始序列号并确认双向可达;它不是应用认证。四次挥手反映 TCP 的两个方向可以独立关闭,read 返回 0 表示对端发送方向结束,但本端可能仍能发送。RST、超时和正常 FIN 应映射为不同关闭原因,便于重连和诊断。

可靠性并不等于及时性 ​

TCP 使用序列号、确认、重传和滑动窗口提供可靠有序字节流;流量控制保护接收方,拥塞控制保护网络。丢包可能触发重传和延迟上升,因此“连接未断”不代表游戏消息仍在可接受时限内。

三、TCP 字节流与消息格式 ​

推荐长度前缀:

text
uint32 length (network byte order)
uint16 messageType
payload[length - header]

TCP 保证字节顺序和可靠传输,不保证消息边界,也不保证一次系统调用对应一次业务消息。接收端通常维护 input buffer:先累计固定头,再解析长度,等到完整 payload 后交给业务;发送端维护 output buffer,短写后等下一次可写事件。设置最大帧长,解析前检查整数溢出和长度合法性,不要把客户端给出的长度直接用于分配。

四、Blocking、Non-blocking 与 epoll ​

阻塞模式把等待交给线程,适合连接少、逻辑简单的服务;非阻塞模式把等待显式变成事件状态,适合大量连接。epoll 只报告“现在可能读/写”,不报告业务消息完整,也不替你管理连接状态。边沿触发需要持续读到 EAGAIN,水平触发则需要避免每轮重复处理未消费数据。

五、Reactor 模型 ​

text
epoll_wait
→ accept/read/write 事件
→ 解析连接输入
→ 生成业务命令
→ 投递任务或立即处理
→ 刷新发送缓冲

业务逻辑不能无限阻塞事件循环;重活应进入任务队列,并把结果以 session/connection ID 投回正确线程。

Backpressure ​

当生产消息速度高于网络发送速度时,数据会积存在 output buffer。没有上限就会把一个慢客户端变成进程级内存问题。背压策略包括限制单连接缓冲、暂停读取上游、丢弃可替代消息、合并状态更新或断开连接。策略取决于消息是否可靠、是否可覆盖以及玩家状态是否可重新同步。

Event Loop 的线程模型 ​

一种常见模型是每个 IO 线程拥有自己的连接集合,业务任务完成后把结果投回原 IO 线程。这样连接状态通常不需要跨线程加锁。若多个线程随意操作同一 Socket 和缓冲区,就必须额外定义顺序、关闭竞态和生命周期。

六、心跳、超时与重连 ​

心跳解决“连接是否仍可用”的应用语义,不等于 TCP 存活。为连接记录最后读写时间、认证状态和关闭原因;超时关闭必须幂等,重连要防止旧连接覆盖新 session。

七、协议设计的知识边界 ​

协议除了长度和类型,还要定义版本、字节序、认证状态、错误码、最大消息、超时和幂等语义。半关闭、重复包、旧 session、慢客户端和非法长度都属于协议的一部分,而不是异常情况。网络层只负责把合法字节交给协议层,不能把不可信 payload 直接交给游戏逻辑。

八、HTTP/WebSocket 边界 ​

HTTP 适合请求响应,WebSocket 在握手后提供双向消息通道。游戏长连接仍需自己的认证、心跳、序列化、限流和断线恢复语义,不能因为使用 WebSocket 就自动具备可靠业务协议。

WebSocket 提供消息帧、Ping/Pong 和浏览器兼容,但底层仍依赖 TCP,因此仍存在队头阻塞。UDP 允许应用选择可靠性策略,更适合部分实时数据,但需要自行面对 MTU、分片、拥塞、安全和 NAT。协议选择应从业务时限和丢失容忍度出发,而不是从“哪个更快”出发。

九、练习与答案 ​

1. 为什么 UDP 不等于更快? ​

它减少部分协议保证,但业务必须自己处理丢包、乱序、重传、拥塞和安全;是否更适合取决于消息语义和网络条件。

2. 为什么发送缓冲要限制? ​

慢客户端会让未发送数据无限增长,最终耗尽内存;应限长、限时、丢弃低优先级或断开连接。

十、阶段输出 ​

提交支持多连接、长度前缀、超时、心跳和错误输入防护的 TCP Server,并用抓包或结构化日志证明消息边界正确。