Files
qiming/qimingclaw/specs/agent-client/vendorsdoc/qiming-rustdesk.md

21 KiB
Raw Permalink Blame History

qiming-rustdesk

项目概述

RustDesk 远程桌面客户端的定制版本支持远程控制、文件传输、音视频服务、剪贴板同步等功能。作为本项目的核心通信库提供双向通信、P2P 直连和中继传输能力。

本地路径: vendors/qiming-rustdesk

目录结构

qiming-rustdesk/
├── src/
│   ├── lib.rs                      # 库入口导出公共API
│   ├── client.rs                   # 客户端连接处理核心模块
│   ├── server.rs                   # 服务端音视频/剪贴板/输入服务
│   ├── common.rs                   # 公共工具函数和类型定义
│   ├── rendezvous_mediator.rs      # 远程连接中介(核心)
│   ├── ipc.rs                      # 进程间通信
│   └── platform/                   # 平台特定代码
│       ├── linux/
│       ├── macos/
│       └── windows/
├── libs/
│   ├── hbb_common/                 # 通用工具库(最重要)
│   │   ├── src/
│   │   │   ├── lib.rs
│   │   │   ├── message_proto/      # protobuf 消息定义
│   │   │   ├── net/                # 网络相关TCP/KCP/WebSocket
│   │   │   ├── crypto/             # 加密模块
│   │   │   ├── config/             # 配置管理
│   │   │   └── util/               # 工具函数
│   │   └── Cargo.toml
│   ├── scrap/                      # 屏幕捕获库
│   ├── enigo/                      # 键盘鼠标模拟库
│   ├── clipboard/                  # 剪贴板库
│   └── virtual_display/            # 虚拟显示驱动
├── proto/                          # protobuf 协议定义
└── Cargo.toml                      # workspace 配置

核心通信架构

整体架构

┌─────────────────────────────────────────────────────────────────┐
│                       agent-client                               │
│                                                                 │
│  ┌─────────────────┐     ┌─────────────────────────────────┐   │
│  │  UI 界面        │     │  核心通信模块                    │   │
│  │  - 聊天窗口     │     │  - rendezvous_mediator (连接管理)│   │
│  │  - 设置界面     │     │  - client (数据收发)             │   │
│  │  - 依赖管理     │     │  - server (音视频服务)           │   │
│  └─────────────────┘     └─────────────────────────────────┘   │
│                                │                                 │
└────────────────────────────────┼────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                    data-server (rustdesk-server)                 │
│                                                                 │
│  ┌─────────────────────┐    ┌─────────────────────┐            │
│  │       hbbs          │    │       hbbr          │            │
│  │   (信令服务器)      │    │   (中继服务器)       │            │
│  │   - 端口: 21116     │    │   - 端口: 21117     │            │
│  │   - ID 注册        │    │   - 数据中继        │            │
│  │   - 连接协商       │    │   - 带宽限制        │            │
│  │   - NAT 穿透       │    │                     │            │
│  └─────────────────────┘    └─────────────────────┘            │
└─────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                  agent-server-admin (管理端)                      │
└─────────────────────────────────────────────────────────────────┘

连接管理 (rendezvous_mediator.rs)

核心结构体

// src/rendezvous_mediator.rs

pub struct RendezvousMediator {
    addr: TargetAddr<'static>,
    host: String,
    host_prefix: String,
    keep_alive: i32,
}

impl RendezvousMediator {
    /// 启动连接管理器(根据配置选择 UDP 或 TCP 模式)
    pub async fn start(server: ServerPtr, host: String) -> ResultType<()> {
        log::info!("start rendezvous mediator of {}", host);

        // 根据配置选择通信模式
        if cfg!(debug_assertions) && option_env!("TEST_TCP").is_some()
            || Config::is_proxy()
            || use_ws()
            || crate::is_udp_disabled()
        {
            // WebSocket/代理模式
            Self::start_tcp(server, host).await
        } else {
            // UDP 直连模式
            Self::start_udp(server, host).await
        }
    }
}

