3. DDS核心概念
ROS 2 的通信底座是 DDS(Data Distribution Service)。理解 DDS 的核心概念,是排查多机通信故障、设计分布式系统的基础。本节围绕六个关键对象展开:域(Domain)、参与者(Participant)、发布者(Publisher)、订阅者(Subscriber)、主题(Topic)以及QoS 策略,并说明数据是如何被真正读写的。
一、核心原理:从域到数据读写的层级关系
DDS 采用“全局数据空间”的思想:数据不直接从一个节点发给另一个节点,而是发布到某个主题上,由 DDS 中间件负责匹配和分发。其层级关系可以概括为:
- 域(Domain):最外层的隔离边界,用整数 Domain ID 标识。只有 Domain ID 相同的参与者才能互相发现和通信。ROS 2 默认使用 Domain 0,可通过
ROS_DOMAIN_ID环境变量修改。 - 参与者(Participant):一个进程接入 DDS 网络的入口,通常一个 ROS 2 节点对应一个 Participant。它负责发现其他参与者、宣告自身的数据读写端点。
- 发布者 / 订阅者(Publisher / Subscriber):Participant 内部的数据写入与读取容器,分别管理一组 DataWriter 和 DataReader。
- 主题(Topic):数据的逻辑名称,如
/chatter,配合数据类型(如std_msgs/msg/String)唯一确定一条数据通道。 - QoS 策略:描述“数据怎么传”,决定可靠性、历史深度、持久性等行为,是发布端与订阅端能否匹配的关键。
下图展示了从域到数据读写的完整结构关系:
二、QoS 策略:决定通信能否匹配的关键
QoS 是 DDS 最强大也最容易踩坑的部分。发布端和订阅端各自声明 QoS,只有请求与提供兼容时才会建立连接。常见策略如下:
| QoS 策略 | 含义 | 常用取值 | 典型场景 |
|---|---|---|---|
| Reliability | 可靠性保证 | RELIABLE / BEST_EFFORT | 控制指令用 RELIABLE,传感器高频数据用 BEST_EFFORT |
| Durability | 历史数据是否保留给后加入者 | VOLATILE / TRANSIENT_LOCAL | 地图、参数用 TRANSIENT_LOCAL |
| History | 缓存多少条历史数据 | KEEP_LAST(n) / KEEP_ALL | KEEP_LAST(10) 最常见 |
| Depth | KEEP_LAST 的队列深度 | 整数,如 1、10、100 | 高频数据用小深度避免延迟 |
| Deadline | 数据发布周期约束 | 时间间隔 | 实时控制回路监控 |
| Liveliness | 发布者存活检测 | AUTOMATIC / MANUAL | 故障检测与快速切换 |
常见误区:发布端用 RELIABLE,订阅端用 BEST_EFFORT,二者可以匹配(订阅端要求更低);反之发布端 BEST_EFFORT、订阅端 RELIABLE 则无法匹配,表现为“话题存在但收不到数据”。排查时先用
ros2 topic info /chatter --verbose 查看两端 QoS。
三、数据读写流程:从 write 到 take
在 DDS 内部,数据写入与读取遵循固定路径:
- 发布端应用调用
DataWriter::write(),数据进入发送队列; - DDS 根据 QoS 决定是否重传、缓存多少条;
- 中间件通过发现机制找到匹配的 DataReader,将数据投递到对端;
- 订阅端 DataReader 收到数据后放入接收缓存;
- 应用通过
take()取走数据(取走后缓存删除),或用read()读取但保留缓存。
在 ROS 2 中,这一过程被封装为回调:订阅者注册 callback 后,DDS 收到数据即触发回调,无需手动 take。下面是一个典型的多机话题通信示例:
# 机器 A(192.168.1.10)
export ROS_DOMAIN_ID=42
ros2 run demo_nodes_cpp talker
# 机器 B(192.168.1.20)
export ROS_DOMAIN_ID=42
ros2 run demo_nodes_cpp listener
# 验证发现与 QoS
ros2 topic list
ros2 topic info /chatter --verbose
ros2 topic echo /chatter --qos-reliability reliable
四、案例与复盘:多机通信收不到数据的排查
假设两台机器在同一网段,ros2 topic list 能看到 /chatter,但 listener 始终无输出。按以下顺序复盘:
- 域是否一致:检查两端
ROS_DOMAIN_ID,不同域完全隔离。 - QoS 是否兼容:用
--verbose对比 Reliability 与 Durability。 - 主题名与类型是否一致:
/chatter与chatter不是同一主题;类型不匹配也不会匹配。 - 网络发现是否正常:多播被禁用时需配置单播发现或使用
ROS_STATIC_PEERS。 - 防火墙是否放行:DDS 使用动态端口,需放行对应 UDP 范围。
实践建议:多机调试时固定
ROS_DOMAIN_ID,并优先使用 RELIABLE + KEEP_LAST(10) + VOLATILE 这一组“通用兼容”QoS,可覆盖绝大多数话题通信场景。只有在需要“后加入者也能拿到历史数据”时,才改用 TRANSIENT_LOCAL。