Skip to content

Protocol Reference

b23r0 edited this page Oct 12, 2026 · 4 revisions

English · 简体中文 · 首页

RakNet 传输协议参考

本页沿着发现服务端、建立连接、交付消息和恢复丢包的流程,说明 rust-raknet 实现的 RakNet UDP 传输,包括二进制格式和具体限制。它不代表原始 RakNet SDK 所有插件、安全扩展或协议版本的完整规范。

当前文档对应已发布的 1.4.0:crates.io,源码 b909c93。报文说明针对本项目实现,并非所有 RakNet 方言的完整规范。

目录

  1. 分层与术语
  2. 基础编码
  3. 发现与离线握手
  4. 连接内握手与控制消息
  5. 数据报与封装帧
  6. 五种交付模式与编号
  7. ACK/NACK 与丢包恢复
  8. 分片与 MTU
  9. 小包合并与本地调度
  10. 生命周期、边界与安全
  11. 互通检查与源码索引

分层与术语

应用消息(载荷以 0xfe 开头)
    ↓ 交付模式、可靠/序列/排序编号、可选分片元数据
封装帧(一个 frame-set 数据报可包含多个帧)
    ↓ 数据报 ID + 24 位数据报序号
UDP 载荷
    ↓ UDP 与 IP 首部
网络

消息是保留边界的应用载荷;帧携带消息或其中一个分片,加上交付元数据;frame set 是装有一个或多个帧的 UDP 数据报。这几个概念不能混用:一个数据报的 ACK 可以同时确认多个可靠帧。

发现、离线连接请求、ACK 和 NACK 直接作为 UDP 控制包发送。连接内握手和应用消息通常封装在 frame set 中。传输可靠性表示对端传输层收到数据,不表示对端应用已经消费、持久化或批准命令。

基础编码

类型 本库使用的编码
u8 1 字节
u16、u32、u64、i64 默认大端,明确标注时除外
u24 序号/索引 3 字节小端,按 2²⁴ 回绕
字符串 大端 u16 UTF-8 字节长度,随后为内容
离线 magic 00 ff ff 00 fe fe fe fe fd fd fd fd 12 34 56 78
IPv4 地址 04,4 个逐字节取反的地址字节,大端 u16 端口;共 7 字节
IPv6 地址 06,小端 i16 地址族值 23,大端端口,大端 32 位 flow 值,16 字节地址,大端 32 位 scope 值;共 29 字节

例如,127.0.0.1:19132 编码为 04 80 ff ff fe 4a bc。目前 IPv6 写入器将 flow/scope 写为零;支持这一地址字段不代表所有带作用域的 IPv6 部署均已验证互通。

序号采用串行数比较:非零的向前差值小于 2²³ 时才判定为更新。ff ff ff 后面是 00 00 00。接收端还会限制允许的前跳范围,因此不能将这些编号当作永不回绕的普通整数比较。

发现与离线握手

监听器接受 RakNet 协议 10 和 11;默认客户端请求 10。可通过 connect_with_version 和 connect_with_version_and_mtu 选择对端支持的版本。RakNet 版本与应用自身的协议版本是两个概念。

sequenceDiagram
    participant C as 客户端
    participant S as 监听器
    C->>S: 0x01 UnconnectedPing(可选)
    S-->>C: 0x1c UnconnectedPong + MOTD
    C->>S: 0x05 OpenConnectionRequest1 + 填充
    S-->>C: 0x06 OpenConnectionReply1 + MTU
    C->>S: 0x07 OpenConnectionRequest2 + 地址/GUID/MTU
    S-->>C: 0x08 OpenConnectionReply2
    C->>S: Frame set:0x09 ConnectionRequest
    S-->>C: Frame set:0x10 ConnectionRequestAccepted
    C->>S: Frame set:0x13 NewIncomingConnection
    Note over C,S: 开始连接内载荷交换
Loading

每个报文首先是 ID,表格按实际线格式排列 ID 后面的字段。magic 指上面的 16 字节常量;地址采用 IPv4/IPv6 可变长度编码。

ID 报文 ID 后的字段
0x01、0x02 未连接 ping 变体 i64 时间戳、magic、u64 客户端 GUID
0x1c 未连接 pong i64 回显时间戳、u64 服务端 GUID、magic、MOTD 字符串
0x05 Open connection request 1 magic、u8 协议版本、填充
0x06 Open connection reply 1 magic、u64 服务端 GUID、u8 安全标志、u16 名义 MTU
0x07 Open connection request 2 magic、服务端地址、u16 名义 MTU、u64 客户端 GUID
0x08 Open connection reply 2 magic、u64 服务端 GUID、客户端地址、u16 名义 MTU、u8 加密标志
0x12 Already connected magic、u64 服务端 GUID
0x19 Incompatible protocol version u8 支持的版本、magic、u64 服务端 GUID

