3. ROS 2.0 与 DDS:架构变化、中间件原理与 QoS 实战
好,咱们进入第三章。这一章我打算聊聊 ROS 2.0 和它背后的 DDS。
说实话,当年我从 ROS 1 切到 ROS 2 的时候,第一感觉就是「怎么变这么复杂了?」。以前写个话题,几行代码搞定。到了 ROS 2,突然冒出个 DDS,还有什么 QoS 策略。但用久了你会发现,这些复杂度换来的,是实实在在的可靠性。
3.1 ROS 2.0 架构到底变了什么?
先看一张图,你就明白 ROS 1 和 ROS 2 的核心区别在哪。
左边是 ROS 1,右边是 ROS 2。最大的变化是什么?去掉了 ROS Master。ROS 1 里所有节点都得先找 Master 注册,Master 挂了整个系统就瘫了。ROS 2 直接用 DDS 的发现机制,节点之间点对点通信,没有单点故障。
核心变化清单:
- 通信层:从 TCPROS/UDPROS 换成 DDS + RTPS
- 节点发现:从 Master 集中式 变成 DDS 分布式发现
- 实时性:从无保障 变成 可配置 QoS 策略
- 跨平台:从 Linux 为主 变成 Windows、macOS、嵌入式全支持
3.2 DDS 中间件原理——它到底在干什么?
DDS,全称 Data Distribution Service,数据分发服务。说白了,它就是一个「谁要数据谁订阅,谁有数据谁发布」的中间件。
我记得第一次接触 DDS 时,被它的概念搞晕了——什么 Domain、Topic、DataWriter、DataReader。后来我总结了一句话:DDS 就是一个超级智能的消息快递系统。
3.2.1 DDS 的核心角色
| 角色 | 类比 | 职责 |
|---|---|---|
| Domain | 快递公司 | 隔离通信域,不同 Domain 的节点互不可见 |
| Topic | 快递单号 | 标识数据类型和内容 |
| DataWriter | 寄件人 | 发布数据到 Topic |
| DataReader | 收件人 | 从 Topic 订阅数据 |
| QoS | 快递等级 | 控制可靠性、时效性、持久性等 |
你想想看,ROS 2 的 Node 其实就是在 DDS 的 Domain 里,通过 Topic 来交换数据。每个 Node 内部可以包含多个 DataWriter 和 DataReader。
3.2.2 RTPS 协议——DDS 的传输层
DDS 本身是一个规范,而 RTPS(Real-Time Publish-Subscribe)是它的具体实现协议。ROS 2 默认用的就是 RTPS。
RTPS 有几个关键机制:
- 自动发现:节点上线后,通过 SPDP(Simple Participant Discovery Protocol)互相发现
- 端到端通信:发现之后直接建立连接,不经过任何中心节点
- 多传输支持:UDP、TCP、共享内存,甚至 InfiniBand
我的经验:在嵌入式平台上,我建议优先用 UDP + 共享内存的组合。UDP 做跨设备通信,共享内存做同设备通信,延迟能降到微秒级。
3.3 QoS 策略配置实战
QoS,Quality of Service,服务质量。这是 ROS 2 相比 ROS 1 最大的杀手锏。
我曾经在一个项目中,两个节点之间丢包率高达 30%,查了半天发现是 QoS 不匹配——发布端用的是 BEST_EFFORT,订阅端却要求 RELIABLE。嗯,这种坑我踩过不止一次。
3.3.1 常用 QoS 策略一览
| 策略 | 选项 | 说明 |
|---|---|---|
| Reliability | RELIABLE / BEST_EFFORT | 是否保证数据送达 |
| Durability | VOLATILE / TRANSIENT_LOCAL | 晚订阅的节点能否收到历史数据 |
| History | KEEP_LAST / KEEP_ALL | 保留最近 N 条还是全部 |
| Depth | 整数 | KEEP_LAST 时保留的条数 |
| Deadline | 时间周期 | 数据发布的最大间隔 |
| Liveliness | AUTOMATIC / MANUAL | 节点是否还活着 |
3.3.2 代码实战:配置 QoS
下面是一个 C++ 的例子,展示如何配置 QoS 策略:
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<rclcpp::Node>("qos_demo");
// 创建 QoS 配置
rclcpp::QoS qos_profile(10); // depth = 10
qos_profile.reliable(); // 可靠性:RELIABLE
qos_profile.transient_local(); // 持久性:TRANSIENT_LOCAL
qos_profile.keep_last(10); // 保留最近 10 条
// 发布者
auto publisher = node->create_publisher<std_msgs::msg::String>(
"chatter", qos_profile);
// 订阅者(必须匹配相同的 QoS)
auto subscriber = node->create_subscription<std_msgs::msg::String>(
"chatter", qos_profile,
[](const std_msgs::msg::String::SharedPtr msg) {
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "收到: %s", msg->data.c_str());
});
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
避坑指南:我曾经在调试时发现订阅者收不到数据,打印日志发现 QoS 不兼容。ROS 2 的 QoS 策略必须发布端和订阅端完全匹配,否则连接会失败。建议用 rqt_qos 工具查看当前话题的 QoS 配置。
3.3.3 QoS 策略选择指南
不同场景选不同的 QoS,我一般这么选:
- 传感器数据(激光雷达、摄像头):BEST_EFFORT + KEEP_LAST(1),丢一帧无所谓,要的是低延迟
- 控制指令:RELIABLE + TRANSIENT_LOCAL,指令必须到达,晚到的节点也要拿到最新指令
- 日志/状态信息:RELIABLE + KEEP_ALL,每条都要,但可以接受稍慢
- 周期性心跳:BEST_EFFORT + LIVELINESS(AUTOMATIC),只关心节点是否活着
3.4 本章小结
ROS 2.0 的架构变化,说白了就是「去中心化 + DDS 加持」。DDS 提供了丰富的 QoS 策略,让你能精细控制通信行为。但这也意味着,你需要理解这些策略,否则就会踩坑。
我个人建议,刚开始接触 ROS 2 时,先用默认的 QoS 配置跑通流程。等遇到实际问题了,再回来调整 QoS。别一开始就搞复杂配置,容易把自己绕进去。