ROS2 分布式通讯:域隔离、组网与安全
同一个教室里三台机器人全用默认配置,A 车的操作命令竟被 B 车执行了——这不是 bug,是"串台"。本课先用 ROS_DOMAIN_ID 治串台(教学分组靠它统一话题名),再沿"同网群控用 namespace、无线组控改单播发现、大规模机群上 DISCOVERY_SERVER"把四级选型一次讲透。
目标本课学完,你应该能做到
能讲清 ROS_DOMAIN_ID 落到教学分组怎么用:给每组分配不同域号,让 6 个小组可以放心共用 /cmd_vel、/chatter 这类标准话题名而不串台,并算出域号对应的组播地址与发现端口。
能用 namespace 把同域多车的话题变成 /robot01/cmd_vel … /robot06/cmd_vel,说清"按车寻址"与"循环前缀=群发"两种群控写法,并用 ros2 topic list 验证前缀已生效。
能解释 802.11 为什么坑组播(按最低速率发、无 ACK 重传、AP 聚合限速),并会写一份 Fast DDS XML 的固定对端名单,用 FASTRTPS_DEFAULT_PROFILES_FILE 切到单播发现。
能按"教学分组 → 同网群控 → 无线组控 → 大规模机群"四级场景选对方案:部署 DISCOVERY_SERVER 集中发现,说清主备多机容灾思路与每一级的代价。
难点:无线组播失败"不报错、时好时坏"的定位;以及为什么名单长了就该换成 DISCOVERY_SERVER 而不是继续加静态 IP。
01问题引入:好好的机器人为什么会"串台"
ROS_DOMAIN_ID 划圈)讲透——教学环境正是靠它给各组划圈,好让大家都统一叫 /cmd_vel。本课在"划圈"之上再解决三件更实际的事:同域多车怎么群控(namespace)、无线为什么坑组播(单播 XML)、上规模后怎么集中发现(DISCOVERY_SERVER 主备)。同一教室三台 B2 实验车同时开机,全部使用 ROS2 默认配置
给 1 号车发 ros2 topic pub /cmd_vel,2 号车竟然也动了
两台车 echo $ROS_DOMAIN_ID 都是空(默认 0),在同一网络里互相"听得见"
ROS2 没有中心注册表,靠网络组播互相发现——同域 = 同一个"频道"
ROS_DOMAIN_ID 相同的节点才能互相发现和通信。ID 取值 0~232,不同域的节点即使插在同一根网线上也"互相看不见"。这是 DDS 层的机制,ROS1 需要换 Master 地址才能做到的事,ROS2 改一个环境变量就行。
事故复盘:一次真实的"串台"
某次实训课,三台 B2 实验车(编号 1/2/3)同时开机做无人驾驶循迹练习。约 10 分钟后,2 号车突然"抽风"——直行中猛地右转撞到护栏。重启后正常,但 20 分钟后 3 号车又出现同样症状。三台车的代码完全一样,都没改过。
全部出厂默认配置,echo $ROS_DOMAIN_ID 都为空 → 都是域 0
日志显示收到了一条 /cmd_vel:angular.z = -0.8,但 2 号车自己从没发过这个话题
同样的指令、同样的动作——三台车的 /cmd_vel 在同一个域里混成了一条
助教在 1 号车上手动发了一段测试指令,2/3 号车同时响应——串台坐实
/cmd_vel 这类话题名在三台车上完全一样——于是"1 号车的意图"被 2 号车忠实地执行了:
① 无隔离圈:同域 = 同一频道,谁都能听见;
② 话题名撞车:三车都叫
/cmd_vel,语义上无法区分"谁的命令";
③ QoS 默认兼容:都是 RELIABLE + KEEP_LAST(10),发现阶段直接撮合成功,没有任何警告;
④ 无声接管:订阅端收到指令就执行,不会问"这条是谁发的"。
—— 四层任何一层有防护,事故都不会发生。而最省事的一层,就是域名。
② 划圈之后,两台设备怎么才能看见彼此?(03 节组网三前提:同段、同 ID、组播通)
③ 同一个 WiFi 下,"不被别人看见"和"能看见自己人"如何同时成立?(02 / 04 节实操)
02先看现象:同域就通、异域就断
不接第二台设备,三个终端、四步、约 2 分钟:先看到"同域互通",再亲眼看它"异域即断、切域即串"。域号的完整给法(当前终端 / 单条命令 / 写进镜像三种)见 04 节"域号的三种给法",本节第一条命令就是最常用的给法一。
export ROS_DOMAIN_ID=42 ros2 run demo_nodes_cpp talker
export ROS_DOMAIN_ID=42 ros2 run demo_nodes_cpp listener
export ROS_DOMAIN_ID=99 ros2 topic list
unset ROS_DOMAIN_ID ros2 topic list
✔ 现象到手:同域通、异域断、切回默认即时串——机制细节与完整操作在 03 / 04 节拆解。
03原理讲解
一个数字如何隔离整张网
Fast-DDS 根据域号计算出专属组播地址 239.255.0.N 和端口 7400 + N×250。发往不同域的发现报文在网卡层就被分流,根本不会送到对方协议栈——这就是隔离的第一道防线。
节点启动即向域内组播地址周期发送 SPDP 发现报文;收到回应后双方通过 SEDP 交换"我有哪些话题/服务/动作端点",才建立点对点的正式传输链。
ROS1 靠唯一的 ROS_MASTER_URI 注册中心,改域要换 Master 地址、全部节点重启;ROS2 每个节点自主发现,改 DOMAIN_ID 就是各改各的环境变量,Master 单点故障问题也随之消失。
跨机通信只依赖三件事:① IP 同网段(能互相 ping 通)② 双方 ROS_DOMAIN_ID 相同 ③ 组播没有被防火墙/交换机拦截。没有 Master、没有配置文件,就这三条。
| 对比维度 | ROS1 | ROS2(DDS) |
|---|---|---|
| 发现方式 | 所有节点向 ROSMaster 注册/查询 | 域内组播 SPDP 自主互发现 |
| "隔离"手段 | 改 Master URI + 全节点重启 | 改 ROS_DOMAIN_ID,即时生效 |
| 取值范围 | 无域概念(一台机器一个 Master) | 0~232,共 233 个逻辑隔离域 |
| 单点故障 | Master 挂了新节点进不来 | 无中心,任意节点退出不影响他人 |
| 隔离层次 | 应用层(还能互相 ping) | 直达 DDS 发现层甚至网卡过滤 |
~/.bashrc 或机器人系统镜像),中央工作站同时开多个终端、用不同 ID 分别接入各车——比给每台车配独立网段省事得多。
组播发现与域隔离的物理过程
239.255.0.1,域 5 是 239.255.0.6。网段第一个地址(.0)与最后一个地址(.255)在 IPv4 组播里不分配给主机,这也是"域号最大只能到 232"的原因之一。记法:地址末位 = 域号 + 1。
| ROS_DOMAIN_ID | 组播地址 | 发现端口(组播 / 单播) | 被谁用 |
|---|---|---|---|
| 0(默认) | 239.255.0.1 | 7400 / 7411 | 出厂默认 → 串台的根源 |
| 1 | 239.255.0.2 | 7650 / 7661 | 多机车间第 1 组 |
| 5 | 239.255.0.6 | 8650 / 8661 | 本节演示用 |
| 7 | 239.255.0.8 | 9150 / 9161 | EXP-12 实操实测值 |
| 42 | 239.255.0.43 | 17900 / 17911 | 实操 A 用(42 = 实验编号,好记) |
1~20 顺序发牌即可,既避开默认域 0,又留足余量。
两种发现模式与通信安全
开局域网、机器人数量少(<50 节点)时的默认选择。零配置、即开即用,但依赖组播——部分企业网/云环境交换机禁用了组播,就全军覆没。
指定一台"发现服务器",所有节点改为向它单播注册。不受组播限制,适合 50~500 节点、跨子网、云端混合部署。代价是要先启动并维护 server 进程。
DDS 标准定义的安全扩展:governance(哪些域要加密)+ permissions(谁能发/收哪个话题)+ passwords(密钥),ROS2 封装为 sros2 工具包,未授权节点连"看到话题"都做不到。
SROS2 Enforce 模式带来约 5~15% CPU 开销。教学小车资源有限,建议:内网实验默认不开,涉及远程遥控/OTA 升级的产品化场景再启用。
| 维度 | 组播发现(默认) | DISCOVERY_SERVER |
|---|---|---|
| 传输方式 | 组播 239.255.0.N | 单播到指定 IP:11811 |
| 配置成本 | 零(只改域号) | 先跑 fastdds discovery,每终端 export 服务器地址 |
| 跨子网/云 | 组播通常被路由丢弃 → 不通 | 单播可路由 → 通 |
| 适用规模 | 单网段、小中型系统 | 几十到数百节点、跨网段 |
| 故障特征 | 防火墙/交换机一拦就全挂 | server 挂了新节点发现不了(有单点)→ 可配多台服务器地址容灾互备,见末节四级选型 |
sros2 security generate_artifacts -p governance.xml -p permissions.xml -k keystore 生成密钥库 ② 每个节点启动时注入 ROS_SECURITY_ENABLE=true、ROS_SECURITY_STRATEGY=Enforce、ROS_SECURITY_KEYSTORE ③ enclave 名必须与 permissions 声明一致。效果:未授权节点 echo 会直接失败。⚠ 密钥库严禁提交 git。
fastdds discovery --server-id 0 --ip-address <本机IP> --port 11811;② 每个终端 export ROS_DISCOVERY_SERVER=<ip>:11811;③ 验证:ros2 daemon status 确认 CLI 守护进程正常,跨子网两台机器各跑 ros2 node list 都能列出对方节点——列得出,发现链路即成立。
场景全景:从教室到机群,四级选型与真实案例
实训室 6 组同时上课:每组发一个域号(1~6),组内全部光明正大叫 /cmd_vel、/chatter——异组之间本来就互相看不见,串台在分组这一层就死掉。这就是我们教学统一用 ROS_DOMAIN_ID 的原因。
同一组 6 台 B2 要联动:进入同一域后,各车话题挂上前缀 /robot01/cmd_vel … /robot06/cmd_vel——指哪打哪是"按车寻址",循环 6 个前缀发一遍就是"群发全组"。
车跑起来靠实训 AP:802.11 对组播按最低速率发、不重传还聚合限速,SPDP 宣告一多就随机丢——把固定几台的 IP 写进一份 Fast DDS XML,宣告逐台单播直达,发现不再赌无线组播。
开放日 40 台车跨教室联调:静态名单已经写不动、组播更会形成发现风暴——全部节点改为向 DISCOVERY_SERVER 单播注册,部署两台 server 一主一备,主挂备切,发现链路不断。
# 给法 A · 命令行(先看效果最直观) ros2 run demo_nodes_cpp talker --ros-args -r __ns:=/robot01 # 给法 B · launch 一行(后面多车实验的标准姿势) Node(package=..., executable=..., namespace='robot01')用(群控两种打法):按车寻址
ros2 topic pub /robot01/cmd_vel … 只动 1 号车;群发全组 = 按 robot01…robot06 循环发同一指令。验:
ros2 topic list 应看到 /robot01/chatter——另一终端不带前缀去 echo /chatter 一条也收不到;ros2 node list 里节点也带着 /robot01 前缀。失败模式:前缀写在了启动前缀之外的终端、或还没进同域就先验前缀——顺序是先划圈(同域)再寻址(前缀)。设(把固定队友写成单播初始对端名单,每台机器都持有一份同样的 XML):
<!-- peers.xml:固定队员的单播对端名单 -->
<participant><rtps><builtin><initialPeersLocator>
<locator><udpv4><address>192.168.10.11</address></udpv4></locator>
<locator><udpv4><address>192.168.10.12</address></udpv4></locator>
</initialPeersLocator></builtin></rtps></participant>
export FASTRTPS_DEFAULT_PROFILES_FILE=$PWD/peers.xml # 每个终端都要,写进 bashrc 固化怎么用:6 台车的 WIFI IP 互写进同一份名单,启动即按名单逐台单播互发宣告——组播从发现的关键路径上被摘掉。验:
ros2 topic list 跨车互见、稳定不闪;tcpdump -ni wlan0 udp port 7400 抓到的是发往固定 IP 的单播。⚠ 名单超过十几台就别再手写——那就是第四级的活。
export ROS_DISCOVERY_SERVER=<ip1>:11811 发现统一走服务器,不再有组播风暴也没有越写越长的静态名单。容灾怎么做:起第二台 server(不同机器、同一端口即可),客户端把备用地址一并写进同一变量(分号分隔两个 ip:port)——主服务器故障时切到备机继续宣告与接入。验:跑起主备后 kill 掉主服务器进程,已建链的话链照常、新节点仍能经备机加入;把两台地址全删光才会回到"组播赌运气"。代价:多一台常驻服务器要维护——这就是划得来的规模门槛。04实验步骤
工程化顺序:先看全景(图 13-2)→ 再进诊断台与三终端实操 → 最后读验收清单。
双机组网诊断台
域号的三种给法
三种给法对应三种使用场景——先在当前终端试(最常用),排查时用前缀式做 A/B 对照,确认无误再固化到机器人镜像里。
影响范围是当前 shell 及其启动的所有子进程——新开的终端不受影响。用官方 demo 的 talker 做发布方:
export ROS_DOMAIN_ID=5 echo $ROS_DOMAIN_ID # 5 ros2 run demo_nodes_cpp talker # 本终端启动的节点都进域 5
再开一个终端 B,同样 export ROS_DOMAIN_ID=5 后跑 listener,就能收到:
export ROS_DOMAIN_ID=5 ros2 run demo_nodes_cpp listener # 同域才能收
多人共用一台机器时必备——排查串台问题做 A/B 对照最干净。同一台机器上,让 listener 留在域 5,talker 用前缀式临时进域 6,立刻看不到数据:
ROS_DOMAIN_ID=6 ros2 run demo_nodes_cpp talker echo $ROS_DOMAIN_ID # 输出仍为空——环境没被改
工控机/机器人出厂配置的标准做法;注意 source 只影响之后新开的终端:
echo 'export ROS_DOMAIN_ID=5' >> ~/.bashrc source ~/.bashrc # 只影响新开终端;老终端不会变
✔ 三种给法的差别一句话记住:export 管当前终端、前缀只管一条命令、bashrc 管开机以后的新终端。
VAR=x cmd 语法只在 cmd 进程及其子进程的环境里注入该变量,命令返回后当前 shell 的变量表毫发无损。排查串台问题时用它做 A/B 对照,比反复 export/unset 干净得多。
单机三终端,10 分钟看到隔离效果
不接第二台设备也能完整验证域机制——开三个终端即可。三个终端都先 cd ~/ros2_ws 并 source install/setup.bash。
export ROS_DOMAIN_ID=42,启动 talker 发布 /chatter
export 同为 42,启动 listener(或 topic echo)→ 能收到,证明同域互通
export ROS_DOMAIN_ID=99 后再 echo → 收不到,publishers 列表为空
三终端全部 unset 恢复默认 0 → 终端3 的 listener 立即开始打印
export ROS_DOMAIN_ID=42 ros2 run demo_nodes_cpp talker
域号相同 = 组播地址端口相同 → SPDP 命中 → SEDP 撮合 → 数据到达:
export ROS_DOMAIN_ID=42 ros2 topic list # 能看到 /chatter ros2 run demo_nodes_cpp listener
注意:列表不是空的——本机域内公共话题仍在,判断标准是有没有对端节点的话题:
export ROS_DOMAIN_ID=99 ros2 topic list # 只有本机公共话题 ros2 run demo_nodes_cpp listener # 一直空,等不到数据
这正是事故复盘里发生的事——只要回到同一个域,别人的话题立刻出现在你眼前:
unset ROS_DOMAIN_ID ros2 topic list
✔ 四步走完,域隔离的机制你已亲手验证:同域互通、异域隔离、切域即时生效。
ros2 topic list 并非"空"——本机域内公共话题(/parameter_events、/rosout)仍在。判断标准是有没有对端节点的话题,不是列表长度。05注意事项与验收
- 验收 1:单机三终端——
talker与listener实测同域互通、异域隔离、切回默认域串台重现,三步截图齐全 - 验收 2:能算出任意域号的发现端口(
7400 + 250×域号),并说明域 232 为何贴着上限 - 验收 3:三种给法各跑一次,能说清导出范围差异(当前终端 / 单条命令 / 开机后新终端)
- 验收 4:能说出跨机组网三前提(同网段 → 同域号 → 组播畅通),并按序排查(ID → ping → multicast → 防火墙)
- 验收 5:对照 图 13-1 上半(SPDP 组播宣告 → SEDP 交换端点 → 数据链路建立),自己默画得出 SPDP→SEDP 发现两阶段流程图
- 验收 6:对照 图 13-4 说清四级选型判据——为什么教学组用域号、同网群控用 namespace、无线要改单播 XML、40 车级必须上 DISCOVERY_SERVER 及其主备容灾思路
- 验收 7:能把
talker挂进/robot01命名空间,用ros2 topic list/ros2 node list验证前缀生效,并解释不带前缀的终端为什么收不到
| 坑 | 症状 | 解法 |
|---|---|---|
| 环境变量没生效 | 改了 bashrc 但当前终端还是老域号 | source 后开新终端;老终端手动 export;或用 env 前缀式当场验证 |
| 防火墙拦组播 | 域号一致、ping 通,就是互相看不到 | ufw allow 7400:8399/udp;云服务器还要在安全组放行 UDP 组播段 |
| 虚拟机 NAT 模式 | 虚拟机内节点与宿主机互发现失败 | VMware/VirtualBox 改"桥接模式",让虚拟机拿到同网段真实 IP |
| script 命令被截断 | 记录会话时 \ 转义链接失效、话题被硬换行截开 | cmd 写成数组 script -qec "ros2 topic pub -r 1 /a std_msgs/msg/String '{data: hello}'" log |
| fastdds 工具不存在 | command not found: fastdds | sudo apt install ros-humble-rmw-fastrtps-cpp |
| SROS2 enclave 名不匹配 | 开了 Enforce 后节点无法通信 | enclave 名必须与 permissions 定义完全一致;密钥库不进 git |
06本课总结与验证题
ROS_DOMAIN_ID 0~232,同号互通、异号隔离,对应专属组播地址与端口。
同网段、同域号、组播畅通。排查顺序:ID → ping → multicast → 防火墙 → 换 RMW。
教学分组用域号、同网群控用 namespace、无线组控改 XML 单播、大规模机群上 DISCOVERY_SERVER 主备容灾。
每台机器人出厂烧录独立域号,中央站多 ID 接入——这是多机车间的标准姿势。
/cmd_vel、QoS 默认兼容、订阅端收到就执行——改一个域号,四层里最关键的那一层就补上了。工程上的落地动作只有一句:把 export ROS_DOMAIN_ID=N 写进每台车的 ~/.bashrc,出厂即隔离。
参考答案与得分点
得分点:①地址末位 = 域号 + 1 → 239.255.0.233 ②端口 = 7400 + 250×232 → 65400(单播 65411) ③再大一级 65650 就超过系统端口上限 65535 → 域号最大 232。(①②各 40%,③ 20%)/chatter,根本分不清谁是谁。给其中一台的启动加 --ros-args -r __ns:=/robot01 后:话题名变成了什么?另一个不带前缀的终端去 echo /chatter 还能收到吗?想一条指令同时驱动 6 台,你怎么做?
参考答案与得分点
得分点:① 话题变为 /robot01/chatter(节点也挂进 /robot01 命名空间)② 收不到——前缀把作用域隔开了,必须echo /robot01/chatter ③ 群控 = 按 robot01…robot06 六个前缀循环发同一指令(或 launch 批量挂 namespace)。(①②各 40%,③ 20%)
export ROS_DOMAIN_ID=42,终端 2 忘了 export 直接跑 listener——预期现象?想让"以后新开的所有终端"都默认进域 42,用哪种给法?
参考答案与得分点
得分点:①终端 2 落在默认域 0 → 收不到任何 I heard(域不同物理隔离) ②用给法三:echo 'export ROS_DOMAIN_ID=42' >> ~/.bashrc 并重开终端/source ③给法一(export)只影响当前 shell 及子进程。(①②各 40%,③ 20%)
参考答案与得分点
得分点:①不能——域号与组播被拦无关,改域只会两边继续不可见 ②修法 A:放行组播(ufw allow 7400:8399/udp、交换机开 IGMP) ③修法 B:部署 fastdds discovery + 每终端 export ROS_DISCOVERY_SERVER=<ip>:11811 改单播发现。(①30%,②③各 35%)
参考答案与得分点
得分点:①现状根因:域互相隔离,单终端只能进一个域 ②方案 A:监控站开 N 个终端各 export 车辆域号(N 个进程) ③方案 B:每域起转发/bridge 节点把 /robot_status 聚到监控域 ④代价:A 终端多、B 多一跳延迟与故障点,"全同域"最省事但串台回归。(①30%,②③各 30%,④ 10%)ros2 topic list 却"时有时无"。有人让你把每台车换成不同的域号——对吗?你按第三级方案写好 peers.xml 并 export FASTRTPS_DEFAULT_PROFILES_FILE 之后,用什么命令能确认发现已经改走单播?
参考答案与得分点
得分点:①不对——丢的是无线组播里的 SPDP(802.11 组播无 ACK、按最低速率发、AP 还限速),域号与它无关,换域只会两边继续不可见 ②变量要写进每台车的 bashrc/镜像,新开终端才行 ③tcpdump -ni wlan0 udp port 7400 应看到发往固定车 IP 的单播、且跨车 topic list 稳定互见。(①40%,②③各 30%)