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 策略:描述“数据怎么传”,决定可靠性、历史深度、持久性等行为,是发布端与订阅端能否匹配的关键。

下图展示了从域到数据读写的完整结构关系:

域 Domain(ROS_DOMAIN_ID = 0) 参与者 Participant A(节点 talker) 发布者 Publisher DataWriter → 主题 /chatter QoS:RELIABLE / KEEP_LAST(10) 订阅者 Subscriber DataReader ← 主题 /chatter QoS:RELIABLE / KEEP_LAST(10) 参与者 Participant B(节点 listener) 订阅者 Subscriber DataReader ← 主题 /chatter QoS:RELIABLE / KEEP_LAST(10) 发布者 Publisher DataWriter → 主题 /chatter QoS:RELIABLE / KEEP_LAST(10) 数据流 主题 /chatter

二、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 内部,数据写入与读取遵循固定路径:

  1. 发布端应用调用 DataWriter::write(),数据进入发送队列;
  2. DDS 根据 QoS 决定是否重传、缓存多少条;
  3. 中间件通过发现机制找到匹配的 DataReader,将数据投递到对端;
  4. 订阅端 DataReader 收到数据后放入接收缓存;
  5. 应用通过 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。