ROS 中的三种通信方式(Topic、Service、Action),包括它们使用的通信文件、代码示例、区别和使用场景。
1. Topic(话题)
- 通信方式: 单向通信,类似于广播。
- 通信文件:
.msg
文件。 - 工作原理:
- 发布者(Publisher)发布消息到 Topic。
- 订阅者(Subscriber)从 Topic 接收消息。
- 特点:
- 单向传输,没有返回值。
- 适合持续的数据流(如传感器数据)。
- 代码示例:
# Publisher import rospy from std_msgs.msg import String rospy.init_node('publisher_node') pub = rospy.Publisher('chatter', String, queue_size=10) rate = rospy.Rate(1) # 1Hz while not rospy.is_shutdown(): pub.publish("Hello, World!") rate.sleep() # Subscriber def callback(msg): rospy.loginfo("I heard: %s", msg.data) rospy.init_node('subscriber_node') rospy.Subscriber('chatter', String, callback) rospy.spin()
- 使用场景:
- 传感器数据(如激光雷达、摄像头数据)。
- 机器人状态信息(如位置、速度)。
- 任何需要持续传输数据的场景。
2. Service(服务)
- 通信方式: 双向通信,类似于函数调用。
- 通信文件:
.srv
文件。 - 工作原理:
- 客户端(Client)发送请求。
- 服务器(Server)处理请求并返回响应。
- 特点:
- 一次性请求/响应。
- 适合短时间任务调用。
- 代码示例:
# Server import rospy from example_srv.srv import AddTwoInts, AddTwoIntsResponse def handle_add(req): return AddTwoIntsResponse(req.a + req.b) rospy.init_node('add_two_ints_server') rospy.Service('add_two_ints', AddTwoInts, handle_add) rospy.spin() # Client import rospy from example_srv.srv import AddTwoInts, AddTwoIntsRequest rospy.init_node('add_two_ints_client') rospy.wait_for_service('add_two_ints') add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts) req = AddTwoIntsRequest(a=1, b=2) resp = add_two_ints(req) rospy.loginfo("Sum: %d", resp.sum)
- 使用场景:
- 执行简单任务(如加法计算)。
- 调用某个功能(如开关设备)。
- 任何需要请求-响应模式的场景。
3. Action(动作)
- 通信方式: 双向通信 + 实时反馈。
- 通信文件:
.action
文件。 - 工作原理:
- 客户端(Client)发送目标(Goal)。
- 服务器(Server)执行任务,并实时发送反馈(Feedback)。
- 任务完成后,服务器返回结果(Result)。
- 特点:
- 支持长时间任务。
- 支持任务取消和实时反馈。
- 代码示例:
# Action Server import rospy import actionlib from example_action.msg import FibonacciAction, FibonacciFeedback, FibonacciResult def execute_cb(goal): feedback = FibonacciFeedback() result = FibonacciResult() sequence = [0, 1] for i in range(1, goal.order): sequence.append(sequence[i] + sequence[i-1]) feedback.sequence = sequence server.publish_feedback(feedback) result.sequence = sequence server.set_succeeded(result) rospy.init_node('fibonacci_server') server = actionlib.SimpleActionServer('fibonacci', FibonacciAction, execute_cb, False) server.start() rospy.spin() # Action Client import rospy import actionlib from example_action.msg import FibonacciAction, FibonacciGoal def feedback_cb(feedback): rospy.loginfo("Feedback: %s", feedback.sequence) rospy.init_node('fibonacci_client') client = actionlib.SimpleActionClient('fibonacci', FibonacciAction) client.wait_for_server() goal = FibonacciGoal(order=10) client.send_goal(goal, feedback_cb=feedback_cb) client.wait_for_result() rospy.loginfo("Result: %s", client.get_result())
- 使用场景:
- 长时间任务(如导航、路径规划)。
- 需要实时反馈的任务(如机器人移动中的位置反馈)。
- 支持任务取消的场景。
对比总结
特性 | Topic (msg) | Service (srv) | Action (action) |
---|---|---|---|
通信模式 | 单向(发布/订阅) | 双向(请求/响应) | 双向(Goal/Result) + 反馈(Feedback) |
实时性 | 持续数据流 | 一次性请求/响应 | 长时间任务 + 实时反馈 |
是否支持反馈 | 不支持 | 不支持 | 支持 |
是否支持取消 | 不支持 | 不支持 | 支持 |
适用场景 | 传感器数据、状态信息 | 简单任务调用 | 复杂任务(如导航、抓取) |
通俗解释
- Topic:就像广播电台,发布者不断发送消息,订阅者只能听,不能回复。
- Service:就像打电话,客户端打电话给服务器,服务器接电话并回复。
- Action:就像点外卖,客户端下单(Goal),商家实时更新配送进度(Feedback),最后送到家(Result)。如果不想等了,还可以取消订单(Cancel)。
在进行ros通信时,需要首先定义消息文件,即.msg .srv .action文件,里面储存着消息的格式,在实际开发中需要我们设计消息的结构体,部署到项目代码中
一、自定义消息开发的通用注意事项
1. 语义清晰性
- 字段命名:避免歧义,直接表达意图(如
target_pose
而非goal
,obstacle_distance
而非distance
)。 - 单位统一:明确标注单位(如
meters
、radians
、seconds
),避免混合单位导致计算错误。 - 状态枚举:使用
uint8
+ 常量定义状态(如SUCCESS=0, FAILURE=1
),而非纯数字。
2. 数据结构优化
- 复用标准消息:优先使用
geometry_msgs
、std_msgs
等ROS内置类型(如Point
、Quaternion
)。 - 避免冗余数据:
# 反面案例:重复传输坐标系信息 x = float32 # 在 map 坐标系下 y = float32 # 在 map 坐标系下 frame_id = string # 冗余,header 中已包含 # 优化方案:使用 PoseStamped header = Header pose = Pose
- 数组长度控制:若数据量过大(如点云),使用分片(
chunked
)或压缩格式(如sensor_msgs/CompressedImage
)。
3. 兼容性与版本管理
- 字段增删规则:仅允许向后兼容的修改(新增字段必须带默认值,禁止删除或修改现有字段)。
- 语义版本号:遵循
MAJOR.MINOR.PATCH
,破坏性变更需升级MAJOR
版本(如PathV1.msg
→PathV2.msg
)。 - 弃用策略:通过文档标注弃用字段,逐步迁移而非强制删除。
4. 性能与效率
- 零拷贝设计:在C++中使用
const&
或shared_ptr
避免数据复制。 - 紧凑数据布局:避免嵌套过深的消息结构(如多层
Array
嵌套)。 - 二进制兼容性:确保消息序列化后在不同平台(x86/ARM)解析一致。
5. 可扩展性
- 预留扩展字段:添加
metadata
(如string json_metadata
)存储自定义参数。 - 位掩码标记:使用
uint8 flags
+ 位运算表示多个布尔状态(如FLAG_EMERGENCY=0x01
)。
6. 文档与测试
- 必含注释:每个字段需说明用途、单位、取值范围(如
# Range: [0.0, 1.0]
)。 - 示例消息:在文档中提供典型场景的
.msg
文件示例。 - 兼容性测试:验证新旧版本消息的序列化/反序列化兼容性。
二、典型机器人业务与优秀消息设计案例
1. 导航与路径规划
- 典型消息:
nav_msgs/Path
- 结构:
Header
+geometry_msgs/PoseStamped[] poses
- 优点:
- 复用
PoseStamped
包含时间戳和坐标系 - 路径点序列清晰,支持插值算法
- 复用
- 扩展设计:可添加
velocity_profile
数组表示速度建议。
- 结构:
2. 机械臂控制
- 典型消息:
trajectory_msgs/JointTrajectory
- 结构:
Header
+string[] joint_names
+JointTrajectoryPoint[] points
- 优点:
- 动态绑定关节名与轨迹点,支持不同机械臂
- 每个轨迹点包含位置、速度、加速度、时间戳
- 扩展设计:添加
torque_profile
表示力矩约束。
- 结构:
3. SLAM与地图构建
- 典型消息:
nav_msgs/OccupancyGrid
- 结构:
Header
+MapMetaData
+int8[] data
- 优点:
- 使用一维数组存储二维栅格地图,内存紧凑
MapMetaData
包含分辨率、原点,支持坐标转换
- 扩展设计:添加
probability
字段表示置信度。
- 结构:
4. 多传感器融合
- 典型消息:
sensor_msgs/PointCloud2
- 结构:
Header
+uint32 height/width
+PointField[] fields
+uint8[] data
- 优点:
- 支持灵活的点云格式(XYZ、RGB、强度等)
- 通过
fields
动态描述数据布局,兼容不同激光雷达
- 扩展设计:结合
CompressedImage
实现压缩传输。
- 结构:
5. 任务调度与状态监控
- 典型消息:
actionlib_msgs/GoalStatus
- 结构:
uint8 status
+string text
+GoalID goal_id
- 优点:
- 状态码标准化(
SUCCEEDED
、ABORTED
) - 通过
text
提供人类可读的调试信息
- 状态码标准化(
- 结构:
三、优秀设计的核心原则总结
设计维度 | 优秀实践 | 反面案例 |
---|---|---|
字段命名 | target_joint_angles | data_array |
数据结构 | 使用 PoseStamped 替代独立 x, y, theta | 重复定义坐标系 |
性能优化 | 分片传输点云(chunked ) | 单消息传输10万个点 |
兼容性 | 新增 optional_field 并设置默认值 | 删除旧字段 required_field |
文档 | 提供字段取值范围(如 # Range: [0, 100] ) | 无注释或仅写“速度值” |
四、最佳实践工具链
- 消息验证工具:使用
rosmsg
检查消息结构,或自定义脚本验证数据合法性。 - IDL转换工具:通过
rosidl
生成不同语言的消息代码,或转换为 Protobuf/JSON。 - 性能分析:用
rostopic bw
监控消息带宽,用rqt_plot
可视化实时数据。
通过以上设计原则和典型案例,开发者可以构建高鲁棒性、高效且易维护的ROS消息接口,满足复杂机器人系统的长期需求。