4. 话题通信深入

话题(Topic)是 ROS2 中最常用的通信方式,采用“发布/订阅”模型实现节点间的异步数据流。它适合传感器数据、状态广播、控制指令等持续、单向、多对多的场景。本章从代码实现、消息定义、QoS 策略、多线程回调到实时性优化,逐层深入话题通信的工程细节。

一、核心原理:发布者与订阅者的数据通路

发布者通过 create_publisher 注册话题,订阅者通过 create_subscription 注册回调。二者并不直接建立连接,而是由 DDS 中间件根据话题名和 QoS 匹配后建立数据通道。发布者调用 publish() 将消息写入 DDS,订阅者由执行器(Executor)调度回调消费消息。理解这条通路,是排查“收不到数据”问题的关键。

ROS2 话题通信数据通路与 QoS 匹配 发布者节点 create_publisher publish(msg) QoS: Reliable DDS 中间件 话题名匹配 QoS 兼容性检查 序列化 / 传输 订阅者节点 create_subscription callback(msg) QoS: Reliable QoS 关键策略 Reliability RELIABLE / BEST_EFFORT Durability VOLATILE / TRANSIENT_LOCAL History KEEP_LAST / KEEP_ALL Depth 队列深度 匹配规则:发布者与订阅者的 QoS 必须兼容,否则不建立连接(如 RELIABLE 发布者无法匹配 BEST_EFFORT 订阅者)。

二、消息定义:接口即契约

ROS2 消息定义在 .msg 文件中,字段类型包括基础类型(int32、float64、string、bool)和复合类型(其他消息或数组)。消息是发布者与订阅者之间的“契约”,一旦发布,字段顺序和类型必须严格一致。

# 文件:msg/RobotStatus.msg
std_msgs/Header header
string robot_id
float32 battery_voltage
float32[] joint_positions
bool is_emergency

在 package.xml 中声明 rosidl_default_generators 依赖,在 CMakeLists.txt 中调用 rosidl_generate_interfaces 生成 C++/Python 代码。修改消息后必须重新编译,否则运行时会报类型不匹配。

三、QoS 配置:决定通信行为

QoS(Quality of Service)是 ROS2 区别于 ROS1 的核心特性。它通过策略组合控制可靠性、持久性和资源占用。下表列出常用策略及其适用场景:

策略 取值 适用场景
Reliability RELIABLE / BEST_EFFORT 控制指令用 RELIABLE;高频传感器用 BEST_EFFORT
Durability VOLATILE / TRANSIENT_LOCAL 地图、参数等“晚加入”数据用 TRANSIENT_LOCAL
History KEEP_LAST / KEEP_ALL 实时控制用 KEEP_LAST,深度 1~10
Deadline 时间周期 监控数据是否按时到达
Liveliness AUTOMATIC / MANUAL 检测发布者是否存活
建议:传感器话题使用 SensorDataQoS(BEST_EFFORT + KEEP_LAST 5),控制话题使用 ReliableQoS,静态地图使用 TransientLocalQoS。

四、多线程回调与实时性优化

默认单线程执行器(SingleThreadedExecutor)按顺序处理所有回调,一个耗时回调会阻塞其他话题。使用 MultiThreadedExecutor 并配合回调组(CallbackGroup)可实现并发:

auto executor = rclcpp::executors::MultiThreadedExecutor();
auto cb_group = create_callback_group(
    rclcpp::CallbackGroupType::Reentrant);

auto sub = create_subscription<Msg>(
    "topic", qos,
    [this](Msg::SharedPtr msg){ /* 耗时处理 */ },
    rclcpp::SubscriptionOptions().callback_group = cb_group);

executor.add_node(shared_from_this());
executor.spin();

实时性优化要点:

  • 将高频传感器回调与低频控制回调放入不同回调组,避免相互阻塞。
  • 回调内避免动态内存分配、日志打印和阻塞 I/O。
  • 使用 KEEP_LAST 且深度设为 1,减少队列延迟。
  • 对时间敏感任务,可设置线程优先级或使用实时内核。
误区:认为“多线程一定更快”。若回调间共享数据且未加锁,会引发数据竞争;若回调组配置为 MutuallyExclusive,则同一组内仍串行执行。

五、案例与复盘:高频激光雷达数据丢失

某机器人订阅 10 Hz 激光雷达话题,发现偶尔丢帧。排查发现:订阅者使用默认 RELIABLE + KEEP_LAST 10,而发布者使用 BEST_EFFORT。QoS 不兼容导致部分数据被丢弃。将订阅者改为 SensorDataQoS 后恢复正常。复盘结论:发布者与订阅者的 QoS 必须成对设计,不能只改一端。

另一个常见问题是回调阻塞:导航节点在单线程执行器中同时订阅地图和里程计,地图回调耗时 200 ms,导致里程计回调延迟。改用多线程执行器并将两者放入不同回调组后,里程计延迟降至 5 ms 以内。

工程建议:先用 ros2 topic info /topic --verbose 查看 QoS 匹配情况,再用 ros2 topic hz 和 ros2 topic delay 量化实时性,最后决定是否引入多线程或调整 QoS。

掌握发布/订阅实现、消息契约、QoS 策略与多线程回调,是构建稳定、低延迟 ROS2 话题通信的基础。实际工程中应结合场景选择 QoS,并通过工具持续观测通信质量。