4. 话题通信基础:话题模型、发布者与订阅者、消息类型
4.1 话题通信的核心模型
话题(Topic)通信是ROS2中最基础、最常用的数据交换方式,其本质是一种异步、单向、多对多的发布/订阅模式。与ROS1不同,ROS2的话题通信基于DDS(Data Distribution Service)中间件实现,去掉了中心化的Master节点,节点之间通过DDS的发现机制自动建立连接。
话题通信模型包含三个核心角色:
- 发布者(Publisher):负责向指定话题发送消息,不关心谁在接收。
- 订阅者(Subscriber):负责监听指定话题并接收消息,不关心消息从哪来。
- 话题(Topic):发布者与订阅者之间的“数据管道”,以字符串名称标识,并绑定一种固定的消息类型。
下图展示了ROS2话题通信的典型结构,其中发布者节点将消息推送到话题,订阅者节点从话题中拉取消息,整个过程无需双方直接建立连接。
4.2 消息类型(Message Type)
话题上传输的数据必须遵循预定义的结构化格式,即消息类型。消息类型由包名/消息名组成,例如 std_msgs/msg/String、geometry_msgs/msg/Twist。ROS2中消息类型定义在 .msg 文件中,每个字段由“类型 + 字段名”构成。
常用消息类型示例:
| 消息类型 | 所属包 | 典型字段 | 应用场景 |
|---|---|---|---|
std_msgs/msg/String |
std_msgs | string data |
文本日志、简单状态通知 |
geometry_msgs/msg/Twist |
geometry_msgs | Vector3 linear, Vector3 angular |
机器人速度控制指令 |
sensor_msgs/msg/LaserScan |
sensor_msgs | float32[] ranges, float32 angle_min |
激光雷达数据 |
nav_msgs/msg/Odometry |
nav_msgs | PoseWithCovariance pose, TwistWithCovariance twist |
机器人里程计信息 |
查看消息类型的详细结构,可在终端使用命令:
ros2 interface show geometry_msgs/msg/Twist
# 输出示例:
# Vector3 linear
# Vector3 angular
4.3 编写发布者与订阅者(Python实现)
下面通过一个完整的示例演示如何创建发布者和订阅者节点。假设我们要发布一个字符串话题 /hello_topic,消息类型为 std_msgs/msg/String。
4.3.1 创建发布者节点
# publisher_node.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class SimplePublisher(Node):
def __init__(self):
super().__init__('simple_publisher')
# 创建发布者:话题名 /hello_topic,消息类型 String,队列长度 10
self.publisher_ = self.create_publisher(String, '/hello_topic', 10)
self.timer = self.create_timer(1.0, self.timer_callback) # 每秒触发一次
self.count = 0
def timer_callback(self):
msg = String()
msg.data = f'Hello ROS2: {self.count}'
self.publisher_.publish(msg)
self.get_logger().info(f'发布: {msg.data}')
self.count += 1
def main(args=None):
rclpy.init(args=args)
node = SimplePublisher()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
4.3.2 创建订阅者节点
# subscriber_node.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class SimpleSubscriber(Node):
def __init__(self):
super().__init__('simple_subscriber')
# 创建订阅者:话题名 /hello_topic,消息类型 String,队列长度 10
self.subscription = self.create_subscription(
String, '/hello_topic', self.listener_callback, 10)
def listener_callback(self, msg):
self.get_logger().info(f'收到: {msg.data}')
def main(args=None):
rclpy.init(args=args)
node = SimpleSubscriber()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
4.3.3 运行与验证
分别打开两个终端,先运行发布者,再运行订阅者:
# 终端1
python3 publisher_node.py
# 终端2
python3 subscriber_node.py
此时订阅者终端会持续打印收到的消息。也可以使用ROS2命令行工具直接查看话题数据:
ros2 topic list # 查看所有活跃话题
ros2 topic echo /hello_topic # 监听并打印话题内容
ros2 topic info /hello_topic # 查看话题类型、发布者/订阅者数量
4.4 队列长度与QoS策略
在创建发布者或订阅者时,最后一个参数队列长度(Queue Size)并非随意设置,它直接影响通信的可靠性。队列长度表示在接收方来不及处理时,ROS2在内存中暂存多少条消息。
- 队列长度 = 10:适合传感器数据流(如图像、激光),允许丢弃旧数据,保证实时性。
- 队列长度 = 100 或更大:适合低频但重要的控制指令或日志,避免丢失。
- 队列长度 = 1:只保留最新一条消息,适用于实时性要求极高的场景。
ROS2还提供了更精细的QoS(Quality of Service)策略,用于控制消息的可靠性(Reliable vs Best Effort)、历史记录策略等。例如,传感器数据通常使用 Best Effort 模式,而控制指令使用 Reliable 模式:
from rclpy.qos import QoSProfile, ReliabilityPolicy
qos = QoSProfile(depth=5, reliability=ReliabilityPolicy.BEST_EFFORT)
self.subscription = self.create_subscription(
String, '/hello_topic', self.callback, qos)
Reliable,订阅者使用 Best Effort,则通信会降级为 Best Effort;若发布者使用 Best Effort,订阅者使用 Reliable,则通信将无法建立。在调试话题不通时,优先检查双方的QoS设置。
4.5 案例:控制机器人运动
下面结合一个实际场景,演示话题通信在机器人控制中的应用。假设我们有一个差速驱动机器人,需要发布速度指令到 /cmd_vel 话题,消息类型为 geometry_msgs/msg/Twist。
# cmd_vel_publisher.py
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
class CmdVelPublisher(Node):
def __init__(self):
super().__init__('cmd_vel_publisher')
self.publisher_ = self.create_publisher(Twist, '/cmd_vel', 10)
self.timer = self.create_timer(0.1, self.publish_cmd) # 10Hz
def publish_cmd(self):
twist = Twist()
twist.linear.x = 0.2 # 前进速度 0.2 m/s
twist.angular.z = 0.5 # 角速度 0.5 rad/s
self.publisher_.publish(twist)
def main(args=None):
rclpy.init(args=args)
node = CmdVelPublisher()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
在真实机器人或仿真环境中,底盘驱动节点会订阅 /cmd_vel 话题,将速度指令转换为轮子转速。这种“发布指令-执行机构订阅”的模式是ROS2机器人应用中最常见的架构。
4.6 误区与复盘
| 常见误区 | 问题描述 | 正确做法 |
|---|---|---|
| 话题名拼写不一致 | 发布者使用 /cmd_vel,订阅者使用 /cmdvel,导致收不到数据 |
使用 ros2 topic list 确认实际话题名 |
| 消息类型不匹配 | 发布 Twist,订阅 String,编译不报错但运行时不通信 |
用 ros2 topic info /topic 查看类型是否一致 |
忘记调用 rclpy.spin() |
回调函数不执行,订阅者收不到消息 | 确保节点进入事件循环,或使用 MultiThreadedExecutor |
| QoS不兼容 | 发布者 Best Effort,订阅者 Reliable,连接失败 |
统一QoS策略,或使用默认的 Reliable |
复盘建议:当话题通信出现问题时,按照以下顺序排查:① ros2 topic list 确认话题存在;② ros2 topic info 确认类型匹配;③ ros2 topic echo 确认数据在流动;④ 检查两端节点的QoS策略;⑤ 确认节点都在运行且没有崩溃。
话题通信是ROS2机器人开发的地基,掌握发布者、订阅者、消息类型三要素,并理解队列与QoS的影响,后续学习服务、动作、参数等机制时就能触类旁通。