ROS2 多机消息串扰隔离方案教程
适用场景:多架无人机机载电脑连接同一局域网(如 192.168.144.x)时,机内 ROS2 消息互相串扰。 方案:
ROS_LOCALHOST_ONLY=1,让每架飞机的 ROS2 成为 "本机私有图"。
1. 背景与问题
1.1 现象
- 两架飞机(A、B)的机载电脑连接同一个网络时:
- 地面站发给 A 的任务,B 也会执行;
- A 的无人机状态 / 遥测,地面站会显示到 B 名下(或反之)。
- 序列号(SN)不同,但消息仍然串。
1.2 根因
消息链路分为两段:
表格
| 段 | 机制 | 是否按 SN 隔离 |
|---|---|---|
| MQTT(地面站 ⇄ 机载) | TCP 直连 broker(xx.xxx.xxx.xxx),按/{SN}/xxx主题收发 | ✅ 已隔离,没问题 |
ROS2(机内话题/ros2/*、/mavros/*) | DDS 默认走网卡组播,同一网段所有机器互通 | ❌ 未隔离,串的原因 |
一句话:MQTT 按序列号分了家,但机内 ROS2 话题是固定名,DDS 广播让同网络的飞机互相听得到。
1.3 结论
只需隔离 ROS2 一层即可。MQTT、TCP(图传 / 云台)、MAVLink(飞控 / 地面站)、网段全部不受影响。
2. 方案 A 原理
2.1 是什么
ROS_LOCALHOST_ONLY=1是 ROS2(Foxy 及以上,含 Humble)官方的环境变量开关,作用:让 DDS 只使用本机回环接口127.0.0.1,消息不出网卡。
- 127.0.0.1 永远只代表 "本机自己",任何其他电脑都无法通过它收到消息;
- 设置后:A 机 ROS2 消息只在 A 机电脑内传播,B 机物理上收不到→ 不可能串。本质:ROS2 的通信协议 DDS 天生是为 "跨机器分布式通信" 设计的 —— 默认通过网卡组播在局域网里互相发现、互相收发。这个设置的本质,是把 DDS 绑定的网络接口从 "网卡" 换成 " 内核回环
127.0.0.1",把 ROS2 通信从" 跨机网络通信 "降级为" 单机进程间通信 "。 | 维度 | 默认状态 | 设置后 | |---|---|---| | DDS 绑定的网络接口 | 物理网卡(192.168.144.x) | 回环接口(127.0.0.1) | | 报文走的路 | 网卡 → 交换机 → 局域网其他设备 | 内核回环路径,不出物理网卡 | | 通信能力 | 跨机分布式通信(DDS 的本职) | 只剩单机进程间通信 | | 能发现谁 | 同网段所有 ROS2 节点 | 只有本机节点 | 一句话:DDS 是分布式协议,默认 "联网";这个开关把它的联网能力关掉了,让它退化成本机专用,其他设备物理上无法参与。
2.2 生效前提(本项目全部满足)
- 每架飞机所有 ROS2 节点运行在同一台电脑上(本项目所有节点均部署在机载电脑,由 systemd 统一管理)✅
- 每架飞机 MQTT 配置独立:
UAV_SN、mqtt_client_ID各机不同(MQTT 层本来就不串,靠此保证)✅ - 无跨机 ROS2 通信需求(当前跨机只有 MQTT 和 TCP)✅
2.3 隔离是双向的、物理强制的(不依赖别人是否设置)
- 只要你这架飞机设置了,不管其他飞机设置与否,你的 ROS2 消息只在自己电脑内部传播,消息不出你的网卡 → 其他飞机收不到你的消息;
- 同理,你也收不到其他飞机的消息(你的 DDS 只在
127.0.0.1收发,到达你网卡的 DDS 报文直接不处理); - 注意:隔离是 "各管各的"——A 设置了,A 与任何人都不串;但B、C 若都没设置,它们之间照样会串。建议所有飞机统一设置。
A机(已设置) B机(没设置) ROS2 ──只在127.0.0.1──> 消息不出网卡 ──╳──> 收不到A的消息 B自己的ROS2 ──走网卡广播──> 局域网收到 ──> A机网卡收到 但A的DDS只在127.0.0.1监听,不处理3. 实施步骤(每架飞机都要执行)
3.1 创建 systemd 全局环境变量配置(核心,1 步)
运行
sudo mkdir -p /etc/systemd/system.conf.d sudo tee /etc/systemd/system.conf.d/ros_localhost.conf <<'EOF' [Manager] DefaultEnvironment="ROS_LOCALHOST_ONLY=1" EOF为什么放在/etc/systemd/system.conf.d/:
| 位置 | 会被 OTA 覆盖吗 | 结论 |
|---|---|---|
.service文件(/etc/systemd/system/) | ✅ 会(update.sh 同步) | 不能放 |
script/*.sh(/home/cat/ros2_ws/script/) | ✅ 会(update.sh 同步) | 不能放 |
/etc/systemd/system.conf.d/ | ❌ 不会 | 放这里✅ |
此配置对所有 systemd 服务生效(含以后 OTA 新部署的服务),永久有效。
3.2 (可选但建议)让手动敲的 ros2 命令也带上变量
运行
echo 'export ROS_LOCALHOST_ONLY=1' | sudo tee /etc/profile.d/ros_localhost.sh配完重新登录 / 重新 ssh生效。不配也能用,但手动验证时结果会不准。
3.3 重载并重启
运行
sudo systemctl daemon-reload sudo reboot注意:
reboot会重启飞机,请确认飞机不在飞行状态、允许重启时再执行。
3.4 检查配置是否生效(无需重启即可查)
运行
systemctl show-environment | grep ROS- 看到
ROS_LOCALHOST_ONLY=1→ 配置已生效; - 看不到 → 检查 3.1 的文件内容是否正确。
4. 验证(关键,两架飞机都要做)
4.1 设置前(可选,用于确认问题存在)
运行
ros2 node list- 能看到对方飞机的节点(如对方的
ros2_sdk_node)→ 确认存在串扰。
4.2 设置后
运行
ros2 node list- 只能看到自己飞机上的节点→ 隔离成功;
- 还能看到对方节点 → 见 "FAQ" 排查。
4.3 交叉验证(端到端)
- 地面站给 B 机发送一条任务;
- 确认 A 机不执行;
- 再给 A 机发送任务,确认 A 正常执行、B 无反应。
4.4 验证注意事项
- 手动终端必须带变量:重新登录后执行,或先
export ROS_LOCALHOST_ONLY=1再执行; - 用
ros2 daemon stop可清除 CLI 后台缓存后再验证。
5. 回退方法(随时可还原)
运行
# 1. 删除配置 sudo rm /etc/systemd/system.conf.d/ros_localhost.conf sudo rm /etc/profile.d/ros_localhost.sh # 如果配了 3.2 # 2. 重载并重启 sudo systemctl daemon-reload sudo reboot回退后即恢复原状(ROS2 恢复走网卡,串扰问题会回来,其他功能不受影响)。
6. 持久性与 OTA 更新
6.1 每次开机
systemd 每次开机都会重新读取/etc/systemd/system.conf.d/*.conf,把DefaultEnvironment应用到所有服务。配置一次,每次开机生效。
6.2 OTA 更新(update.sh 实际覆盖范围)
| 目录 | update.sh 行为 | 本方案配置 |
|---|---|---|
/etc/systemd/system/*.service | 从发布包复制覆盖 | 不受影响 |
/home/cat/ros2_ws/script/ | 同步覆盖 | 不受影响 |
/home/cat/ros2_ws/config/ | 只加新文件,不覆盖已有 | 不受影响 |
/home/cat/ros2_ws/install/ | 整体替换 | 不受影响 |
/etc/systemd/system.conf.d/ | 完全不碰 | ✅ 永久保留 |
/etc/profile.d/ | 完全不碰 | ✅ 永久保留 |
OTA 更新的标准收尾就是 "重启以加载新版本",重启时方案 A 自然生效,无需额外操作。
7. 影响范围与注意事项
7.1 影响范围
| 通信 | 走什么 | 受影响吗 |
|---|---|---|
ROS2 话题(/ros2/*、/mavros/*) | 之前走网卡组播 | ✅ 改走本机 127.0.0.1 |
| MQTT(地面站 xx.xxx.xxx.xxx) | TCP 网卡 | ❌ 照常 |
| TCP(图传 / 云台) | TCP 127.0.0.1 | ❌ 照常 |
| MAVLink(飞控串口 / 地面站 UDP) | 串口 / UDP | ❌ 照常 |
| 网段 / IP / 路由器(192.168.144.x) | 网络层 | ❌ 完全不动 |
| 非 ROS2 服务(ZLMediaKit、frpc 等) | — | ❌ 无影响(不读该变量) |
7.2 注意事项
- 不要在
.service文件中显式设置Environment=ROS_LOCALHOST_ONLY=0等,会覆盖默认值(目前现有 service 均未设置,无冲突); - 已知边界:机上 ROS2无法与外部机器 DDS 通信(当前架构无此需求;MQTT/TCP 跨机通道不受影响);
- 若某架飞机由两台电脑分别跑 ROS2 节点,它们之间会断开(本项目每架一台机载,不适用)。
8. FAQ
Q1:为什么不用 ROS_DOMAIN_ID?有效范围只有 0~232,同网段必须全局唯一,机队规模大时无法分配,运维成本高。
Q2:为什么不用 namespace(话题带 SN)?改动面大(10+ 文件:SDK、桥、mavros、tcp、launch 等),漏改一处会导致本机功能断裂或残留串扰,且单机测不出来,需双机联调。方案 A 一行配置即达到等价效果,且无漏改风险。
Q3:验证时还能看到对方节点?按顺序排查:
- 确认终端带了变量:
export ROS_LOCALHOST_ONLY=1后重试; - 确认
systemctl show-environment | grep ROS有输出; - 确认 3.1 的文件内容无误后
sudo systemctl daemon-reload并重启相关服务。 - Q4:会串进别的网络吗?不会。该方案仅影响本机 ROS2 的通信接口选择,不改变任何网络配置。
- Q5:以后想恢复多机 ROS2 互通信怎么办?删除 3.1 的配置文件并重启即可恢复;或改用 namespace 方案(另议)。
- Q6:其他飞机没设置,会收到我的消息吗?不会。只要你设置了,你的消息不出自己网卡,物理上到不了任何其他飞机,与对方是否设置无关(详见 2.3)。但若对方也没设置,它仍会与其他同样没设置的飞机互串,所以建议全机队统一设置。