0x19 表格描述的是发送写入器。目前读取器却先读 magic 再读版本字节,这种错误报文编解码不对称是处理不支持版本回复时的互通限制,不是另一种协商出来的布局。

GUID 用于传输协商中的节点标识,不是经过认证的身份。本库离线回复中的安全/加密标志为零,不实现原始 SDK 的安全握手变体。

Request 1 用填充探测名义 MTU:UDP 载荷长度为 MTU − 28,监听器用收到的载荷长度加 28 还原名义 MTU。这 28 字节来自 IPv4/UDP 约定,本库的包预算也使用此约定。这是握手时的协商,不是在连接建立后持续进行的路径 MTU 发现。

连接内握手与控制消息

ID 载荷 ID 后的字段
0x09 Connection request u64 客户端 GUID、i64 请求时间戳、u8 安全标志
0x10 Connection request accepted 客户端地址、u16 system index、内部地址列表、i64 请求时间戳、i64 接受时间戳
0x13 New incoming connection 服务端地址、内部地址列表、i64 请求时间戳、i64 接受时间戳
0x00 Connected ping i64 客户端时间戳
0x03 Connected pong i64 回显客户端时间戳、i64 服务端时间戳
0x15 Disconnect notification 无额外载荷
0xfe 应用消息 由应用定义的后续字节

序列化器写入 10 个内部地址;解析器兼容常见的 10/20 地址布局,并要求末尾剩余 16 字节时间戳。这些连接内控制载荷不再重复离线 magic。完成离线 MTU 协商后,还需要 0x09 → 0x10 → 0x13 来建立连接内就绪状态。

本地超时使用单调时钟,报文时间戳则是独立的墙上时钟字段。send 会等待握手完成。发现接口无需建立应用连接就能获得 MOTD。当前应用接收路径只向调用者交付以 0xfe 开头的载荷;自定义协议应将任意数据包在该字节之后,而不是另选首字节。

数据报与封装帧

解码器接受 0x80..=0x8d 的 frame-set ID。写入器使用 0x84;首片之后的分片加上 continuation 位,成为 0x8c。这是本库所支持的传输形式,并不覆盖上游 RakNet 的所有数据报变体。

Frame-set 首部:
  u8 报文 ID
  u24-LE 数据报序号

重复的帧记录:
  u8 flags
  u16-BE 载荷位数(不是字节数)
  [u24-LE 可靠索引]                   可靠模式
  [u24-LE 序列索引]                   sequenced 模式
  [u24-LE 排序索引 + u8 通道]         有序或 sequenced 模式
  [u32-BE 分片总数 + u16-BE 分组 ID + u32-BE 分片索引]
  载荷字节

flags 高 3 位编码可靠模式,即 flags >> 5;第 4 位(0x10)表示分片。载荷位数不包含任何首部。解析器要求长度按字节对齐,且每个条件字段都必须有足够字节。

模式值 不含载荷的帧元数据 单帧 UDP 载荷开销,不含 IP/UDP
0 Unreliable flags + 长度 7 B
1 UnreliableSequenced flags + 长度 + 序列 + 排序/通道 14 B
2 Reliable flags + 长度 + 可靠索引 10 B
3 ReliableOrdered flags + 长度 + 可靠索引 + 排序/通道 14 B
4 ReliableSequenced flags + 长度 + 可靠索引 + 序列 + 排序/通道 17 B

开销列包含一次 4 字节 frame-set 首部;分片元数据还会为每帧增加 10 B。多个帧合并只能摊薄 frame-set/IP/UDP 首部,每帧仍保留自己的条件元数据。

五种交付模式与编号

模式 丢包处理 接收端交付 典型场景
Unreliable 不保留可靠重传状态 尽力交付,不保证顺序 可丢弃遥测或更新
UnreliableSequenced 不保留可靠重传状态 丢弃落后于通道已接受状态的序列索引 新状态替代旧状态的位置、瞄准更新
Reliable 重传直到确认或连接失败 可靠,但不保证发送顺序 相互独立的事件
ReliableOrdered 重传直到确认或连接失败 按通道连续交付排序索引,有缺口就等待 命令、事务、文件分块
ReliableSequenced 保留可靠重传状态 可靠传输,加上按通道过滤过时序列 不必交付过时状态的更新

