← ALL ARTICLES
游戏网络7 MIN READ

游戏网络传输

全库目录221
本文目录14

一次send并不对应一次recv,UDP也不保证消息到达。游戏网络传输先定位端点,再根据消息有效期选择可靠性、顺序与拥塞策略。序列化长度与分帧保证接收端能安全识别完整消息。

端点与连接建立

分层、地址与端点

OSI 七层模型适合解释职责,实际互联网协议栈常按链路、网络、传输和应用理解。游戏代码主要面对 IP 路由、TCP/UDP/QUIC 传输与应用协议;网卡帧、交换与路由仍会影响 MTU、丢包和延迟,但不应泄漏成每个玩法系统的细节。

IPv4 地址标识网络接口,端口标识主机上的传输端点,协议、地址和端口共同确定通信目标。私网地址通常需要 NAT 映射;服务监听地址、外网可达地址和客户端看到的地址可能不同。连接问题应分别检查 DNS、路由、防火墙、端口监听和会话握手。

同一局域网发送 IPv4 包时,主机先查询路由表。直连目标通过ARP查询目标IP对应的链路层地址;经网关路由的目标查询下一跳网关的链路层地址,缺少更具体路由时才使用默认网关,再由路由器逐跳转发。DHCP 可以为客户端分配地址、网关、DNS服务器 和租约。抓包定位连接问题时,应分清链路可达、IP 路由、传输端口和应用握手四个层次。

IPv6 使用更大的地址空间,并通过邻居发现等机制处理本地寻址。应用应尽量使用地址族无关的解析与 Socket API,不能把文本 IPv4 地址或固定长度写进协议结构。

DNS

域名系统(DNS)把域名解析为地址和其他记录。客户端先查询本机缓存,再请求配置的递归解析器。解析器在需要时逐级查询根、顶级域和权威服务器,并按 TTL 缓存响应。应用的解析路径还受操作系统和网络配置影响。

连接候选常来自A或AAAA地址记录,CNAME指向别名,SRV还提供端口、优先级和权重;MX和TXT分别服务邮件路由与文本验证等用途。游戏握手需要的是可用端点,解析到其他记录不构成连接成功。

DNS 返回多个地址时,客户端需要连接回退和超时策略。TTL 到期、负缓存、递归服务器故障和本地网络劫持都会影响结果。DNS 解析成功只说明获得了候选地址,不代表端口可达或服务健康。

传输与消息边界

TCP 与 UDP

TCP 提供有序、可靠的字节流,带拥塞控制,适合登录、聊天、配置和不频繁的事务。一个前部包丢失会阻塞后续字节交付,实时状态若全部走同一连接可能产生队头阻塞。

UDP 是数据报,不保证到达、顺序和唯一性,但应用可以决定哪些数据值得重传。高频位置快照过期后通常无需补发,关键技能和库存操作则需要可靠确认。选择 UDP 不等于忽略拥塞与安全,也不等于天然更快。

可靠 UDP 常加入序号、ACK/NACK、滑动窗口、超时重传和重复包过滤。不同消息可设置可靠有序、可靠无序或不可靠。重传策略必须考虑 RTT 和拥塞;无上限地补包会在网络恶化时进一步堵塞链路。

ARQ 是通过确认、超时和重传恢复丢包的一类机制。滑动窗口允许多个包同时在途,避免 Stop-and-Wait 每次只利用一个 RTT。窗口既限制未确认数据量,也用于判断序号范围、重复包和累计确认。

KCP 在 UDP 上实现面向消息的可靠传输,以更积极的重传和窗口策略换取较低延迟。它仍需要应用提供 UDP 收发、定时更新、会话标识和拥塞参数。可靠传输并不适合所有实时状态,过期快照应优先丢弃而不是排队补齐。

TCP 连接与可靠性

TCP 用序号标识字节位置,用 ACK 表示下一段期望字节。接收窗口实现流量控制,限制发送方不能淹没接收缓冲;拥塞窗口则根据网络拥塞调整在途数据量。两者取较小值决定实际发送窗口。

建立连接使用三次握手:客户端发送 SYN,服务端回复 SYN+ACK,客户端再 ACK。第三步让双方都确认收发方向、初始序号和连接参数。断开通常需要双方分别发送 FIN 和 ACK,因为 TCP 支持半关闭;主动关闭方可能进入 TIME_WAIT,保留一段时间以吸收旧包并确保最后 ACK 可重传。

取一个简化序列:发送连续字节区间[100,104)和[104,108),第一段丢失、第二段先到。接收端可缓存后段,却仍期待字节100;累计ACK维持100,不能向应用交付第二段来跳过空洞。重传第一段到达后,连续前缀扩展到108,应用才可取得完整有序数据。晚到的同一字节段按序号去重,不会变成第二份业务消息。

可靠性来自校验、序号、累计或选择确认、超时重传和重复数据去除。重传超时根据 RTT 和抖动估计。收到多个重复 ACK 时可进行快速重传,不必等待完整超时。

拥塞控制通常经历慢启动、拥塞避免、快速重传和恢复。具体算法可以是 Reno、CUBIC、BBR 或平台实现,应用不能假设窗口增长方式固定。丢包、ECN 和 RTT 变化会影响发送速率。

