Dragonfly p2p下载镜像服务端流式RPC
proxy_via_daemon 方法(4001接口)通过grpc请求调用"/dfdaemon.v2.DfdaemonDownload/DownloadTask", 构建服务端流式RPC响应链。
download_task方法(task下载接口)每个Piece下载完成后,发送DownloadTaskResponse(带piece_id),通过mpsc channel,传递给out_stream。
proxy_via_daemon 方法实时获取到DownloadTaskResponse,打开对应task文件,读取响应piece的字节流,写入返回给Containerd的字节流writer里面。

第1层:Server 端 mpsc channel(进程内)
发送端(dfdaemon_download.rs:444):
let (out_stream_tx, out_stream_rx) = mpsc::channel(4);
写入数据(task.rs:1138-1153,每个 piece 完成时):
download_progress_tx // 就是 out_stream_tx 的 clone
.send_timeout(
Ok(DownloadTaskResponse {
host_id: host_id.to_string(),
task_id: task_id.to_string(),
peer_id: peer_id.to_string(),
response: Some(download_task_response::Response::DownloadPieceFinishedResponse(
DownloadPieceFinishedResponse { piece: Some(response_piece) },
)),
}),
REQUEST_TIMEOUT,
)
.await
接收端包装为 tonic Streaming(dfdaemon_download.rs:710):
Ok(Response::new(ReceiverStream::new(out_stream_rx)))
ReceiverStream 实现了 Stream<Item = Result<DownloadTaskResponse, Status>>,tonic 逐条从 channel 取消息,序列化为 protobuf,写入 HTTP/2 DATA frame。
第2层:Unix Domain Socket 上的 HTTP/2 流
Client 发送:
POST /dfdaemon.v2.DfdaemonDownload/DownloadTask
Content-Type: application/grpc
┌─ DATA frame: DownloadTaskRequest (protobuf编码) ─┐
Server 返回 (Server Streaming):
┌─ DATA frame: DownloadTaskResponse #1 (Header + Protobuf消息) ─┐
├─ DATA frame: DownloadTaskResponse #2 ─┤
├─ DATA frame: DownloadTaskResponse #3 ─┤
├─ ... ─┤
└─ DATA frame: trailers (grpc-status: 0) ─┘
每个 DATA frame 格式:
[1 byte compressed flag][4 bytes message length][N bytes protobuf data]
第3层:Client 端 Streaming(proxy 进程内)
Client 调用(dfdaemon_download.rs:1948-1951):
pub async fn download_task(
&self,
request: DownloadTaskRequest,
) -> ClientResult<tonic::Response<tonic::codec::Streaming<DownloadTaskResponse>>> {
// ...
self.client.download_task(request).await
}
返回 Streaming<DownloadTaskResponse> — tonic 的流式解码器,内部持有 HTTP/2 stream 的接收缓冲区。
Proxy 消费(proxy/mod.rs:757, 841):
// 第757行: 拿到 Streaming
let mut out_stream = response.into_inner();
// 第841行: 逐条取消息
match out_stream.message().await {
Ok(Some(message)) => { /* 一条 DownloadTaskResponse */ }
Ok(None) => { /* stream 结束 */ }
Err(err) => { /* stream 出错 */ }
}
message().await 内部做的事:
- 从 HTTP/2 接收缓冲区读取一个 DATA frame
- 解析 5 字节 gRPC 帧头(compressed flag + length)
- 反序列化 protobuf 为
DownloadTaskResponse - 返回
Ok(Some(message)) - 如果读到 trailers(stream 结束),返回
Ok(None)
完整时序
task.rs dfdaemon_download.rs UDS/HTTP2 proxy/mod.rs
───── ──────────────────── ──────── ────────────
piece 2 下载完成
│
├─send_timeout(Resp#2)────────►out_stream_tx
│ │
│ ├─channel(4)─►out_stream_rx
│ │ │
│ │ ReceiverStream
│ │ │
│ │ tonic编码protobuf
│ │ │
│ │ 写入HTTP/2 DATA frame
│ │ │
│ │ ├────UDS────►
│ │ │ tonic解码
│ │ │ │
│ │ │ Streaming.message()
│ │ │ │
│ │ │ ◄──Ok(Some(Resp#2))
│ │ │ │
│ │ │ download_from_local
│ │ │ → File::open(task文件)
│ │ │ → reader存入HashMap
Rust mpsc channel
它的全称是 Multiple Producer, Single Consumer(多生产者,单消费者)通道。
简单来说,它就像是一条单向传送带:允许多个发送者(Producer)往传送带上放数据,但只允许一个接收者(Consumer)在另一端拿数据。
浙公网安备 33010602011771号