可靠UDP传输协议AI开发指南 (AI Development Guide for Reliable UDP Protocol)
1. 项目概述 (Project Overview)
核心目标 (Core Goal): 基于 Tokio 的异步 UdpSocket
,从零开始实现一个高性能、支持并发连接的可靠UDP传输协议库。
指导原则 (Guiding Principles):
-
极致异步与无锁化 (Async-first & Lock-free):
- 所有IO操作和内部状态管理必须是完全异步的。
- 严格禁止使用任何形式的阻塞锁 (
std::sync::Mutex
,parking_lot::Mutex
等)。 - 优先采用消息传递(
tokio::sync::mpsc
)和原子操作 (std::sync::atomic
,ArcSwap
) 来管理并发状态,保证数据在不同任务间的安全流转,将共享状态的修改权限定在单一任务内。
-
为极差网络环境优化 (Optimized for Unreliable Networks):
- 协议设计必须显式地处理高丢包率、高延迟和网络抖动(Jitter)的场景。
- 拥塞控制算法应保守且能快速适应网络变化,避免传统TCP在这些场景下的激进重传和窗口骤降问题。
- 优先保证数据的可靠到达,其次才是吞吐量。
-
清晰的代码与文档 (Clarity in Code & Documentation):
- 所有公开的 API (函数、结构体、枚举等) 必须提供中英双语的文档注释 (
doc comments
)。 - 协议实现中的关键逻辑、状态转换、复杂算法等部分,必须添加清晰的实现注释。
- 代码风格遵循官方的
rustfmt
标准。
- 所有公开的 API (函数、结构体、枚举等) 必须提供中英双语的文档注释 (
-
AI 协作语言 (AI Collaboration Language):
- 为确保沟通的清晰和统一,所有与本项目相关的AI助手回复都必须使用 中文。
2. 协议设计 (Protocol Design)
核心思想融合 (Integration of Core Ideas): 我们将以最初的设计为蓝图,并深度融合 AP-KCP 和 QUIC 的优秀特性。
- AP-KCP: 借鉴其精简的头部设计、高效的ACK机制、包粘连和快速连接思想。
- QUIC: 借鉴其长短头分离的概念,用于区分连接管理包和数据传输包,进一步优化开销。
2.1. 数据包结构 (Packet Structure)
为了在不同场景下达到最优效率,我们借鉴QUIC,设计两种头部:长头部 (Long Header) 用于连接生命周期管理,短头部 (Short Header) 用于连接建立后的常规数据传输。
命令 (Command) / 包类型 (Packet Type): 所有包的第一个字节为指令字节。我们将AP-KCP的指令集与我们的状态管理需求结合:
0x01
:SYN
(长头) - 连接请求。0x02
:SYN-ACK
(长头) - 连接确认。0x03
:FIN
(短头) - 单向关闭连接。0x10
:PUSH
(短头) - 数据包。0x11
:ACK
(短头) - 确认包,其载荷为SACK信息。0x12
:PING
(短头) - 心跳包,用于保活和网络探测。0x13
:PATH_CHALLENGE
(短头) - 路径验证请求,用于连接迁移。0x14
:PATH_RESPONSE
(短头) - 路径验证响应,用于连接迁移。
短头部 (Short Header) - 19字节 (参考 AP-KCP)
用于 PUSH
, ACK
, PING
包。这是最常见的包类型,头部必须极致精简。
字段 (Field) | 字节 (Bytes) | 描述 (Description) |
---|---|---|
command | 1 | 指令, 0x10 , 0x11 , 0x12 , 0x13 , 0x14 etc. |
connection_id | 4 | 连接ID。AP-KCP使用2字节,我们暂定4字节以备将来扩展,但可作为优化点。 |
recv_window_size | 2 | 接收方当前可用的接收窗口大小(以包为单位)。 |
timestamp | 4 | 发送时的时间戳 (ms),用于计算RTT。 |
sequence_number | 4 | 包序号。 |
recv_next_sequence | 4 | 期望接收的下一个包序号 (用于累积确认)。 |
长头部 (Long Header)
用于 SYN
, SYN-ACK
。包含版本信息和完整的连接ID。
格式待定,但至少应包含 command
, protocol_version
, source_cid
, destination_cid
。
2.2. 连接生命周期 (Connection Lifecycle)
- 快速连接建立 (Fast Connection Establishment): 借鉴AP-KCP和QUIC的0-RTT思想。
- 客户端 (Client): 客户端发出的第一个
SYN
包可以直接携带业务数据。 - 服务端 (Server): 服务器若同意连接,会先返回一个
Stream
句柄给应用层。连接此时处于“半开”状态。当应用层首次调用write()
准备发送数据时,协议栈会将一个无载荷的SYN-ACK
帧和携带业务数据的PUSH
帧聚合(Coalesce)到同一个UDP数据报中,一并发送给客户端。这确保了SYN-ACK
的发送与服务器的实际就绪状态同步,构成了完整的0-RTT交互。客户端收到SYN-ACK
后,连接即进入Established
状态。
- 客户端 (Client): 客户端发出的第一个
- 可靠连接断开 (Reliable Disconnection): 保持标准的四次挥手 (
FIN
->ACK
->FIN
->ACK
),确保双方数据都已完整传输和确认,防止数据丢失。 - 状态机 (State Machine): 每个连接必须维护一个明确的状态机 (e.g.,
SynSent
,Established
,FinWait
,Closed
)。
2.3. 可靠性与传输优化 (Reliability & Transmission Optimizations)
-
ARQ 与 快速重传 (ARQ & Fast Retransmission):
- 超时重传 (RTO): 基于动态计算的RTT来决定超时重传。
- 快速重传: 当收到某个包的ACK,但其序号之前的包ACK丢失,并且后续有N个包(例如3个)都已确认时,立即重传该丢失的包,无需等待RTO超时。
-
选择性确认与ACK批处理 (SACK & ACK Batching):
- 核心机制:
ACK
包的Payload不再是确认单个包,而是携带一个或多个SACK段 (SACK Ranges),例如[[seq_start_1, seq_end_1], [seq_start_2, seq_end_2]]
。 - 效率: 这种方式极大提高了ACK信息的承载效率,一个ACK包就能清晰描述接收窗口中所有“空洞”,完美替代了原版KCP中发送大量独立ACK包的低效行为。
- 核心机制:
-
包粘连/聚合 (Packet Coalescing):
- 在发送数据时,如果队列中有多个小包(如一个
PUSH
和一个ACK
),应将它们打包进同一个UDP数据报中发送。这能显著降低网络包头的开销,提升有效载荷比。
- 在发送数据时,如果队列中有多个小包(如一个
-
快速应答 (Immediate ACK Response):
- 当接收方在短时间内收到了大量数据包,导致待发送的ACK信息(无论是新的ACK还是重复的ACK)积累到一定数量时,应立即发送一个ACK包,而不是等待下一个心跳周期。这能让发送方更快地更新RTT和拥塞窗口。
2.4. 流量与拥塞控制 (Flow & Congestion Control)
-
流量控制 (Flow Control):
- 使用滑动窗口协议 (Sliding Window Protocol)。接收方通过每个包头中的
recv_window_size
字段动态地告知发送方自己还有多少可用的缓冲区空间。
- 使用滑动窗口协议 (Sliding Window Protocol)。接收方通过每个包头中的
-
拥塞控制 (Congestion Control):
- 决策: AP-KCP采用激进的、基于丢包的策略。虽然这在某些网络下能获得高吞吐,但与我们**“为极差网络环境优化”**的核心目标相悖。在丢包不等于拥塞(如无线干扰)的网络中,这种策略会导致错误的窗口收缩。
- 我们的选择: 我们将坚持采用基于延迟的拥塞控制算法 (如 Vegas-like 或简化的 BBR)。当检测到RTT开始增加时,就认为网络出现拥塞迹象并主动降低发送速率。这比等到丢包再反应要更加敏感和稳定,更适合不稳定的网络环境。
3. 实现架构指南 (Implementation Architecture)
3.1. 核心组件 (Core Components)
TransportReliableUdpSocket
/TransportListener
: 协议栈对外的统一用户接口。TransportReliableUdpSocket
负责发起新连接,而TransportListener
负责接收传入的连接。它们共同构成了用户与协议栈交互的入口点。SocketEventLoop
: 协议栈的“大脑”和事件中枢。它在一个专用的异步任务中运行,负责统一处理所有I/O事件,包括接收UDP数据包、处理用户API命令(如connect
)以及管理所有连接的生命周期。FrameRouter
:SocketEventLoop
内部的智能路由引擎。它维护着ConnectionId -> Endpoint
和SocketAddr -> ConnectionId
的双重映射,能准确地将网络帧分派给正确的Endpoint
实例,并原生支持连接迁移(NAT穿透)。Endpoint
: 代表一个独立的、可靠的连接端点。这是实现协议核心逻辑的地方。每个Endpoint
实例及其所有状态都由一个独立的Tokio任务(tokio::task
)拥有和管理。Endpoint
内部实现了完整的协议状态机,并拥有一个ReliabilityLayer
实例来处理可靠性机制。ReliabilityLayer
:Endpoint
内部的可靠性引擎,聚合了ARQ、SACK、拥塞控制(Vegas
)、收发缓冲区等所有核心可靠性算法。transport_sender_task
: 一个专职的、独立的批量发送任务。协议栈中所有需要发送的数据包都会通过MPSC通道发送给它,由它进行聚合和批量发送,以优化网络性能。
3.2. 无锁并发模型 (Lock-free Concurrency Model)
协议栈的并发模型基于Actor模型构建,通过tokio::sync::mpsc
通道实现任务间的异步消息传递,完全避免了传统锁。
-
读写分离的I/O任务:
- 接收路径:
SocketEventLoop
在其主循环中拥有UDP套接字的接收权。它持续调用transport.recv_frames()
,接收所有传入的数据报。 - 发送路径: 一个独立的
transport_sender_task
拥有UDP套接字的发送权。所有Endpoint
任务产生的待发数据包,都会通过一个全局MPSC通道发送给此任务。
- 接收路径:
-
智能分发与路由:
SocketEventLoop
收到数据报后,将其解码成Frame
。FrameRouter
根据Frame
头部的连接ID(CID)或源地址,从映射表中找到对应的Endpoint
任务的通信通道。
-
消息驱动的
Endpoint
:FrameRouter
将Frame
通过MPSC通道发送给目标Endpoint
任务。- 用户通过
Stream
API写入数据时,数据同样通过MPSC通道作为命令发送给Endpoint
任务。
-
隔离的连接状态:
- 每个
Endpoint
任务拥有其连接的全部状态(ReliabilityLayer
、缓冲区、状态机等)。由于所有状态修改都在这个单一的任务内完成,因此从根本上杜绝了数据竞争。
- 每个
graph TD subgraph "用户应用" UserApp -- "write()" --> StreamHandle StreamHandle -- "read()" --> UserApp end subgraph "协议栈任务" subgraph "Socket EventLoop (接收和路由)" direction LR RecvTask["Transport::recv_frames()"] --> Router{FrameRouter} end subgraph "Endpoint 任务 (每个连接一个)" direction LR EndpointLogic[协议逻辑<br>ReliabilityLayer] end subgraph "Transport Sender (批量发送)" direction LR SenderTask["transport_sender_task"] -- "Transport::send_frames()" --> Network end Router -- "mpsc::send(Frame)" --> EndpointLogic StreamHandle -- "mpsc::send(Data)" --> EndpointLogic EndpointLogic -- "mpsc::send(FrameBatch)" --> SenderTask EndpointLogic -- "mpsc::send(Data)" --> StreamHandle end Network[物理UDP套接字] --> RecvTask style UserApp fill:#333,color:#fff style StreamHandle fill:#333,color:#fff style Network fill:#333,color:#fff
3.3. 用户接口:流式传输 (User-Facing API: Stream Interface)
最终目标是提供一个抽象的、类似 tokio::net::TcpStream
的流式接口。用户不应感知到底层的包、ACK或重传逻辑。他们将通过 Stream
对象上的 AsyncRead
和 AsyncWrite
trait 实现的方法进行连续字节流的读写。库的内部负责将字节流分割成 PUSH
包,并在接收端重新组装成有序的字节流。
3.4. 模块结构风格 (Module Structure Style)
为保持项目结构的清晰和现代化,本项目遵循 Rust 2018 Edition 的模块路径风格:
- 禁止使用
mod.rs
文件。 - 对于一个目录模块(例如
packet
目录),应创建一个同名的packet.rs
文件来声明其子模块。
示例:
src/
├── lib.rs
└── packet/
├── command.rs
└── header.rs
└── packet.rs # 内容为: pub mod command; pub mod header;
4. 其他要求 (Miscellaneous)
- 加密 (Cryptography): 根据明确要求,本项目不包含加密层。所有数据均以明文形式传输。这简化了协议实现、提升了性能,但牺牲了数据机密性和完整性。
- 错误处理: 使用
thiserror
crate 定义详细、有意义的错误类型。 - 依赖管理: 保持最小化的依赖。
- 性能分析: 在后期阶段,使用
tokio-console
或其他工具分析性能瓶颈。