ReliableSequenced 不保证每个旧更新都进入应用。传输层收包与过时过滤是不同步骤;它也不代表本库发送端在提交新状态时会取消所有旧状态的待重传帧。

四个编号各有职责:

  • 数据报序号: 标记一次传输,供 ACK/NACK 使用;重传时换新值。
  • 可靠索引: 标记逻辑可靠帧,用于去重;重传不改变它,每个分片拥有自己的可靠索引。
  • 排序索引: 定义同一通道的发送次序;同一有序消息的所有分片共享它,重组完成后只占一个排序位置。
  • 序列索引: 按通道过滤过时 sequenced 消息;排序和序列计数在通道元数据中有关联,不是所有模式共享一个全局交付顺序。

公开通道接口使用 u8,默认发送使用通道 0。有序/sequenced 状态按通道维护。独立通道能减少一个有序缺口阻塞无关有序流的情况,但仍共享 UDP、发送容量和瓶颈带宽。不能因本库参数是 u8 就认为所有其他 RakNet 实现支持 256 个通道。可靠去重窗口允许 65,536 范围内的前向索引,跳跃过远会被拒绝。

ACK/NACK 与丢包恢复

ACK(0xc0)和 NACK(0xa0)直接作为 UDP 控制包发送:

u8 ID
u16-BE 记录数量
重复的记录:
  u8 single 标记                  1 = 单个,0 = 闭区间
  u24-LE 起点
  [u24-LE 终点]                   仅区间携带

例如,确认数据报 7 为 c0 00 01 01 07 00 00;请求重传闭区间 7–9 为 a0 00 01 00 07 00 00 09 00 00。跨回绕的区间拆成不回绕的多个区间。无效标记、反向区间和截断数据会在不安全分配或编号展开之前被拒绝。

接收端把收到的数据报序号汇总成 ACK 区间,将缺口记入 NACK 区间。缺口只能说明可能丢失,乱序也会形成缺口。在 NACK 发出前补到的数据报会移除对应缺口。ACK 表示数据报收到了,不是业务确认。

发送端有三条恢复路径:

  1. 显式 NACK: 匹配在途可靠帧的数据报序号,立即标记重传;NACK 处理器会发送可重传帧,无需等到 RTO。
  2. ACK 缺口快速重传: 有效的后续 ACK 推进已确认序号时,会暴露更早仍在途的数据报,并标记重传。分组合并 ACK 会先处理区间内所有已确认成员,再找剩余缺口。
  3. 计时器兜底: 如果反馈也丢了,或尾包之后没有 ACK 暴露缺口,到期后仍会重传。

重传保持可靠/排序/序列元数据不变,只换数据报序号。每个在途帧最多保留 64 个历史数据报编号,让迟到 ACK 仍能释放重传状态。超出真实发送历史的 ACK 不推进发送状态。合并数据报的 ACK 会确认其中所有可靠帧,不可靠帧则不进入可靠重传队列。

当前计时算法属于本库的具体实现:

初始 SRTT = 50 ms;初始基础 RTO = 50 ms
SRTT = floor((4 × 旧 SRTT + RTT 样本) / 5)
基础 RTO = clamp(floor(1.5 × SRTT), 50 ms, 12,000 ms)
重复重传间隔:每次乘 1.5,最多 12,000 ms

重传帧不参与有歧义的 RTT 采样,即 Karn 规则。缓存重传截止时间可避免每次发送都扫描全部在途帧。该估计器不使用 TCP 的 RTT 方差公式。乱序下 ACK 缺口快速重传可能带来额外流量;超时退避本身也不等于自适应拥塞控制。

分片与 MTU

默认名义 MTU 为 1400 B,客户端和监听器 MTU 接口允许 61–1492 B。发送消息按 协商 MTU − 60 的载荷预算分片,为首部预留空间。默认情况下每片载荷预算为 1340 B,包含应用的 0xfe 字节。

目前只有 ReliableOrdered 自动拆分过大的发送消息。其他四种模式返回 PacketSizeExceedMTU,不会悄悄换成另一种可靠模式。若需要在其他模式中传输较大的逻辑数据,应在应用层定义分块和重构语义。

每片携带总数、分组 ID 和从零开始的片索引。接收端对可靠帧去重、核对分组元数据,集齐全部索引后按片索引拼接,再执行有序交付。重复分片不会让分组完成两次。分片的 ACK 会释放其传输重试状态,即使整条消息尚未完成重组。

