🏗️ 架构设计
🎯 1. 动机:从远程控制到 Robot as a Service

⚠️ 现有通用方案的挑战
A. 📡 中心化流式服务 (WebSocket/MQTT):通过流式协议中转指令是 IoT 领域的常见做法,但在复杂的机器人控制场景
- 🔗 架构耦合:业务逻辑与通信协议深度绑定,需开发维护复杂的私有通信协议。
- 💰 资源负担:需要长期维护高可用的公网中转服务
B. 🔀 通用反向代理与隧道 (Frp / Cloudflare Tunnel / VPN)
- 🎯 设计初衷差异:上述方案通常是为了让长期在线的内网服务暴露于公网,而机器人的常态是“频繁开关机/网络切换“
- ⚙️ 资源与运维压力:配置复杂,需要为每个终端维护公网映射记录或依赖中心化带宽中转
🏗️ 2. 架构:基于 WebRTC 的透明隧道

本项目实现了一个跨平台的中间件,利用 WebRTC DataChannel 构建一个能够穿透 NAT 的 TCP 透明传输层。
🔧 核心机制与组件
- 🖥️ Portal (用户/控制侧): 发起 WebRTC 连接,监听本地端口,等待 TCP 请求并桥接 DataChannel。
- 🤖 Proxy (机器人/设备侧):等待 WebRTC 连接,等待 DataChannel 并桥接 TCP 连接。
📡 MQTT 信令通道
采用 MQTT 作为信令通道,解耦“连接握手“与“权限管理“
- 🤝 连接握手
- 📥 接受方:Proxy 订阅 callee 信令话题,等待 offer,并发布 answer 到 caller 信令话题;上下线时发布 callee 状态话题;
- 📤 发起方:Portal 通过 callee 状态话题等待上线; 发布 offer 到 callee 信令话题,并从 caller 信令话题等待 answer;
- 🔐 权限管理 EMQX + Authing
- 鉴权操作全部发生在 mqtt broker
- 将复杂的机器人控制权限抽象为标准的 MQTT Topic 读写权限
🦀 技术选型:为什么选择 Rust?
- ⚡ 统一的异步生态:基于
Tokio无缝集成rumqttc(MQTT)、webrtc-rs和tonic(gRPC),避免上下文切换开销与复杂的架构设计。 - 🛡️ 内存与线程安全:所有权机制在编译期消除了并发数据竞争,这对于同时处理信令、WebRTC 状态机和 TCP 连接的复杂异步系统至关重要。
- 🚀 高性能与跨平台:Rust 提供了接近 C/C++ 的性能;支持 Android、Linux (ARM/x86) 等多种架构。
✨ 3. 核心优势
🔌 协议无关的透明传输
- 🌐 协议无关:支持所有基于 TCP 的应用层协议。
- ⚡ 高性能转发:Rust 零成本抽象设计,极低转发开销,充分利用 P2P 直连。
🚀 极简部署与一致性体验
- 💻 纯软件方案:仅需维护 MQTT Broker 和 TURN/STUN 服务,端侧仅需一对可执行文件
- ⚙️ 配置简单:通过唯一 ID 标识和连接,无需复杂 IP 路由
- ✨ 体验一致:不侵入业务逻辑,公网和局域网使用完全相同的接口代码
🔮 4. 未来展望
- 👥 集群控制:单 Portal 连接多个 Proxy,实现请求广播(如机器人编队控制)
- ♻️ 连接复用:DataChannel 和 Socket 复用机制,减少握手开销
- 🔒 HTTPS 支持
- 🔐 基于 EMQX + Authing 统一鉴权体系
🚀 快速开始
本指南将协助您快速搭建 remote_rpc 运行环境。系统依赖公网基础设施进行信令交换与 NAT 穿透。
📦 1. 基础设施准备
为确保 WebRTC 在复杂网络环境(如对称 NAT)下正常工作,需在公网服务器上部署:
必需服务
-
📡 MQTT Broker(信令交换)
- 推荐:Eclipse Mosquitto 或 EMQX
- Mosquitto Docker 指南
- EMQX Docker 指南
-
🔀 TURN Server(NAT 穿透中继)
- 推荐:Coturn
- Coturn Docker 指南
可选服务
- 🔍 STUN Server(NAT 检测)
- 可使用公共服务器:
stun.l.google.com:19302或stun.cloudflare.com:3478
- 可使用公共服务器:
🚀 2. 快速运行
场景:从笔记本电脑访问内网机器人上的 TCP 服务(端口 12345)
Step 1: 🤖 启动设备端代理 (Proxy)
./proxyd \
--local-id robot_1 \
--proxy-addr 127.0.0.1:12345 \
--mqtt-broker mqtt://<public_ip>:1883 \
--peer-turn turn:user:pass@host:port
Step 2: 🖥️ 启动用户端入口 (Portal)
./portald \
--local-id user_1 \
--remote-id robot_1 \
--portal-addr 127.0.0.1:54321 \
--mqtt-broker mqtt://<public_ip>:1883 \
--peer-turn turn:user:pass@host:port
Step 3: ✅ 验证连接
当 portald 提示连接成功后,访问 127.0.0.1:54321 即等同于访问机器人端的 127.0.0.1:12345。
💡 此时 gRPC Client 可以直接连接 127.0.0.1:54321 进行操作。
📖 3. 命令行参数详解 (CLI Reference)
🤖 proxyd (Robot Side)
proxyd 负责驻守在设备端,等待来自 Portal 的连接请求,并桥接本地 TCP 服务。
$ ./proxyd -h
Usage: proxyd [OPTIONS]
Options:
-l, --local-id <LOCAL_ID> 本地 ID [必须]
-p, --proxy-addr <PROXY_ADDR> 需要被代理的目标服务地址 [必须] (例如: 127.0.0.1:9000 或 unix:///tmp/sock)
-b, --mqtt-broker <BROKER> MQTT Broker 地址 [默认: mqtt://localhost:1883]
--mqtt-username <USERNAME> MQTT 用户名 [可选]
--mqtt-password <PASSWORD> MQTT 密码 [可选]
--peer-stun <STUN> STUN 服务器地址 (可指定多个) [默认: stun:stun.l.google.com:19302]
--peer-turn <TURN> TURN 服务器地址 (可指定多个) (格式: turn:user:pass@host:port)
--online-timeout <SEC> 等待对端上线超时时间 [默认: 5]
--connect-timeout <SEC> WebRTC 建连超时时间 [默认: 5]
-h, --help 显示帮助信息
🖥️ portald (User Side)
portald 运行在控制端,负责开启本地入口端口,并寻找远程 Peer 建立隧道。
$ ./portald -h
Usage: portald [OPTIONS]
Options:
-l, --local-id <LOCAL_ID> 本地 ID [必须]
-r, --remote-id <REMOTE_ID> 目标设备的 ID [必须]
-p, --portal-addr <PORTAL_ADDR> 代理到本地的地址 [必须] (例如: 127.0.0.1:9000 或 unix:///tmp/sock)
-b, --mqtt-broker <BROKER> MQTT Broker 地址 [默认: mqtt://localhost:1883]
--mqtt-username <USERNAME> MQTT 用户名 [可选]
--mqtt-password <PASSWORD> MQTT 密码 [可选]
--peer-stun <STUN> STUN 服务器地址 (可指定多个) [默认: stun:stun.l.google.com:19302]
--peer-turn <TURN> TURN 服务器地址 (可指定多个) (格式: turn:user:pass@host:port)
--online-timeout <SEC> 等待对端上线超时时间 [默认: 5]
--connect-timeout <SEC> WebRTC 建连超时时间 [默认: 5]
-h, --help 显示帮助信息
编译与测试指南
本指南介绍如何 进行 loong_remote_communication 的跨平台编译和集成测试。
1. 编译
Release:slow
docker compose run --rm build-release-x86_64
docker compose run --rm build-release-aarch64
docker compose run --rm build-release-win64
Debug:fast
docker compose run --rm build-x86_64
docker compose run --rm build-aarch64
docker compose run --rm build-win64
编译产物位于 target/<target-triple>/debug/ 或 target/<target-triple>/release/ 目录。
2. 测试
步骤 1: 启动 MQTT Broker
docker compose up -d mqtt-broker
docker compose ps mqtt-broker
步骤 2: 设备端:启动 Echo 服务器和 proxyd
使用 TCP socket:
python3 test/py/server.py --addr 0.0.0.0:12345
./target/x86_64-unknown-linux-gnu/debug/proxyd \
--local-id robot_01 \
--mqtt-broker mqtt://127.0.0.1:1883 \
--proxy-addr 127.0.0.1:12345
使用 Unix socket:
python3 test/py/server.py --addr unix:///tmp/echo-server.sock
./target/x86_64-unknown-linux-gnu/debug/proxyd \
--local-id robot_01 \
--mqtt-broker mqtt://127.0.0.1:1883 \
--proxy-addr unix:///tmp/echo-server.sock
步骤 3: 用户端:启动 proxyd 和 Echo 客户端
使用 TCP socket:
./target/x86_64-unknown-linux-gnu/debug/portald \
--local-id user_01 \
--remote-id robot_01 \
--mqtt-broker mqtt://127.0.0.1:1883 \
--portal-addr 0.0.0.0:54321
python3 test/py/client.py --addr 127.0.0.1:54321
使用 Unix socket:
./target/x86_64-unknown-linux-gnu/debug/portald \
--local-id user_01 \
--remote-id robot_01 \
--mqtt-broker mqtt://127.0.0.1:1883 \
--portal-addr unix:///tmp/echo-client.sock
python3 test/py/client.py --addr unix:///tmp/echo-client.sock
监控 MQTT 消息
运行以下命令,可以实时查看 MQTT 信令消息:
# 查看设备的在线状态
mosquitto_sub -h 127.0.0.1 -t 'callee/+/status' -v
# 查看用户的在线状态
mosquitto_sub -h 127.0.0.1 -t 'caller/+/status' -v
# 查看用户向设备端发送的 offer信令
mosquitto_sub -h 127.0.0.1 -t 'callee/+/signal' -v
# 查看设备端向用户返回的 answer信令
mosquitto_sub -h 127.0.0.1 -t 'caller/+/signal' -v
🔌 Portal Hub API
Portal Hub 提供了统一的 API 服务,用于动态创建和管理 WebRTC Portal。与 portald 不同,Portal Hub 支持多用户、多服务的集中管理。
📋 概述
Portal Hub 提供两种接口:
- 🔌 gRPC API (
portal_hub_grpc):提供 gRPC 接口供管理 portal - 🌐 REST API (
portal_hub_rest):提供 REST 接口供管理 portal
sequenceDiagram
participant Client as 客户端应用
participant Hub as Portal Hub
participant MQTT as MQTT Broker
participant Proxy as 远端 Proxy (proxyd)
participant Service as 远端服务
Note over Client,Service: 1. 启动阶段
Hub->>MQTT: 连接 MQTT Broker
Proxy->>MQTT: 连接 MQTT Broker<br/>订阅信令话题
Service->>Proxy: 启动本地服务<br/>(如 gRPC/REST Server)
Note over Client,Service: 2. 创建 Portal
Client->>Hub: POST /portal<br/>(创建 Portal 请求)
Hub->>MQTT: 查找设备在线状态
MQTT-->>Hub: 设备在线
Hub->>MQTT: 发送 WebRTC offer
Proxy->>MQTT: 接收 offer,发送 answer
Hub->>Proxy: 建立 WebRTC 连接
Hub-->>Client: 返回 Portal 地址<br/>(如 "0.0.0.0:12345")
Note over Client,Service: 3. 使用 Portal 访问服务
Client->>Hub: 连接 Portal 地址<br/>(TCP/Unix Socket)
Hub->>Proxy: 通过 WebRTC DataChannel<br/>转发数据
Proxy->>Service: 转发到本地服务
Service->>Proxy: 返回响应
Proxy->>Hub: 通过 WebRTC 返回数据
Hub-->>Client: 返回服务响应
Note over Client,Service: 4. 销毁 Portal
Client->>Hub: DELETE /portal<br/>(销毁 Portal)
Hub->>Proxy: 关闭 WebRTC 连接
Hub-->>Client: 确认销毁
📖 命令行参数详解
🔌 portal_hub_grpc
portal_hub_grpc 运行在控制端,提供 gRPC 服务用于创建和管理 Portal。
接口定义见
crates/grpc/proto/lrc_user_rpc.proto
$ ./portal_hub_grpc -h
Usage: portal_hub_grpc [OPTIONS]
Options:
-u, --user-id <USER_ID>
默认用户 ID,当请求中未提供 user_id 时使用 [默认: ]
-l, --listen <LISTEN>
Portal hub 的 gRPC 服务器监听地址 [默认: [::1]:50051]
-m, --mqtt-broker <MQTT_BROKER>
MQTT Broker 地址 (mqtt://host:port) [默认: mqtt://localhost:1883]
--mqtt-username <MQTT_USERNAME>
MQTT 用户名 [可选]
--mqtt-password <MQTT_PASSWORD>
MQTT 密码 [可选]
--peer-stun <PEER_STUN>
STUN 服务器地址 (例如: stun:stun.l.google.com:19302),可指定多个 [默认: stun:stun.l.google.com:19302]
--peer-turn <PEER_TURN>
TURN 服务器地址 (例如: turn:user:pass@host:port),可指定多个
--online-timeout <ONLINE_TIMEOUT>
等待远程端上线超时时间 (秒) [默认: 5]
--connect-timeout <CONNECT_TIMEOUT>
WebRTC 连接超时时间 (秒) [默认: 5]
-h, --help
显示帮助信息
🌐 portal_hub_rest
portal_hub_rest 运行在控制端,提供 HTTP REST API 用于创建和管理 Portal。
$ ./portal_hub_rest -h
Usage: portal_hub_rest [OPTIONS]
Options:
-u, --user-id <USER_ID>
默认用户 ID,当请求中未提供 user_id 时使用 [默认: ]
-l, --listen <LISTEN>
Portal hub 的 HTTP 服务器监听地址 [默认: 127.0.0.1:3000]
-m, --mqtt-broker <MQTT_BROKER>
MQTT Broker 地址 (mqtt://host:port) [默认: mqtt://localhost:1883]
--mqtt-username <MQTT_USERNAME>
MQTT 用户名 [可选]
--mqtt-password <MQTT_PASSWORD>
MQTT 密码 [可选]
--peer-stun <PEER_STUN>
STUN 服务器地址 (例如: stun:stun.l.google.com:19302),可指定多个 [默认: stun:stun.l.google.com:19302]
--peer-turn <PEER_TURN>
TURN 服务器地址 (例如: turn:user:pass@host:port),可指定多个
--online-timeout <ONLINE_TIMEOUT>
等待远程端上线超时时间 (秒) [默认: 5]
--connect-timeout <CONNECT_TIMEOUT>
WebRTC 连接超时时间 (秒) [默认: 5]
-h, --help
显示帮助信息
📚 API 使用说明
➕ 创建 Portal
端点: POST /portal | lrc.user.rpc.PortalLauncher/CreatePortal
请求体:
grpc 请求体
Config与 Json 请求体字段一致
{
"user_id": "user_1", // 可选,用户 ID
"robot_id": "robot_1", // 必须,机器人/设备 ID
"service_name": "tcp_service", // 必须,服务名称
"portal_type": "inet", // 可选,"inet" 或 "unix",默认 "inet"
"inet_port": "12345", // 可选,仅当 portal_type="inet" 时有效
"unix_file": "/tmp/sock" // 可选,仅当 portal_type="unix" 时有效
}
响应体:
grpc 响应体
SockAddr与 Json 请求体字段一致
{
"uri": "0.0.0.0:12345" // Portal 地址 URI
// type=INET: "0.0.0.0:12345"
// type=UNIX: "unix:///tmp/rpc_gps.sock"
}
➖ 销毁 Portal
端点: DELETE /portal | lrc.user.rpc.PortalLauncher/DestroyPortal
请求体: 同 CreatePortal 的 Config(至少需要 robot_id 和 service_name)
响应体: 空消息
🔗 代码示例
完整的代码示例请参考 examples/ 目录:
-
REST API 示例:
examples/rest_server.py- REST 服务端示例examples/rest_client_pipe.py- 通过 Portal Hub REST API 创建 Portal 并访问服务的客户端示例
-
gRPC API 示例:
examples/grpc_server.py- gRPC 服务端示例examples/grpc_client_pipe.py- 通过 Portal Hub gRPC API 创建 Portal 并访问服务的客户端示例
🎯 使用场景
- ☁️ 云平台设备管理 - 通过统一的 API 管理多个设备的 Portal
- 🔧 微服务架构 - 服务间通过 Portal Hub 动态建立连接
- 👥 多租户系统 - 不同用户通过
user_id隔离 Portal 资源 - 🧪 自动化测试 - 通过 API 动态创建和销毁测试环境
⚠️ 注意事项
- 用户 ID 管理: 如果请求中未提供
user_id,必须通过命令行参数-u/--user-id设置默认值 - 远程 ID 构建: Portal Hub 会自动将
robot_id和service_name组合为远程 ID,格式为{robot_id}-{service_name} - 端口分配: 创建 INET 类型 Portal 时,如果不指定
inet_port,系统会自动分配随机端口 - 连接超时: 确保设备端(proxyd)已启动并在线,否则创建 Portal 会超时失败