Nagle Algorithm 会合并未确认的小块写入,减少小包数量;延迟确认会稍晚发送确认。两者叠加可能增加交互延迟。实时小消息可以评估 TCP_NODELAY,但关闭 Nagle 后仍应在应用层批量消息,避免产生大量包头和系统调用。

TCP Keepalive 用于长时间空闲后探测失效连接,默认间隔通常不适合快速游戏断线判断。应用层心跳能同时携带 Tick、延迟和会话状态,并使用业务需要的超时。

数据包与序列化

协议需要明确包头、消息类型、长度、版本、序号和校验方式。TCP 是字节流,没有天然消息边界,因此必须长度前缀或其他分帧。UDP 保留数据报边界,但还要处理 MTU,尽量避免 IP分片。

所谓 TCP 粘包不是 TCP 把消息错误合并,而是应用把一次 send 当成一次 recv。字节流只保证顺序,不保留调用边界;接收端必须循环读取并按长度或分隔协议组帧,也要处理半包。

多字节数字要约定字节序。序列化格式应限制长度、层级和枚举范围,不能信任远端给出的数组大小。高频状态可用位打包、量化和差量压缩,控制面消息更看重兼容与可诊断性。

协议演进要区分可选字段和破坏性修改。客户端、服务端版本不一致时应明确拒绝或走兼容分支,不能靠解析异常猜测。

接收缓冲与分帧循环

TCP 保证有序字节流,不保留发送时的消息边界。接收缓冲需要先等待固定长度头,再等待完整负载:

cpp
constexpr size_t wireHeaderBytes = 8; // 2字节版本、2字节类型、4字节负载长度
while (receiveBuffer.Size() >= wireHeaderBytes) {
    PacketHeader header = PeekNetworkOrderHeader(receiveBuffer);
    if (header.payloadBytes > maxPayloadBytes ||
        header.payloadBytes > SIZE_MAX - wireHeaderBytes) {
        Disconnect("oversized packet");
        return;
    }
    size_t packetBytes = wireHeaderBytes + size_t(header.payloadBytes);
    if (receiveBuffer.Size() < packetBytes) break;
    receiveBuffer.Consume(wireHeaderBytes);
    Bytes payload = receiveBuffer.Consume(header.payloadBytes);
    DispatchValidated(header.messageType, payload);
}

例如头部声明负载4字节,整个消息需要12字节。第一次recv得到5字节时连头都不足,缓冲保留5;再来5字节后已有10,头可解析但还缺2字节,仍不消费;第三次来14字节,前2字节补齐第一条,剩余12字节可恰好构成第二条。循环先派发第一条再检查剩余缓冲,不能把第三次recv的14字节整体当一条新消息。

本片段固定线上头长为8字节。PeekNetworkOrderHeader按偏移0、2、4分别读取大端的16位版本、16位类型与32位负载长度,不直接复制C++结构体;接收缓冲字节按无符号解释。总长度使用size_t,先检查剩余可表示范围再相加。头字段需要固定字节序,长度在分配内存前校验,消息类型进入反序列化器前校验权限和版本。一次 recv 可能只得到半个包,也可能得到多个包。用随机切分的输入字节流做测试,解析结果应与发送边界无关。

本机机制检查:20种随机字节切分得到相同的消息序列;模型使用2字节长度头,没有建立真实 Socket。检查代码见 reference.py,运行 python 知识库/examples/mechanisms/reference.py。真实Socket收发、丢包恢复和拥塞行为需要传输栈实验。

诊断

网络问题至少记录时间、连接、消息类型、序号、字节数、RTT、丢包率、重传和队列长度。抓包能看见传输事实,游戏内 网络性能分析器则要关联到实体和系统。模拟延迟、抖动、丢包、乱序和限带宽,比只在本机回环测试更接近真实环境。

消息年龄与队列

可靠发送队列里的旧快照,即使最终送达,也可能已经没有显示价值。每条消息同时记录生成Tick、有效期限和可靠性类别:库存请求保留事务ID等待结果,高频位置可让新状态覆盖尚未发送的旧状态。拥塞时先减少可替代状态的频率与精度,避免可靠队列积压进一步推高延迟。

TCP分帧只恢复应用消息边界。长度头合法仍不说明负载合法:解码时继续检查字段范围和版本,并限制一次派发的消息数,防止大量小合法包占满一个Tick。头部长度常量、剩余缓冲字节数与消息预算分别记录,才能区分半包等待和业务过载。

验证方法

随机切分字节流比较分帧结果,注入乱序、重复、丢包与限带宽,记录消息年龄和队列。固定线上头长并检查总长度溢出,超长包断开后立即退出解析。

实现与证据

现有 reference.py提供2字节头的随机分帧模型。正文网络代码是机制片段,真实Socket、拥塞与链路故障需在目标传输栈测量;CPU分帧不证明这些结果。

相关主题

参考资料

  • RFC 9293:第3节Sequence Numbers、Acknowledgment及接收处理:字节序号、累计确认、重组与重复去除;2026年10月23日复读。固定线上头是应用协议。
  • RFC 8085:3.1拥塞控制、3.2消息大小;UDP应用责任。