双协议支持

// UDP 直连模式(优先使用,性能更好)
async fn start_udp(server: ServerPtr, host: String) -> ResultType<()> {
    // 注册超时管理
    const MIN_REG_TIMEOUT: i64 = 3_000;
    const MAX_REG_TIMEOUT: i64 = 30_000;
    let mut reg_timeout = MIN_REG_TIMEOUT;

    loop {
        // 心跳延迟计算EMA 指数移动平均)
        if ema_latency == 0 {
            ema_latency = latency;
        } else {
            ema_latency = latency / 30 + (ema_latency * 29 / 30);
        }

        // 动态调整注册间隔
        let mut n = latency / 5;
        if n < 3000 { n = 3000; }

        // 心跳保活
        if !send_heartbeat(...).await {
            reg_timeout = (reg_timeout * 2).min(MAX_REG_TIMEOUT);
        }
    }
}

// TCP/WebSocket 模式(代理环境或 UDP 被禁用时)
async fn start_tcp(server: ServerPtr用于, host: String) -> ResultType<()> {
    // 建立 TCP 连接
    let mut socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?;

    // 发送注册消息
    let mut msg_out = Message::new();
    let mut rr = RegisterRelay {
        socket_addr: socket_addr.into(),
        version: crate::VERSION.to_owned(),
        ..Default::default()
    };
    msg_out.set_register_relay(rr);
    socket.send(&msg_out).await?;

    // 处理中继响应
    if let Some(msg) = socket.next().await {
        let response = msg?.payload;
        // 建立中继连接...
    }
}

P2P 直连流程 (打洞)

打洞请求处理

// src/rendezvous_mediator.rs

async fn handle_punch_hole(&self, ph: PunchHole, server: ServerPtr) -> ResultType<()> {
    // 1. 解析对端地址
    let mut peer_addr = AddrMangle::decode(&ph.socket_addr);

    // 2. 去重处理100ms 内重复消息忽略)
    let last = *LAST_MSG.lock().await;
    if last.0 == peer_addr && last.1.elapsed().as_millis() < 100 {
        return Ok(());  // 忽略重复消息
    }

    // 3. 判断是否需要中继
    let relay = use_ws() || Config::is_proxy() || ph.force_relay;
    let nat_type = NatType::from_i32(Config::get_nat_type())?;

    // 对称型 NAT 或强制中继时降级到中继模式
    if nat_type == NatType::SYMMETRIC || relay || (config::is_disable_tcp_listen() && ph.udp_port <= 0) {
        return self.create_relay(...).await;
    }

    // 4. UDP 打洞
    if ph.udp_port > 0 {
        peer_addr.set_port(ph.udp_port as u16);
        return self.punch_udp_hole(peer_addr, server, msg_punch, control_permissions).await;
    }

    // 5. TCP 打洞
    let mut socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?;
    allow_err!(socket_client::connect_tcp_local(peer_addr, Some(local_addr), 30).await);
}

NAT 类型与连接策略

// NAT 类型决定连接策略
enum NatType {
    NO_NAT,         // 无 NAT优先 P2P 直连
    FULL_CONE,      // 完全锥形 NAT优先 P2P 直连
    RESTRICTED,     // 限制锥形 NAT可能需要中继
    SYMMETRIC,      // 对称型 NAT必须中继
}

// 连接策略选择
fn select_connection_strategy(nat_type: NatType, force_relay: bool) -> ConnectionMode {
    if force_relay || nat_type == NatType::SYMMETRIC {
        ConnectionMode::Relay  // 必须中继
    } else if nat_type == NatType::RESTRICTED {
        ConnectionMode::TryP2PThenRelay  // 尝试 P2P失败则中继
    } else {
        ConnectionMode::P2P  // 优先 P2P
    }
}

中继模式 (Relay)

创建中继连接

// src/rendezvous_mediator.rs

