系统整理 UE(含 Iris 与传统模式共用的底层)在 UDP 之上自建的”半可靠协议栈”,并与 TCP 做横向对比。


一、UE 不可靠 UDP 协议栈整体架构

UE 没有直接裸用 UDP,也没有用 TCP,而是基于 UDP 自建了一套分层混合可靠性协议。整体可分为四层:

┌────────────────────────────────────────────────────┐
│  应用语义层  Iris ReplicationWriter / Reliable RPC │
│             ┌─ 属性同步:丢失重发"最新值"          │
│             ├─ Reliable RPC:FReliableNetBlobQueue │
│             └─ Unreliable RPC:丢即弃              │
├────────────────────────────────────────────────────┤
│  Bunch 分片层  UNetConnection / FOutBunch          │
│             ┌─ Bunch = 一段逻辑数据                │
│             ├─ 大 Bunch 拆分(PartialBunch)       │
│             └─ Reliable Bunch 序号窗口             │
├────────────────────────────────────────────────────┤
│  Packet 控制层  PacketHandler / FNetPacketNotify    │
│             ┌─ Sequence Number(包序号)           │
│             ├─ AckRecord 反向 ACK 位图             │
│             └─ Delivered/Lost/Discard 三态通知     │
├────────────────────────────────────────────────────┤
│  传输层  UDP Socket(OS 内核)                      │
│             ┌─ 不保证送达                           │
│             ├─ 不保证有序                           │
│             └─ 包大小受 MTU 限制                   │
└────────────────────────────────────────────────────┘

二、各层关键机制详解

第 1 层:UDP(OS 内核)

  • 能力:尽力交付的数据报;保留消息边界;MTU 内一发一收。
  • 缺陷:丢包、乱序、重复、无连接、无拥塞控制。
  • UE 利用点:要的就是它的”低延迟 + 不阻塞”,所有可靠性都自己上层补。

第 2 层:Packet 控制层(PacketHandler / FNetPacketNotify)

这是 UE 网络的”心脏”,负责把 UDP 升级为带 ACK 反馈的”事实通道”:

机制实现作用
Sequence NumberFNetPacketNotify::OutSeq每个出包递增序号
ACK 反向位图FNetPacketNotify::AckRecord收包时把”已收到”位 OR 进 history,回程 packet header 捎带
滑动窗口HistoryWordCount(默认 32×32 = 1024 位)窗口外未确认即判 Lost
三态翻译EPacketDeliveryStatus::{Delivered,Lost,Discard}通知应用层最终命运
加密 / 防篡改PacketHandler 责任链(DTLS、AES 等)可选

关键属性:只通知,不重传。重传策略完全交给上层。

第 3 层:Bunch 分片层(UNetConnection)

把”应用层逻辑数据块”映射到”Packet”:

  • Bunch:一段逻辑数据(一个 Actor 的属性、一个 RPC、Iris 的一段 ChangeMask 数据)。
  • 分片:当 Bunch 大于 packet 剩余空间,拆为 bPartial = 1 的多个连续片段,最后一片 bPartialFinal = 1。
  • Reliable Bunch 通道:
  • 每个 Channel 独立维护 InReliable / OutReliable 序号;
  • 接收端按序号去重 + 重排;
  • 发送端把未 Ack 的 Reliable Bunch 留在 OutRec 链表,丢失时按 packet ACK 反馈重发。
  • Unreliable Bunch:丢了就丢,packet ACK 通知后只做应用层位回滚(属性同步走的就是这个路径)。

第 4 层:应用语义层(Iris ReplicationWriter / Reliable NetBlob)

不同语义对 packet ACK 三态做不同响应:

数据类型DeliveredLostDiscard
属性同步(Iris)推进 baseline、释放 ChangeMask 副本ChangeMask 位 OR 回;作废 baseline释放资源
Reliable RPC从重发队列出队标记需重发;维持序号窗口释放 blob
Unreliable RPC释放 blob释放 blob,不重发释放 blob
对象 Create/Destroy状态机推进状态保持,下帧重发创建/销毁包释放资源
NetToken / Export标记已导出标记需重导释放

三、UE UDP 协议栈 vs TCP 横向对比

3.1 能力对比表

