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里面。

image

 

第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 内部做的事:

  1. 从 HTTP/2 接收缓冲区读取一个 DATA frame
  2. 解析 5 字节 gRPC 帧头(compressed flag + length)
  3. 反序列化 protobuf 为 DownloadTaskResponse
  4. 返回 Ok(Some(message))
  5. 如果读到 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)在另一端拿数据。
 
 
 
 
posted on 2026-10-15 15:45  月影缤纷  阅读(2)  评论(0)    收藏  举报