# 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) ### 核心结构体 ```rust // 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 } } } ``` ### 双协议支持 ```rust // 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 直连流程 (打洞) ### 打洞请求处理 ```rust // 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 类型与连接策略 ```rust // 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) ### 创建中继连接 ```rust // src/rendezvous_mediator.rs async fn create_relay( &self, socket_addr: Vec, relay_server: String, uuid: String, server: ServerPtr, secure: bool, initiate: bool, socket_addr_v6: bytes::Bytes, control_permissions: Option, ) -> 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(()) } ``` ### 心跳保活机制 ```rust // 心跳机制确保连接活跃 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>, timeout_secs: i64) -> bool { if let Some(timestamp) = last_heartbeat { let elapsed = Utc::now() - timestamp; elapsed.num_seconds() > timeout_secs } else { false } } ``` ## Protocol Buffers 消息定义 ### 主消息类型 ```protobuf // 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; } ``` ### 打洞消息 ```protobuf 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 本地地址 } ``` ### 文件传输(支持分片和断点续传) ```protobuf 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; } ``` ### 输入事件 ```protobuf 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. 客户端连接管理 ```rust // agent-client/src/connection/mod.rs use hbb_common::{rendezvous_proto::*, ResultType}; use std::sync::Arc; pub struct ConnectionManager { mediator: Arc, state: ConnectionState, } pub enum ConnectionState { Disconnected, Connecting, Connected(PeerConnection), Relay(RelayConnection), } impl ConnectionManager { /// 连接到远程客户端 pub async fn connect( &self, peer_id: &str, key: &str, ) -> ResultType { // 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. 消息收发 ```rust // agent-client/src/connection/message.rs use hbb_common::message_proto::{Message, MessageType}; pub struct MessageChannel { stream: Box, } impl MessageChannel { /// 发送消息 pub async fn send(&mut self, msg: &Message) -> ResultType<()> { self.stream.send(msg).await?; Ok(()) } /// 接收消息 pub async fn recv(&mut self) -> ResultType> { 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. 连接状态指示 ```rust // agent-client/src/ui/status_bar.rs use hbb_common::rendezvous_proto::PunchHole; pub struct ConnectionIndicator { state: ConnectionState, last_heartbeat: Option>, } impl Render for ConnectionIndicator { fn render(&mut self, cx: &mut ViewContext) -> 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 => "未连接", }) } } ``` ## 文件传输功能 ### 文件分片传输 ```rust // 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, download_jobs: HashMap, } 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/` | 跨平台剪贴板同步 | ### 推荐集成方式 ```toml [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` | 远程桌面 |