async fn create_relay(
    &self,
    socket_addr: Vec<u8>,
    relay_server: String,
    uuid: String,
    server: ServerPtr,
    secure: bool,
    initiate: bool,
    socket_addr_v6: bytes::Bytes,
    control_permissions: Option<ControlPermissions>,
) -> ResultType<()> {
    // 1. 建立到 rendezvous server 的 TCP 连接
    let mut socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?;

    // 2. 发送中继请求
    let mut msg_out = Message::new();
    let mut rr = RelayResponse {
        socket_addr: socket_addr.into(),
        version: crate::VERSION.to_owned(),
        socket_addr_v6,
        ..Default::default()
    };
    msg_out.set_relay_response(rr);
    socket.send(&msg_out).await?;

    // 3. 建立 relay 连接
    crate::create_relay_connection(
        server,
        relay_server,
        uuid,
        peer_addr,
        secure,
        is_ipv4(&self.addr),
        control_permissions,
    ).await;

    Ok(())
}

心跳保活机制

// 心跳机制确保连接活跃
async fn send_heartbeat(&self, socket: &mut impl FramedWrite) -> bool {
    let mut msg = Message::new();
    msg.set_ping(Ping {
        timestamp: get_time().await,
        ..Default::default()
    });

    if let Err(e) = socket.send(&msg).await {
        log::error!("Heartbeat failed: {}", e);
        return false;
    }
    true
}

// 心跳超时检测
fn check_heartbeat_timeout(last_heartbeat: Option<DateTime<Utc>>, timeout_secs: i64) -> bool {
    if let Some(timestamp) = last_heartbeat {
        let elapsed = Utc::now() - timestamp;
        elapsed.num_seconds() > timeout_secs
    } else {
        false
    }
}

Protocol Buffers 消息定义

主消息类型

// libs/hbb_common/src/message_proto/mod.rs

// 主消息类型oneof 模式支持多种消息)
message Message {
  int32 id = 1;                    // 消息 ID
  MessageType type = 2;            // 消息类型
  oneof payload {
    LoginRequest login_request = 3;
    LoginResponse login_response = 4;
    AudioFrame audio_frame = 5;
    VideoFrame video_frame = 6;
    InputEvent input_event = 7;
    ClipboardData clipboard_data = 8;
    FileTransfer file_transfer = 9;
    CloseReason close_reason = 10;
    Ping ping = 11;
    Pong pong = 12;
    PunchHole punch_hole = 13;     // 打洞消息
    RelayResponse relay_response = 14;  // 中继响应
  }
}

enum MessageType {
  NONE = 0;
  LOGIN_REQUEST = 1;
  LOGIN_RESPONSE = 2;
  AUDIO_FRAME = 3;
  VIDEO_FRAME = 4;
  INPUT_EVENT = 5;
  CLIPBOARD = 6;
  FILE_TRANSFER = 7;
  CLOSE = 8;
  PING = 9;
  PONG = 10;
  PUNCH_HOLE = 11;
  RELAY_RESPONSE = 12;
}

打洞消息

message PunchHole {
  string peer_id = 1;              // 对端 ID
  bytes socket_addr = 2;           // 序列化地址
  int32 socket_addr_version = 3;   // 地址版本
  int32 peer_port = 4;             // 对端端口
  bool force_relay = 5;            // 强制中继
  int32 nat_type = 6;              // NAT 类型
  bytes local_addr = 7;            // 本地地址
  int32 local_port = 8;            // 本地端口
  int32 udp_port = 9;              // UDP 端口
  int32 conn_type = 10;            // 连接类型
  bytes socket_addr_v6 = 11;       // IPv6 地址
  bytes local_addr_v6 = 12;        // IPv6 本地地址
}

文件传输(支持分片和断点续传)

message FileTransfer {
  string id = 1;                   // 传输 ID
  FileTransferAction action = 2;
  bytes data = 3;                  // 文件数据
  int64 offset = 4;                // 文件偏移(支持断点续传)
  bool compressed = 5;             // 是否压缩
}