本库限制:每组最多 65,536 片,最多 1,024 个并行重组组,重组记账上限 64 MiB,包含每帧 128 B 的估算元数据。分组 60 秒未收到新的分片进展时到期;当前连接维护会关闭连接,不交付半条消息。分组 ID 为 16 位,不能作为长期唯一的业务消息 ID。

小包合并与本地调度

send 不会增加凑包计时器。显式 batch 接口可合并已经就绪、未分片的 ReliableOrdered 帧;每个数据报最多 8 帧,且必须符合协商的包预算。每条逻辑消息仍有自己的索引和边界。其他模式和分片消息使用独立数据报。收到 NACK 或触发重传超时后,该连接余下时间停用合并,使用普通丢包恢复路径。

默认可靠在途窗口是 64 帧,不是 64 条应用消息,也不是 64 个合并数据报。一个分片消息会占多个槽位;合并数据报中的每个可靠帧也各占一个槽位。

可选 send-policy feature 增加本地帧数/编码字节在途限制、可靠与不可靠队列预算、单次 flush 字节预算。通过 set_send_options 按连接启用。它使用现有线格式,无需对端协商。独立待发送队列可以保留不可靠消息准入容量,但预留和公平调度均不保证网络带宽或交付。本库不自动估计码率、不自动调整拥塞窗口,不提供固定速率 pacing,也不会淘汰已经接收进入队列的不可靠消息。具体配置和实测代价见发送策略。

生命周期、边界与安全

  • 默认发送记账具有 256 KiB 准入高水位和 64 MiB 硬上限,每帧估算 128 B 开销。允许进入队列的大型有序消息可以超过软高水位;这些数值不是进程 RSS 限制。
  • 有序接收缓冲最多 65,536 条消息和 64 MiB 载荷。应用交付 channel 和待交付载荷还有独立边界;大量发送时仍应持续接收。不要将传输 ACK 当作业务完成信号。
  • flush 等待发送队列及可靠确认状态排空,不代表应用处理完毕。close、对端断开和接收超时会终止连接,操作可能失败。
  • 连接维护通常采用 50 ms 节奏,新工作可以唤醒它;明确丢包反馈可立即触发重传。空闲维护需要显式启用,可在安静 500 ms 且队列为空后进入休眠,新流量会唤醒。当前接收不活跃超时为 60 秒。
  • 本库不提供传输 TLS、GUID 身份认证、加密 ACK 认证、原 SDK 安全握手、拥塞公平性保证、NAT 穿透插件或 NetherNet。应按部署需要提供可信网络边界或应用安全机制。
  • 原始 RakNet 还定义了 ACK receipt 模式和 SDK 优先级/插件功能。本库公开的可靠模式仅支持 0–4 五个值,不能因为上游存在 5–7 就推断本库支持它们。

互通检查与源码索引

核对协议版本、名义 MTU 约定、数据报 ID、通道范围、10/20 内部地址布局、字节序、载荷前缀和分片行为。使用实际目标对端验证消息交换;离线 ping 成功不能说明连接内握手和交付语义均正常。终止协议连接的反向代理会建立两条独立 RakNet 连接,各自生成传输元数据。

内容 对应源码副本中的模块
ID、握手、ACK/NACK 编解码 src/packet.rs
整数、字符串、地址编码和 magic src/datatype.rs
帧格式、接收队列、ACK 缺口、重传和合并 src/arq.rs
串行数比较与可靠去重 src/sequence.rs
分片重组、元数据检查和上限 src/fragment.rs
连接任务、应用前缀、反馈和生命周期 src/socket.rs
版本/MTU 协商、发现和连接接收 src/server.rs、src/utils.rs
可选发送限制 src/send_options.rs
平台专用 UDP 快速路径 src/udp.rs

上游 SDK 的 PacketPriority.h、MessageIdentifiers.h 和 ReliabilityLayer.cpp 是原始可靠模式、消息标识和可靠传输机制的一手参考。本文的数字格式、限制和调度行为已按本项目源码核对,不假定上游行为完全一致。

可选恢复调度(1.2.0)

恢复策略加入期限唤醒、完整尾部数据报探测和RTT进展退避,不改变上述报文。选项默认全关;编译功能还修正反馈处理后的后续发包时间戳。基础RTO估算和普通超时/NACK回退保留。

Clone this wiki locally