3. ROS 2.0 与 DDS

好,咱们进入第三章。这一章我打算聊聊 ROS 2.0 和它背后的 DDS。

说实话,当年我从 ROS 1 切到 ROS 2 的时候,第一感觉就是「怎么变这么复杂了?」。以前写个话题,几行代码搞定。到了 ROS 2,突然冒出个 DDS,还有什么 QoS 策略。但用久了你会发现,这些复杂度换来的,是实实在在的可靠性。

3.1 ROS 2.0 架构到底变了什么?

先看一张图,你就明白 ROS 1 和 ROS 2 的核心区别在哪。

ROS 1 vs ROS 2 架构对比 ROS 1 架构 Node (roscpp / rospy) ROS Master (XMLRPC) TCPROS / UDPROS 单点故障 · 无 QoS · 实时性弱 ROS 2 架构 Node (rclcpp / rclpy) DDS 中间件层 RTPS (实时发布订阅协议) 去中心化 · 丰富 QoS · 硬实时

左边是 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。别一开始就搞复杂配置,容易把自己绕进去。