维度TCP(OS 内核)UE 自建 UDP 栈
送达保证✅ 字节级可靠⚠ 分语义:属性最终一致 / Reliable RPC 必达 / Unreliable RPC 不保证
顺序保证✅ 字节流严格有序⚠ 分语义:Reliable Bunch 有序、属性无序、Unreliable 乱序
去重✅ 内核处理✅ Reliable Bunch 按序号去重;Unreliable 不去重
重传策略✅ 内核自动(RTO + 快速重传)✅ 上层自定义(属性发”最新值”、RPC 发”原内容”)
拥塞控制✅ Cubic / BBR 等⚠ 简化:NetSpeed + 带宽预算 + 优先级调度
消息边界❌ 字节流,需上层分帧✅ Packet/Bunch 天然有边界
Head-of-Line 阻塞❌ 严重(一个字节丢了,后续全部阻塞)✅ 几乎无(属性走 Unreliable,丢了下帧补最新值;只有 Reliable Channel 内部有局部 HOL)
建立连接开销三次握手(1×RTT)UE Handshake(PacketHandler)通常 1~2 RTT
断开连接开销四次挥手 + TIME_WAIT即时断开
NAT 穿透友好度❌ 较差,需 STUN/TURN 协助✅ UDP 天然友好
多路复用❌ 单流✅ Channel/NetRefHandle 天然多路
应用层可控性❌ 内核黑盒✅ 完全可控(带宽分配、优先级、丢弃策略)

3.2 延迟特性对比

TCP 在 5% 丢包下的尾延迟:
  最坏情况 = RTO(>= 200ms) + 队列堆积 → P99 延迟可达 1~5 秒
  原因:HOL 阻塞 + 重传超时 + 拥塞窗口收缩

UE UDP 在 5% 丢包下的尾延迟:
  属性同步:丢了即下一帧(16~33ms)补最新值 → P99 延迟 ≈ 1~2 帧
  Reliable RPC:丢了下个 packet(16~33ms)重发 → P99 延迟 ≈ 1~2 RTT
  原因:无 HOL,重传由应用调度

结论:在丢包率 1%~10% 的真实公网环境下,UE 的尾延迟比 TCP 低一到两个数量级。


四、UE UDP 协议栈的优势

1. 延迟可控,无 HOL 阻塞

属性同步走 Unreliable Bunch,丢一个 packet 不会阻塞后续 packet 的处理。客户端总能拿到”较新”的状态,不会卡住等”较旧”的丢包重传。这是游戏体验的根本保证。

2. 重传语义可定制

属性丢了不重发”丢失值”,而是下一帧自然带上”最新值”——这比 TCP 的”必须按字节顺序重发旧数据”高效得多:

  • 节省带宽:旧值已被新值覆盖,重发旧值是浪费;
  • 加快收敛:客户端直接看到最新值,不需要逐步追赶。

3. 应用层完全控带宽

每条连接的 NetSpeed、每对象的 Priority、每帧的发包预算(MaxBunchSizeBytes、PacketBudget)都由 UE 自定,可以做到:

  • 关键 Actor(玩家自身)始终优先;
  • 非关键 Actor(远处场景)饥饿时自动延迟;
  • TCP 完全做不到这种”应用感知调度”。

4. 多路复用 + 边界天然

每个 Channel 独立序号、每个 Bunch 独立边界,天然支持”成千上万个并发对象同步”。TCP 单流模型必须在应用层再做分帧(如 KCP、QUIC),并仍受 HOL 影响。

5. NAT 穿透 / P2P 友好

UDP 天然支持打洞、P2P;TCP 在 NAT 后建连困难,必须经过中继服务器,对动作类游戏不可接受。

6. 混合可靠性”按需付费”

TCP 是”全可靠或全没有”,开销固定。UE 让每段数据自选可靠性级别:

  • 高频属性(位置、HP)→ Unreliable Bunch(最低开销);
  • 关键 RPC(购买物品、技能释放) → Reliable Bunch;
  • 一次性事件 → 不可靠 RPC 或 NetToken。

带宽利用率显著高于”什么都用 TCP”的方案。

7. 包级压缩与 Delta