enum FileTransferAction {
  FILE_TRANSFER_NONE = 0;
  FILE_TRANSFER_REQUEST = 1;       // 请求传输
  FILE_TRANSFER_RESPONSE = 2;      // 响应
  FILE_TRANSFER_DATA = 3;          // 数据块
  FILE_TRANSFER_CANCEL = 4;        // 取消
  FILE_TRANSFER_DONE = 5;          // 完成
}

// 文件元数据
message FileMeta {
  string name = 1;
  int64 size = 2;
  int64 modified = 3;
  bool is_directory = 4;
}

// 文件块
message FileChunk {
  int32 id = 1;
  int32 file_num = 2;
  bytes data = 3;
  bool compressed = 4;
  int64 offset = 5;
}

输入事件

message InputEvent {
  oneof event {
    KeyEvent key = 1;
    MouseEvent mouse = 2;
    WheelEvent wheel = 3;
    ClipboardEvent clipboard = 4;
  }
}

message KeyEvent {
  Direction direction = 1;
  int32 code = 2;                  // 扫描码
  int32 key = 3;                   // 虚拟键码
  bool scan = 4;
  bool raw = 5;
}

message MouseEvent {
  Direction direction = 1;
  int32 button = 2;
  int32 x = 3;
  int32 y = 4;
}

message WheelEvent {
  Axis axis = 1;
  int32 delta = 2;
}

enum Direction {
  DOWN = 0;
  UP = 1;
}

enum Axis {
  HORIZONTAL = 0;
  VERTICAL = 1;
}

在本项目中的使用

1. 客户端连接管理

// agent-client/src/connection/mod.rs

use hbb_common::{rendezvous_proto::*, ResultType};
use std::sync::Arc;

pub struct ConnectionManager {
    mediator: Arc<RendezvousMediator>,
    state: ConnectionState,
}

pub enum ConnectionState {
    Disconnected,
    Connecting,
    Connected(PeerConnection),
    Relay(RelayConnection),
}

impl ConnectionManager {
    /// 连接到远程客户端
    pub async fn connect(
        &self,
        peer_id: &str,
        key: &str,
    ) -> ResultType<PeerConnection> {
        // 1. 从 hbbs 获取对端信息
        let peer_info = self.mediator.get_peer_info(peer_id).await?;

        // 2. 尝试 P2P 直连
        if let Some(conn) = self.try_p2p_connect(&peer_info, key).await {
            return Ok(conn);
        }

        // 3. P2P 失败,降级到中继
        self.connect_via_relay(&peer_info, key).await
    }

    /// 监听远程连接
    pub async fn listen(&self, id: &str, key: &str) -> ResultType<()> {
        self.mediator.start(id, key).await
    }
}

2. 消息收发

// agent-client/src/connection/message.rs

use hbb_common::message_proto::{Message, MessageType};

pub struct MessageChannel {
    stream: Box<dyn FramedStream>,
}

impl MessageChannel {
    /// 发送消息
    pub async fn send(&mut self, msg: &Message) -> ResultType<()> {
        self.stream.send(msg).await?;
        Ok(())
    }

    /// 接收消息
    pub async fn recv(&mut self) -> ResultType<Option<Message>> {
        match self.stream.next().await {
            Some(Ok(msg)) => Ok(Some(msg)),
            Some(Err(e)) => Err(e.into()),
            None => Ok(None),
        }
    }

    /// 发送聊天消息
    pub async fn send_chat(&mut self, content: &str) -> ResultType<()> {
        let msg = Message {
            id: generate_id(),
            type_: MessageType::MESSAGE_CHAT as i32,
            payload: Some(ChatMessage {
                content: content.to_string(),
                timestamp: get_timestamp(),
            }.into()),
            ..Default::default()
        };
        self.send(&msg).await
    }
}

3. 连接状态指示

// agent-client/src/ui/status_bar.rs

use hbb_common::rendezvous_proto::PunchHole;

pub struct ConnectionIndicator {
    state: ConnectionState,
    last_heartbeat: Option<DateTime<Utc>>,
}

