RustDesk Server源码分析:Rust语言下的高性能P2P实现
引言:P2P远程桌面的技术挑战与Rust解决方案
你是否还在为企业远程办公的网络延迟和带宽成本困扰?是否在寻找一种既能保障数据安全又能实现高性能远程控制的解决方案?RustDesk作为一款开源远程桌面软件,其服务器端(RustDesk Server)采用Rust语言实现了高效的P2P(Peer-to-Peer,对等网络)通信架构,完美解决了传统远程控制软件依赖中心化服务器的性能瓶颈问题。
本文将深入剖析RustDesk Server的源代码,重点解读其如何利用Rust语言的内存安全特性和异步编程模型,构建高性能、低延迟的P2P通信系统。通过本文,你将获得以下核心知识点:
- RustDesk Server的模块化架构设计与核心组件功能
- 基于UDP/TCP混合传输的P2P打洞(Hole Punching)实现原理
- 分布式节点发现与NAT穿透的关键技术细节
- Rust异步运行时(Tokio)在高并发网络处理中的应用
- 带宽控制与流量管理的优化策略
一、项目架构概览:RustDesk Server的核心组件
RustDesk Server采用模块化设计,主要包含两大核心服务和多个功能模块。通过分析项目源码结构,我们可以清晰地看到其架构分层:
src/
├── common.rs // 通用工具函数与常量定义
├── database.rs // SQLite数据库交互模块
├── hbbr.rs // 中继服务器(Relay Server)实现
├── lib.rs // 库函数入口
├── main.rs // 主程序入口与参数解析
├── peer.rs // 对等节点(Peer)管理
├── relay_server.rs // 中继服务核心逻辑
├── rendezvous_server.rs // rendezvous(节点发现)服务
└── utils.rs // 辅助工具函数
1.1 核心服务组件
RustDesk Server包含两个关键服务进程:
- hbbs:Rendezvous Server(节点发现服务器),负责对等节点的注册、发现和NAT穿透协调
- hbbr:Relay Server(中继服务器),在P2P连接失败时提供数据中继服务
这种分离设计允许系统根据网络环境灵活部署,在理想网络条件下可完全通过P2P通信,仅在必要时使用中继服务,从而最大化性能并降低带宽成本。
1.2 架构流程图
二、Rendezvous Server:P2P连接的核心协调者
rendezvous_server.rs是整个系统的核心,实现了节点发现和NAT穿透的关键逻辑。Rendezvous Server基于UDP协议工作,负责管理对等节点的生命周期和协调P2P连接建立。
2.1 服务启动流程
Rendezvous Server的启动过程在RendezvousServer::start方法中实现,主要包含以下步骤:
- 解析命令行参数和配置
- 创建UDP监听套接字和TCP监听(用于Websocket和备用连接)
- 初始化数据库连接(SQLite)
- 启动主I/O事件循环
关键代码片段:
pub async fn start(port: i32, serial: i32, key: &str, rmem: usize) -> ResultType<()> {
let (key, sk) = Self::get_server_sk(key);
let nat_port = port - 1;
let ws_port = port + 2;
let pm = PeerMap::new().await?;
log::info!("serial={}", serial);
let rendezvous_servers = get_servers(&get_arg("rendezvous-servers"), "rendezvous-servers");
log::info!("Listening on tcp/udp :{}", port);
log::info!("Listening on tcp :{}, extra port for NAT test", nat_port);
log::info!("Listening on websocket :{}", ws_port);
// 创建UDP监听
let mut socket = create_udp_listener(port, rmem).await?;
// 创建TCP监听
let mut listener = create_tcp_listener(port).await?;
let mut listener2 = create_tcp_listener(nat_port).await?;
let mut listener3 = create_tcp_listener(ws_port).await?;
// 启动主I/O循环
let main_task = async move {
loop {
log::info!("Start");
match rs
.io_loop(
&mut rx,
&mut listener,
&mut listener2,
&mut listener3,
&mut socket,
&key,
)
.await
{
LoopFailure::UdpSocket => {
drop(socket);
socket = create_udp_listener(port, rmem).await?;
}
// 处理其他可能的连接失败...
}
};
};
// 等待信号或任务完成
tokio::select! {
res = main_task => res,
res = listen_signal => res,
}
}
2.2 对等节点注册与发现机制
节点注册是P2P通信的第一步。当客户端启动时,会向Rendezvous Server发送注册请求,服务器将客户端的ID、公钥和网络地址等信息存储在内存和数据库中。
// 处理UDP注册请求
async fn handle_udp(
&mut self,
bytes: &BytesMut,
addr: SocketAddr,
socket: &mut FramedSocket,
key: &str,
) -> ResultType<()> {
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(bytes) {
match msg_in.union {
Some(rendezvous_message::Union::RegisterPeer(rp)) => {
// 新节点注册
if !rp.id.is_empty() {
log::trace!("New peer registered: {:?} {:?}", &rp.id, &addr);
self.update_addr(rp.id, addr, socket).await?;
// 版本检查与配置更新
if self.inner.serial > rp.serial {
let mut msg_out = RendezvousMessage::new();
msg_out.set_configure_update(ConfigUpdate {
serial: self.inner.serial,
rendezvous_servers: (*self.rendezvous_servers).clone(),
..Default::default()
});
socket.send(&msg_out, addr).await?;
}
}
}
// 处理其他消息类型...
}
}
Ok(())
}
2.3 NAT穿透与P2P打洞实现
NAT(Network Address Translation,网络地址转换)穿透是P2P通信的核心挑战。RustDesk Server实现了多种NAT穿透技术,包括UDP打洞、STUN协议等,以应对不同网络环境。
// 处理打洞请求
async fn handle_punch_hole_request(
&mut self,
addr: SocketAddr,
ph: PunchHoleRequest,
key: &str,
ws: bool,
) -> ResultType<(RendezvousMessage, Option<SocketAddr>)> {
// 检查许可证密钥
if !key.is_empty() && ph.licence_key != key {
let mut msg_out = RendezvousMessage::new();
msg_out.set_punch_hole_response(PunchHoleResponse {
failure: punch_hole_response::Failure::LICENSE_MISMATCH.into(),
..Default::default()
});
return Ok((msg_out, None));
}
let id = ph.id.clone();
// 检查目标节点是否在线
if let Some(peer) = self.pm.get(&id).await {
let (elapsed, peer_addr) = {
let r = peer.read().await;
(r.last_reg_time.elapsed().as_millis() as i32, r.socket_addr)
};
// 检查节点是否离线
if elapsed >= REG_TIMEOUT {
let mut msg_out = RendezvousMessage::new();
msg_out.set_punch_hole_response(PunchHoleResponse {
failure: punch_hole_response::Failure::OFFLINE.into(),
..Default::default()
});
return Ok((msg_out, None));
}
// 检查是否需要中继
let peer_is_lan = self.is_lan(peer_addr);
let is_lan = self.is_lan(addr);
let mut relay_server = self.get_relay_server(addr.ip(), peer_addr.ip());
// 如果需要强制中继或网络类型不匹配,则使用中继
if ALWAYS_USE_RELAY.load(Ordering::SeqCst) || (peer_is_lan ^ is_lan) {
if peer_is_lan {
// 使用本地IP作为中继服务器
relay_server = self.inner.local_ip.clone()
}
ph.nat_type = NatType::SYMMETRIC.into(); // 强制使用中继
}
// 检查是否在同一局域网
let same_intranet: bool = !ws
&& (peer_is_lan && is_lan || {
match (peer_addr, addr) {
(SocketAddr::V4(a), SocketAddr::V4(b)) => a.ip() == b.ip(),
(SocketAddr::V6(a), SocketAddr::V6(b)) => a.ip() == b.ip(),
_ => false,
}
});
// 构建打洞消息
let socket_addr = AddrMangle::encode(addr).into();
if same_intranet {
// 同一局域网,请求本地地址
log::debug!(
"Fetch local addr {:?} {:?} request from {:?}",
id,
peer_addr,
addr
);
msg_out.set_fetch_local_addr(FetchLocalAddr {
socket_addr,
relay_server,
..Default::default()
});
} else {
// 不同网络,发送打洞请求
log::debug!(
"Punch hole request to {:?} from {:?}",
&id,
&addr
);
msg_out.set_punch_hole_request(PunchHoleRequest {
socket_addr,
relay_server,
..ph
});
}
Ok((msg_out, Some(peer_addr)))
} else {
// 目标节点不存在
let mut msg_out = RendezvousMessage::new();
msg_out.set_punch_hole_response(PunchHoleResponse {
failure: punch_hole_response::Failure::NOT_FOUND.into(),
..Default::default()
});
Ok((msg_out, None))
}
}
三、Relay Server:P2P通信的可靠后备
尽管P2P是理想的通信方式,但在复杂网络环境下(如对称NAT),直接通信可能失败。此时,Relay Server(hbbr)作为后备机制,提供数据中转服务。
3.1 中继服务核心逻辑
Relay Server的主要功能是在无法建立直接P2P连接时,在两个客户端之间转发数据。其核心实现在relay_server.rs中:
// 中继服务主循环
async fn relay(
addr: SocketAddr,
stream: &mut impl StreamTrait,
peer: &mut Box<dyn StreamTrait>,
total_limiter: Limiter,
id: String,
) -> ResultType<()> {
let ip = addr.ip().to_string();
let mut tm = std::time::Instant::now();
let mut elapsed = 0;
let mut total = 0;
let mut total_s = 0;
let mut highest_s = 0;
let mut downgrade: bool = false;
let mut blacked: bool = false;
// 带宽限制参数
let sb = SINGLE_BANDWIDTH.load(Ordering::SeqCst) as f64;
let limiter = <Limiter>::new(sb);
let blacklist_limiter = <Limiter>::new(LIMIT_SPEED.load(Ordering::SeqCst) as _);
let downgrade_threshold =
(sb * DOWNGRADE_THRESHOLD_100.load(Ordering::SeqCst) as f64 / 100. / 1000.) as usize;
// 定时器,用于监控连接状态和流量
let mut timer = interval(Duration::from_secs(3));
let mut last_recv_time = std::time::Instant::now();
loop {
tokio::select! {
// 从对等节点接收数据并转发
res = peer.recv() => {
if let Some(Ok(bytes)) = res {
last_recv_time = std::time::Instant::now();
let nb = bytes.len() * 8; // 转换为比特数
// 根据限流策略消耗带宽
if blacked || downgrade {
blacklist_limiter.consume(nb).await;
} else {
limiter.consume(nb).await;
}
total_limiter.consume(nb).await;
total += nb;
total_s += nb;
if !bytes.is_empty() {
stream.send_raw(bytes.into()).await?;
}
} else {
break;
}
},
// 从当前流接收数据并转发
res = stream.recv() => {
if let Some(Ok(bytes)) = res {
last_recv_time = std::time::Instant::now();
let nb = bytes.len() * 8;
if blacked || downgrade {
blacklist_limiter.consume(nb).await;
} else {
limiter.consume(nb).await;
}
total_limiter.consume(nb).await;
total += nb;
total_s += nb;
if !bytes.is_empty() {
peer.send_raw(bytes.into()).await?;
}
} else {
break;
}
},
// 检查超时
_ = timer.tick() => {
if last_recv_time.elapsed().as_secs() > 30 {
bail!("Timeout");
}
}
}
// 定期更新流量统计和带宽控制
let n = tm.elapsed().as_millis() as usize;
if n >= 1_000 {
// 检查是否被封禁
if BLOCKLIST.read().await.get(&ip).is_some() {
log::info!("{} blocked", ip);
break;
}
blacked = BLACKLIST.read().await.get(&ip).is_some();
tm = std::time::Instant::now();
let speed = total_s / n;
if speed > highest_s {
highest_s = speed;
}
elapsed += n;
// 更新使用统计
USAGE.write().await.insert(
id.clone(),
(elapsed as _, total as _, highest_s as _, speed as _),
);
total_s = 0;
// 检查是否需要降级带宽
if elapsed > DOWNGRADE_START_CHECK.load(Ordering::SeqCst)
&& !downgrade
&& total > elapsed * downgrade_threshold
{
downgrade = true;
log::info!(
"Downgrade {}, exceed downgrade threshold {}bit/ms in {}ms",
id,
downgrade_threshold,
elapsed
);
}
}
}
Ok(())
}
3.2 带宽控制与流量管理
Relay Server实现了多层次的流量控制机制,确保服务器资源的公平分配和系统稳定性:
- 全局带宽限制:通过
TOTAL_BANDWIDTH控制服务器总带宽使用 - 单连接带宽限制:通过
SINGLE_BANDWIDTH限制单个连接的最大带宽 - 动态降级机制:当检测到带宽过度使用时,自动降低特定连接的优先级
- 黑名单与封禁管理:支持手动或自动封禁滥用服务器资源的IP地址
这些参数可以通过命令行或运行时命令动态调整:
// 处理管理命令
async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
let mut res = "".to_owned();
let mut fds = cmd.trim().split(' ');
match fds.next() {
Some("h") => {
res = format!(
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
"blacklist-add(ba) <ip>",
"blacklist-remove(br) <ip>",
"blacklist(b) <ip>",
"blocklist-add(Ba) <ip>",
"blocklist-remove(Br) <ip>",
"blocklist(B) <ip>",
"downgrade-threshold(dt) [value]",
"downgrade-start-check(t) [value(second)]",
"limit-speed(ls) [value(Mb/s)]",
"total-bandwidth(tb) [value(Mb/s)]",
"single-bandwidth(sb) [value(Mb/s)]",
"usage(u)"
)
}
// 处理其他命令...
_ => {}
}
res
}
四、Rust异步编程:Tokio运行时的高效网络处理
RustDesk Server充分利用了Tokio异步运行时的优势,实现了高性能的网络服务。通过分析源码,我们可以看到Tokio在多个关键方面的应用:
4.1 多线程异步运行时
// 在rendezvous_server.rs中
#[tokio::main(flavor = "multi_thread")]
pub async fn start(port: i32, serial: i32, key: &str, rmem: usize) -> ResultType<()> {
// 启动服务器逻辑...
}
// 在relay_server.rs中
#[tokio::main(flavor = "multi_thread")]
pub async fn start(port: &str, key: &str) -> ResultType<()> {
// 启动中继服务...
}
使用multi_thread调度器允许Tokio充分利用多核CPU资源,将任务分配到多个工作线程,提高并发处理能力。
4.2 非阻塞I/O与事件驱动
RustDesk Server使用Tokio的异步I/O模型,通过async/await语法编写简洁而高效的异步代码:
// UDP数据包处理循环
async fn io_loop(
&mut self,
rx: &mut Receiver,
listener: &mut TcpListener,
listener2: &mut TcpListener,
listener3: &mut TcpListener,
socket: &mut FramedSocket,
key: &str,
) -> LoopFailure {
let mut timer_check_relay = interval(Duration::from_millis(CHECK_RELAY_TIMEOUT));
loop {
tokio::select! {
// 定时检查中继服务器状态
_ = timer_check_relay.tick() => {
if self.relay_servers0.len() > 1 {
let rs = self.relay_servers0.clone();
let tx = self.tx.clone();
tokio::spawn(async move {
check_relay_servers(rs, tx).await;
});
}
}
// 处理接收到的消息
Some(data) = rx.recv() => {
match data {
Data::Msg(msg, addr) => { allow_err!(socket.send(msg.as_ref(), addr).await); }
Data::RelayServers0(rs) => { self.parse_relay_servers(&rs); }
Data::RelayServers(rs) => { self.relay_servers = Arc::new(rs); }
}
}
// 处理UDP数据包
res = socket.next() => {
match res {
Some(Ok((bytes, addr))) => {
if let Err(err) = self.handle_udp(&bytes, addr.into(), socket, key).await {
log::error!("udp failure: {}", err);
return LoopFailure::UdpSocket;
}
}
Some(Err(err)) => {
log::error!("udp failure: {}", err);
return LoopFailure::UdpSocket;
}
None => {}
}
}
// 处理TCP连接
res = listener.accept() => {
match res {
Ok((stream, addr)) => {
stream.set_nodelay(true).ok();
self.handle_listener(stream, addr, key, false).await;
}
Err(err) => {
log::error!("listener.accept failed: {}", err);
return LoopFailure::Listener;
}
}
}
// 处理其他TCP监听...
}
}
}
tokio::select!宏允许同时等待多个异步操作,当任何一个操作准备就绪时立即处理,这种事件驱动模型极大地提高了I/O密集型应用的性能。
五、数据存储与节点管理
RustDesk Server使用SQLite数据库存储节点信息和连接记录,同时在内存中维护活跃节点状态以提高性能。
5.1 对等节点管理
peer.rs模块实现了节点信息的管理,包括节点注册、更新和查询:
// PeerMap结构体管理所有对等节点
#[derive(Clone)]
pub(crate) struct PeerMap {
map: Arc<RwLock<HashMap<String, LockPeer>>>,
pub(crate) db: database::Database,
}
impl PeerMap {
// 获取或创建节点
#[inline]
pub(crate) async fn get_or(&self, id: &str) -> LockPeer {
if let Some(p) = self.get(id).await {
return p;
}
let mut w = self.map.write().await;
if let Some(p) = w.get(id) {
return p.clone();
}
let tmp = LockPeer::default();
w.insert(id.to_owned(), tmp.clone());
tmp
}
// 更新节点公钥和地址信息
#[inline]
pub(crate) async fn update_pk(
&mut self,
id: String,
peer: LockPeer,
addr: SocketAddr,
uuid: Bytes,
pk: Bytes,
ip: String,
) -> register_pk_response::Result {
log::info!("update_pk {} {:?} {:?} {:?}", id, addr, uuid, pk);
let (info_str, guid) = {
let mut w = peer.write().await;
w.socket_addr = addr;
w.uuid = uuid.clone();
w.pk = pk.clone();
w.last_reg_time = Instant::now();
w.info.ip = ip;
(
serde_json::to_string(&w.info).unwrap_or_default(),
w.guid.clone(),
)
};
// 更新数据库
if guid.is_empty() {
match self.db.insert_peer(&id, &uuid, &pk, &info_str).await {
Err(err) => {
log::error!("db.insert_peer failed: {}", err);
return register_pk_response::Result::SERVER_ERROR;
}
Ok(guid) => {
peer.write().await.guid = guid;
}
}
} else {
if let Err(err) = self.db.update_pk(&guid, &id, &pk, &info_str).await {
log::error!("db.update_pk failed: {}", err);
return register_pk_response::Result::SERVER_ERROR;
}
log::info!("pk updated instead of insert");
}
register_pk_response::Result::OK
}
}
5.2 数据库交互
database.rs模块实现了与SQLite数据库的交互,使用SQLx库进行类型安全的数据库操作:
// 数据库初始化
pub(crate) async fn init(&self) -> ResultType<()> {
sqlx::sqlite::query(
"CREATE TABLE IF NOT EXISTS peer (
id TEXT PRIMARY KEY,
guid BLOB NOT NULL UNIQUE,
uuid BLOB NOT NULL,
pk BLOB NOT NULL,
info TEXT NOT NULL,
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
)"
).execute(&self.pool).await?;
sqlx::sqlite::query(
"CREATE TABLE IF NOT EXISTS relay (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id_a TEXT NOT NULL,
id_b TEXT NOT NULL,
start_time INTEGER NOT NULL,
end_time INTEGER,
bytes INTEGER NOT NULL DEFAULT 0,
duration INTEGER NOT NULL DEFAULT 0,
ip_a TEXT NOT NULL,
ip_b TEXT NOT NULL
)"
).execute(&self.pool).await?;
// 创建索引和触发器...
Ok(())
}
六、性能优化与最佳实践
RustDesk Server在多个层面进行了性能优化,使其能够在资源有限的服务器上高效运行:
6.1 内存安全与零成本抽象
Rust的所有权系统和类型安全确保了内存安全,避免了常见的内存泄漏和数据竞争问题。同时,Rust的零成本抽象允许高级抽象(如Arc、RwLock)在运行时不产生额外开销。
// 使用Arc和RwLock实现线程安全的共享数据
type LockPeer = Arc<RwLock<Peer>>;
// 高效的读写锁使用模式
let (elapsed, peer_addr) = {
let r = peer.read().await; // 读锁定
(r.last_reg_time.elapsed().as_millis() as i32, r.socket_addr)
};
// 锁在作用域结束后自动释放
6.2 网络缓冲区优化
通过调整UDP接收缓冲区大小,RustDesk Server能够高效处理高吞吐量的网络数据:
// main.rs中配置UDP接收缓冲区
const RMEM: usize = 0; // 默认使用系统配置
fn main() -> ResultType<()> {
// ...参数解析...
let rmem = get_arg("rmem").parse::<usize>().unwrap_or(RMEM);
// ...启动服务器...
RendezvousServer::start(port, serial, &get_arg_or("key", "-".to_owned()), rmem)?;
Ok(())
}
管理员可以通过系统配置和应用参数调整缓冲区大小,以适应不同的网络环境:
# 调整系统UDP缓冲区大小
sudo sysctl -w net.core.rmem_max=52428800
6.3 连接复用与资源管理
RustDesk Server使用连接池和资源复用技术,减少频繁创建和销毁连接带来的开销:
// 数据库连接池
pub(crate) struct Database {
pool: sqlx::SqlitePool,
}
impl Database {
pub(crate) async fn new(db_url: &str) -> ResultType<Self> {
let pool = sqlx::SqlitePoolOptions::new()
.max_connections(5) // 限制最大连接数
.connect(db_url)
.await?;
Ok(Self { pool })
}
}
七、部署与使用指南
7.1 编译与安装
RustDesk Server使用Cargo作为构建工具,编译过程简单直接:
# 克隆仓库
git clone https://gitcode.com/gh_mirrors/ru/rustdesk-server.git
cd rustdesk-server
# 编译发布版本
cargo build --release
# 生成的可执行文件位于target/release/目录下
ls target/release/hbbs target/release/hbbr
7.2 基本运行命令
# 启动Rendezvous Server
./hbbs -r <relay-server-ip>
# 启动Relay Server
./hbbr
7.3 配置选项
RustDesk Server提供丰富的配置选项,可以通过命令行参数或配置文件进行设置:
# 查看帮助信息
./hbbs --help
# 自定义端口和中继服务器
./hbbs -p 21115 -r relay.example.com:21116
八、总结与未来展望
RustDesk Server展示了Rust语言在高性能网络编程领域的强大能力。通过结合现代异步编程模型、高效内存管理和精心设计的P2P协议,RustDesk Server实现了低延迟、高安全性的远程桌面解决方案。
8.1 核心优势总结
- 高性能P2P通信:减少对中心化服务器的依赖,降低带宽成本
- 鲁棒的NAT穿透:多种穿透技术确保复杂网络环境下的连接可靠性
- 精细的带宽控制:多层次流量管理确保服务稳定性和公平性
- 内存安全与并发控制:Rust语言特性避免常见的安全问题和性能瓶颈
- 模块化设计:便于维护和扩展新功能
8.2 潜在改进方向
- QUIC协议支持:进一步优化弱网络环境下的连接稳定性
- 分布式节点发现:提高大规模部署时的可扩展性
- 智能中继选择:基于网络状况动态选择最优中继服务器
- 更精细的流量分析:提供详细的网络使用统计和可视化
通过深入理解RustDesk Server的源码实现,我们不仅可以掌握P2P网络编程的关键技术,还能学习到如何利用Rust语言特性构建高性能、可靠的网络服务。无论是远程桌面、实时通信还是分布式系统,这些技术和设计模式都具有广泛的应用价值。
附录:关键技术术语对照表
| 英文术语 | 中文解释 | 核心作用 |
|---|---|---|
| Rendezvous Server | 节点发现服务器 | 协调对等节点的注册与发现 |
| Relay Server | 中继服务器 | P2P连接失败时提供数据中转 |
| NAT Hole Punching | NAT打洞 | 实现处于不同NAT后的节点直接通信 |
| Peer | 对等节点 | 网络中的客户端设备 |
| STUN | 会话穿越实用工具 | 用于NAT类型检测和公网地址发现 |
| Tokio | Rust异步运行时 | 提供高效的异步I/O和任务调度 |
| Arc | 原子引用计数 | 实现多线程间的共享所有权 |
| RwLock | 读写锁 | 允许多个读者或单个写者的同步原语 |
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