Iris 的 baseline-delta 编码、ChangeMask 稀疏压缩、量化(Quantize)等,只有掌握 packet 边界才能高效实施。TCP 字节流模型无法天然支持这种 packet 级别的 codec。


五、UE UDP 协议栈的劣势

1. 实现复杂度高

需要自建 PacketHandler(加密、握手)、PacketNotify(ACK/序号)、Bunch(分片/可靠通道)、Replication(应用语义)四层。任何一层 Bug 都会导致同步异常,且调试困难(没有 Wireshark 直接解析)。

2. 拥塞控制简化,存在丢包恶化风险

UE 的”拥塞控制”基本是 NetSpeed 静态配额 + 带宽预算,没有 Cubic/BBR 那样的动态 RTT/丢包率反馈。在跨国跨网的极端弱网下,可能出现:

  • 持续打满带宽 → 持续丢包 → 状态长时间不收敛;
  • 没有 TCP 那样的”主动退避”。

部分项目会引入第三方传输库(如 KCP、QUIC、ENet)来补强。

3. 字节级可靠场景不适合

对于必须严格”每个事件都到达且有序”的场景(如登录、计费、聊天历史拉取),UE 仍然会切换到 TCP 或 HTTP/HTTPS——Online Subsystem、HttpModule 都是走 TCP 的。UDP 栈不是万能替代。

4. 防篡改和可观测性需要额外建设

TCP 自带 sequence + checksum,操作系统级防篡改。UE 必须在 PacketHandler 层挂 DTLS/AES,在应用层做反作弊(如 server-authoritative + 权威校验)。Wireshark 不能直接解析 UE bunch,调试需要专用工具(如 NetworkProfiler、DemoNet)。

5. 生态成熟度低于 TCP

TCP 经过 40 年优化,几乎所有 OS、CDN、负载均衡、防火墙都对 TCP 友好。UDP 在某些企业网络、移动运营商网络下会被限速甚至阻断,需要项目方做接入点优化(多点接入、就近调度)。

6. Reliable Channel 内部仍存在小范围 HOL

虽然全局没有 HOL,但单个 Reliable Channel 内部,丢一个 Reliable Bunch 会阻塞同一 Channel 后续 Reliable Bunch 的应用(必须按序号顺序应用)。所以 UE 鼓励关键 RPC 走独立小颗粒 Channel,而非堆在一起。

7. 对 UE 熟悉度的要求高

新人很容易写出”以为可靠其实不可靠”的代码(如把高频脏标的属性改成 RPC 又设成 Unreliable),出 bug 难以排查。需要团队整体对协议栈分层有认知。


六、综合对比:什么时候用 TCP,什么时候用 UE UDP

场景推荐方案原因
游戏内实时状态同步(位置、属性、技能)UE UDP 栈低延迟、无 HOL、最终一致即可
游戏内关键操作 RPC(购买、技能释放)UE UDP Reliable Bunch必达且有序,但仍要低延迟
登录、鉴权、计费、聊天历史TCP / HTTPS强一致 + 安全审计 + 与运营平台对接
资源下载、补丁分发TCP / HTTP大文件、CDN 友好、断点续传
跨服匹配、好友列表TCP / WebSocket长连接 + 推送,状态量小
录像回放(Demo)UE UDP(DemoNet)或 TCP 离线下载在线录像沿用 UDP 栈,离线下载用 TCP
P2P 联机(无中心服)UE UDPTCP 无法穿 NAT

七、一句话总结

TCP 是”为正确性优化的字节流”,UE UDP 协议栈是”为延迟优化的可定制混合可靠协议”。 TCP 把所有数据当成同等重要的”必须有序到达的字节”,因此在弱网下用尾延迟换正确性;UE 通过把数据切成 Packet/Bunch 并赋予不同的可靠性语义,让”游戏状态”享受低延迟、“关键事件”享受可靠性、“一次性事件”享受零开销——三者在同一条 UDP 通道上各取所需。代价是协议栈完全自建、拥塞控制简化、调试和生态成熟度较 TCP 低,需要团队投入和项目级运维支持。对实时多人游戏而言,UE 这套设计是延迟敏感场景下的最优解;但对登录/计费/下载等场景,TCP/HTTP 仍是不可替代的。