发布-订阅模型就像电台广播。电台(发布者)在特定频率上播放节目,听众(订阅者)只要调到这个频率就能收听到。电台不需要知道谁在听,听众也不需要告诉电台自己正在听。这种解耦让双方各自专注自己的事情。
技术定义:在DDS中,发布节点将数据写入"全局数据空间",订阅节点从中读取感兴趣的数据。双方通过主题(Topic)关联——主题就是那个"广播频率",发布者和订阅者通过主题名称匹配。
传统点对点通信要求发送方和接收方必须在同一时间在线,且双方代码中硬编码对方的地址。DDS的发布-订阅模型完全打破了这种耦合。发布者只管往全局数据空间里写数据,订阅者只管从里面读数据,彼此不需要知道对方的存在。
在我参与的一个多机器人协同项目中,四个机器人同时发布各自的里程计数据,一个中心节点订阅所有里程计做融合。如果采用传统Socket通信,每个机器人上线/下线都需要中心节点修改配置。用DDS,机器人上线后自动被发现,中心节点零配置就能收到数据。
想象教室里有块大黑板。任何学生都可以上去写内容(发布),任何学生都可以阅读黑板上的内容(订阅)。写的人不用指定谁来看,看的人也不用等写的人通知。DDS的全局数据空间就是这块"虚拟黑板",所有节点共享这个数据空间。
技术定义:全局数据空间(Global Data Space)是DDS中间件在逻辑上创建的共享数据层,所有节点通过域(Domain)进入同一个空间。同一域内的节点可以看到彼此发布的数据,不同域之间完全隔离。
DDS中的数据读写通过两个核心接口完成:
| 角色 | 所属节点 | 核心操作 | 对端感知 |
|---|---|---|---|
| DataWriter | 发布节点 | write(data) | 不知道谁在订阅 |
| DataReader | 订阅节点 | take() / 回调 | 不知道谁在发布 |
实际开发中,大部分团队直接使用ROS2封装的发布/订阅API(如create_publisher),底层自动创建对应的DataWriter和DataReader。但如果需要精细控制QoS或处理特殊数据流,了解这两个接口的差异会很有帮助。
如果发布者发送数据的速率是100Hz,但订阅者处理速度只有10Hz,会发生什么?数据会堆在缓冲区里,最终溢出丢弃。DDS通过QoS(服务质量)策略来解决这类冲突。QoS就像交通规则——它规定了数据从发布者到订阅者的"运输方式"和"交付标准"。
| QoS策略 | 作用 | 常见取值 | 典型场景 |
|---|---|---|---|
| RELIABILITY | 数据是否必须送达 | RELIABLE / BEST_EFFORT | 控制指令用RELIABLE,图像流用BEST_EFFORT |
| DURABILITY | 历史数据是否保留 | VOLATILE / TRANSIENT_LOCAL | 地图数据用TRANSIENT_LOCAL,实时状态用VOLATILE |
| HISTORY | 保留多少历史样本 | KEEP_LAST(n) / KEEP_ALL | 传感器数据用KEEP_LAST(1),日志用KEEP_ALL |
| DEADLINE | 数据最晚多久到达一次 | 具体时长 | 安全关键系统强制心跳检测 |
from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy, HistoryPolicy
# 配置QoS:可靠传输 + 保留最新5条历史 + 新订阅者能获取历史数据
qos = QoSProfile(
depth=5,
reliability=ReliabilityPolicy.RELIABLE,
durability=DurabilityPolicy.TRANSIENT_LOCAL,
history=HistoryPolicy.KEEP_LAST
)
# 发布者使用该QoS
pub = node.create_publisher(String, 'chatter', qos)
QoS不匹配是新手最容易踩的坑。发布者和订阅者的QoS必须兼容,否则通信静默失败——不报错,但数据就是到不了订阅者手中。规则很简单:订阅者要求的可靠性不能高于发布者提供的可靠性。例如发布者用BEST_EFFORT,订阅者要求RELIABLE,通信会失败。
TRANSIENT_LOCAL且depth>0,新订阅者会自动获取缓冲的历史数据。在实际项目中,如果资源受限(比如运行在STM32这类MCU上),建议选用Micro-XRCE-DDS作为DDS实现。它是专门为微控制器设计的轻量级DDS,内存占用可以控制在几十KB级别,通过客户端-代理(Client-Proxy)架构与完整DDS网络互通。我在一个搭载FreeRTOS的Cortex-M4项目中就采用这套方案,稳定运行了两年。
在传统网络中,节点A要跟节点B通信,需要先知道B的IP地址和端口号。在ROS2中,你只需要让两个节点使用相同的主题名和域ID,它们就能自动找到对方。这背后的魔法就是DDS的自动发现机制。
自动发现分为两个阶段:
自动发现机制让系统具备了"即插即用"的特性。节点加入网络时,不需要修改任何配置文件,不需要重启其他节点。我在产线调试时,经常同时运行6-7个终端,随时增减节点验证功能,体验比ROS1(依赖静态URI配置)流畅得多。
如果发现节点无法互相发现,大概率是以下原因之一:组播被路由器禁用、域ID不一致、防火墙屏蔽了UDP端口。排查时先用ros2 daemon status确认守护进程在运行,然后用ros2 node list检查节点是否被注册。
实验:观察QoS不匹配导致通信失败
ros2 run demo_nodes_cpp talker --qos-reliability reliableros2 run demo_nodes_cpp listener --qos-reliability best_effort这个实验直接验证QoS兼容性规则——订阅方的可靠性要求不能高于发布方。