4: 0-RTT 连接与四次挥手
功能描述:
协议实现了高效的连接生命周期管理,包括支持0-RTT/1-RTT的快速连接建立机制,以及标准的四次挥手来确保连接被优雅、可靠地关闭,防止数据丢失。
实现位置:
- 状态机定义:
src/core/endpoint/types/state.rs
- 生命周期管理:
src/core/endpoint/lifecycle/manager.rs
(LifecycleManager
) - 核心事件循环:
src/core/endpoint/core/event_loop.rs
- 顶层协调:
src/socket/event_loop/session_coordinator.rs
(SocketSessionCoordinator
)
1. 0-RTT/1-RTT 连接建立
协议通过帧聚合 (Packet Coalescing) 和 延迟发送 SYN-ACK
的方式,优雅地统一了 0-RTT 和 1-RTT 的连接流程。
sequenceDiagram participant ClientApp participant ClientStack participant ServerStack participant ServerApp ClientApp->>ClientStack: connect(initial_data) ClientStack->>ServerStack: 发送 UDP 包 [SYN, PUSH(initial_data)] ServerStack->>ServerStack: 解码出 SYN 和 PUSH 帧 ServerStack->>ServerApp: accept() 返回 Stream ServerStack->>ServerStack: 将 PUSH 数据放入接收区 ServerApp->>ServerStack: stream.read() ServerApp-->>ServerApp: 获得 initial_data ServerApp->>ServerStack: stream.write(response_data) Note right of ServerStack: 应用层写入触发 SYN-ACK。<br/>对0-RTT数据的确认会<br/>捎带在后续的数据包中。 ServerStack->>ClientStack: 发送 UDP 包 [SYN-ACK, PUSH(response_data)] ClientStack-->>ClientApp: 连接建立, 可读/写
-
0-RTT (客户端有初始数据):
- 客户端: 当用户调用
connect
并提供初始数据时,客户端协议栈会创建一个**SYN
帧**,并将初始数据打包成一个或多个PUSH
帧。这些帧会被**聚合(Coalesce)**到同一个UDP数据报中一次性发出。 - 服务端接收与处理:
SocketSessionCoordinator
收到数据报后,会解码出其中所有的帧(一个SYN
和若干PUSH
)。- 识别到第一个帧是
SYN
后,它会创建一个新的Endpoint
任务(状态为SynReceived
)并向上层返回一个Stream
句柄。 - 关键地,
SocketSessionCoordinator
会立即将SYN
帧之后的所有PUSH
帧转发给这个新创建的Endpoint
。
- 数据立即可用:
Endpoint
在启动后,其接收队列中已经有了0-RTT数据。这些数据被正常处理并放入接收缓冲区,因此服务器应用几乎可以立刻通过stream.read()
读到这份数据,真正实现0-RTT。 - 服务端响应:
- 为了网络效率,服务器在收到0-RTT的
PUSH
帧后,并不会立即回复一个独立的ACK
。 - 当服务器应用调用
stream.write()
发送响应数据时,Endpoint
才会将SYN-ACK
帧和包含响应数据的PUSH
帧聚合在一起发送给客户端。对0-RTT数据的确认信息(更新的期望收包序号recv_next_sequence
)会捎带在这些出站数据包的头部中,从而避免了额外的ACK网络开销。
- 为了网络效率,服务器在收到0-RTT的
- 客户端: 当用户调用
-
1-RTT (客户端无初始数据):
- 流程简化:客户端只发送一个单独的
SYN
帧。服务器收到后,创建Endpoint
并返回Stream
。当服务器应用调用stream.write()
时,会发送SYN-ACK
(可能聚合了数据),完成握手。
- 流程简化:客户端只发送一个单独的
这种设计确保了 SYN-ACK
的发送总是与服务器的实际就绪状态同步,提高了效率。
2. 四次挥手关闭
协议实现了标准的四次挥手,以确保双方的数据都能被完整发送和确认。
sequenceDiagram participant A as Endpoint A participant B as Endpoint B A->>B: FIN Note over A: 进入 Closing 状态 B->>A: ACK (for FIN) Note over B: 知道A已关闭写, 进入 FinWait Note over B: B 继续发送剩余数据... B->>A: PUSH A->>B: ACK (for PUSH) Note over B: B 数据发送完毕 B->>A: FIN A->>B: ACK (for FIN) Note over A,B: 连接完全关闭
-
发起方 (A):
- 当用户调用
stream.close()
,Endpoint
A 的LifecycleManager
将其状态转换为Closing
。 - 它会发送一个
FIN
包给B,然后停止接受新的用户数据,但会继续处理已发送数据的ACK。
- 当用户调用
-
响应方 (B):
Endpoint
B 收到FIN
后,立即回复一个ACK
。- B 的
LifecycleManager
将其状态转换为FinWait
,这意味着它知道A不会再发送任何新数据了。此时,B的应用层在调用stream.read()
时会得到Ok(0)
(EOF)。 - B 仍然可以继续发送它缓冲区里尚未发送完毕的数据。
-
完成关闭:
- 当 B 发送完所有数据并关闭其写入流后,它也会发送一个
FIN
包给 A。 - A 收到 B 的
FIN
后,回复最后一个ACK
。 - 此时,双方都确认对方已经关闭,连接被完全拆除,
Endpoint
任务终止。
- 当 B 发送完所有数据并关闭其写入流后,它也会发送一个
这个过程由 src/core/endpoint/types/state.rs
中定义的 Connecting
, Established
, Closing
, FinWait
, Closed
状态机精确驱动。