3.2 话题服务动作三大通信模型


3.2 话题、服务、动作三大通信模型

本节摘要:话题、服务、动作构成 ROS2 数据通信的三种基本形状:持续数据流、瞬时问答、长时事务。本节在 3.1 调度认知之上,给出三者的精确语义、命令行演练与选型三问,重点拆解动作的三段结构——它是初学者最陌生、也最容易与.service 误用的一环,更是第 6 章导航与机械臂栈的通用接口。

选型比写法更重要

三种通信模型的 API 写法,官方教程几页纸就能讲完;真正拉开工程差距的是选型——把长时任务塞进服务、把一问一答架在话题上,是真实项目里最常见的两类返工。本节按"语义、演练、选型"的顺序走,最后给一份从需求出发的决策框架。

一、话题:为持续数据流而生

话题是发布订阅模式:发布者向主题喂数据,订阅者从主题收数据,双方互不知道对方是谁、有几个。它的语义特征有三:单向(数据只从发布端流向订阅端)、多对多(一个主题可以挂任意多发布者与订阅者)、无回执(发布者不知道谁收到了、收到没有)。

这些特征决定了话题的适用域:传感器流、状态广播、指令下发——凡是"数据在生产、消费者来去自由"的场景。反过来的信号是:一旦你需要"对方确认收到"或"返回处理结果",话题就是错的工具,哪怕技术上可行。

命令行是理解话题语义最快的路径,不用写一行代码就能完成一次完整收发:

# 终端一:手动发布,--rate 指定频率 ros2 topic pub /chatter std_msgs/msg/String "{data: '你好'}" --rate 2 # 终端二:订阅并查看 ros2 topic echo /chatter # data: 你好 # data: 你好 # 旁路诊断三板斧 ros2 topic hz /chatter # 实测频率,查抖动 ros2 topic bw /chatter # 实测带宽,评估负载 ros2 topic info /chatter # 查两端数量与 QoS(第2章的老朋友)

💡 关键直觉:把 topic pub 当成系统的"数字信号发生器"。调试下游节点时,用一个手动 pub 顶替上游真实数据源,是隔离问题的标准手法——第 6 章调导航时我们会频繁使用它。

二、服务:一次请求,一次响应

服务是请求响应模式:客户端发请求,服务器处理后回响应,一来一回,同步闭环。它与话题的本质差异是有回执——调用方明确知道"结果是什么、成功没有"。代价是语义上的约束:请求应当"快速完成",因为客户端通常在等。

服务的命名空间里有类型与名字两个维度,命令行同样能空手演练:

# 查看系统里现有的服务类型定义 ros2 interface show std_srvs/srv/SetBool # bool data # --- # bool success # string message # 手动调用一次(谁的服务器开着就调谁) ros2 service call /set_flag std_srvs/srv/SetBool "{data: true}" # success: true # message: 标志已设置

代码侧要留心的是 C++ 与 Python 的同步调用差异:Python 的 call 同步版本会阻塞执行线程——还记得 3.1 的警告吗?单线程节点里在回调里同步调服务是自锁行为,应当用异步调用加完成回调。C++ 侧同理,推荐 spin_until_future_complete 或回调式客户端,别在回调里硬等。

三、动作:为长时任务设计的三段式

动作是最复杂也最容易被误用的一环。先立语义:动作用于发起后需要较长时间完成、过程值得汇报、中途可能取消的任务——导航去一个点、机械臂执行轨迹、相机扫描一圈。它把一次任务拆成三段交互:目标(发任务、可被接受或拒绝)、反馈(执行期间的周期性进度)、结果(终态与结论)。

一个实现事实能帮你理解它的构造:一个动作服务器在底层就是一个话题(目标与反馈通道)加两个服务(发目标与取消)的组合封装。所以它同时具备两者的性格——反馈像话题一样持续流动,目标接受像服务一样有确认。

# 客户端发送目标并挂三个回调:响应、反馈、结果 from example_interfaces.action import Fibonacci def feedback_cb(msg): print('进度:', msg.partial_sequence[-1]) future = action_client.send_goal_async(Fibonacci.Goal(order=10)) goal_handle = rclpy.spin_until_complete(node, future).result() def result_cb(fut): print('完成,序列长度:', len(fut.result().sequence)) goal_handle.get_result_async().add_done_callback(result_cb)

命令行同样能直接操作动作,第 6 章导航调试会反复用到这一条:

ros2 action send_goal /navigate_to_pose nav_msgs/action/NavigateToPose \ "{pose: {header: {frame_id: map}, pose: {position: {x: 2.0, y: 1.0}}}}" --feedback

图 3-2:三种通信模型的形状对照

图 3-2:三种通信模型的形状对照

四、选型三问与一组误用对照

拿到需求,按顺序问三个问题。**数据是持续产生的吗?**是,话题,结束。**需要拿到返回结果吗?**不需要,话题;需要,进入第三问。**这个结果是"瞬间"能给出的吗?**是,服务;需要较长时间且过程值得跟踪,动作。

误用 正解 代价
用话题发指令并等待生效 服务或动作 指令丢失无感知,重试逻辑散落各处
用服务执行 30 秒的建图 动作 客户端长阻塞,超时与取消无从谈起
用动作查询当前速度 话题或服务 协议样板翻倍,调用方维护目标句柄
一个话题挂多个语义不同的发布者 拆分主题 消费端被迫解析来源,QoS 无法分开调

第三行值得展开一句:动作不是"更高级所以更好",它的三段结构对简单查询是纯粹的样板负担。原语没有高低,只有合不合身。

⚠️ 常见坑:服务与动作的接口文件里,请求与响应字段用三条横线分隔——写接口时把结果字段误写在横线上方,编译不报错、运行时却发现"响应里没有数据",这类静态可查的错误建议用 interface show 先确认再写代码。

本节要点回顾

  • 话题为持续流服务,无回执、多对多;需要确认就换工具;
  • 服务为瞬时问答服务,有回执但要快;回调里禁止同步等待;
  • 动作为长时任务服务,目标、反馈、结果三段式,底层是话题加服务的组合;
  • 选型三问:持续吗、要回执吗、耗时长吗,三问定原语;
  • topic pub 与 service call 是隔离调试的标准道具,先用它们顶替真实节点。

第四种通信原语——参数——有着自己的运行时性格,下一节见。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U