impl Render for ConnectionIndicator {
    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
        let status = match self.state {
            ConnectionState::Connected => Status::Online,
            ConnectionState::Relay => Status::Relay,  // 中继模式
            ConnectionState::Connecting => Status::Connecting,
            ConnectionState::Disconnected => Status::Offline,
        };

        let color = match status {
            Status::Online => cx.theme().colors.success,
            Status::Relay => cx.theme().colors.warning,
            Status::Connecting => cx.theme().colors.info,
            Status::Offline => cx.theme().colors.error,
        };

        div()
            .flex()
            .items_center()
            .gap_2()
            .child(
                div()
                    .w_3()
                    .h_3()
                    .rounded_full()
                    .bg(color)
            )
            .child(match status {
                Status::Online => "已连接 (P2P)",
                Status::Relay => "已连接 (中继)",
                Status::Connecting => "连接中...",
                Status::Offline => "未连接",
            })
    }
}

文件传输功能

文件分片传输

// agent-client/src/connection/file_transfer.rs

use hbb_common::message_proto::{FileTransfer, FileTransferAction};

const CHUNK_SIZE: usize = 64 * 1024;  // 64KB 分片

pub struct FileTransferManager {
    upload_jobs: HashMap<String, UploadJob>,
    download_jobs: HashMap<String, DownloadJob>,
}

pub struct UploadJob {
    file: File,
    path: PathBuf,
    offset: i64,
    peer_id: String,
}

impl FileTransferManager {
    /// 上传文件(支持断点续传)
    pub async fn upload(&mut self, path: &Path, peer_id: &str) -> ResultType<()> {
        let file = File::open(path).await?;
        let metadata = file.metadata().await?;
        let file_name = path.file_name().unwrap().to_string_lossy();

        // 检查断点续传
        let offset = self.get_checkpoint(&peer_id, &file_name).unwrap_or(0);

        let job = UploadJob {
            file,
            path: path.to_path_buf(),
            offset,
            peer_id: peer_id.to_string(),
        };

        // 开始分片传输
        self.upload_with_offset(job).await
    }

    async fn upload_with_offset(&mut self, mut job: UploadJob) -> ResultType<()> {
        job.file.seek(SeekFrom::Start(job.offset as u64)).await?;

        let mut buffer = vec![0u8; CHUNK_SIZE];
        let mut offset = job.offset;

        while let Ok(n) = job.file.read(&mut buffer).await {
            if n == 0 { break; }

            // 发送分片
            let chunk = FileTransfer {
                id: job.peer_id.clone(),
                action: FileTransferAction::FILE_TRANSFER_DATA as i32,
                data: buffer[..n].to_vec(),
                offset,
                compressed: false,
            };

            self.send_chunk(&chunk).await?;

            // 保存断点
            self.save_checkpoint(&job.peer_id, &job.path, offset);

            offset += n as i64;
        }

        // 传输完成
        self.send_done(&job.peer_id).await?;

        Ok(())
    }
}

可复用代码

核心复用模块

模块 路径 用途
hbb_common libs/hbb_common/ 消息编解码、网络工具、配置管理
scrap libs/scrap/ 屏幕捕获、视频编码
enigo libs/enigo/ 键盘鼠标输入模拟
clipboard libs/clipboard/ 跨平台剪贴板同步

推荐集成方式

[dependencies]
hbb_common = { path = "vendors/qiming-rustdesk/libs/hbb_common" }

关键设计模式

  1. 双协议支持UDP 直连优先,失败降级 TCP/WebSocket 中继
  2. NAT 打洞:根据 NAT 类型自动选择连接策略
  3. 心跳保活EMA 动态调整心跳间隔
  4. 断点续传:支持文件传输中断后继续
  5. 分片传输:大文件分块传输,提高可靠性

与本项目的集成点

功能 使用模块 说明
双向通信 hbb_common::message_proto 消息编解码
P2P 直连 rendezvous_mediator NAT 穿透
中继传输 relay_connection 数据中转
文件传输 file_transfer 分片传输
输入模拟 enigo 远程控制
屏幕捕获 scrap 远程桌面