news 2026/9/10 7:30:19

ROS2服务通信详解:从srv接口到工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ROS2服务通信详解:从srv接口到工程实践

1. 为什么机器人开发不能只靠“一问一答”

先抛一个场景:你在跑一个 ROS2 的导航程序,机器人走到一半,你突然想知道当前里程计累计走了多少米,或者想让某个传感器立即回传一次数据。这种场景下,用话题(Topic)去广播一轮,太啰嗦;把数据存在参数服务器里,又不够实时。ROS2 给的标准答案是:服务(Service)

服务是 ROS2 四大核心通信机制之一,其余三个分别是话题、动作(Action)和参数。如果你刚接触 ROS2,可能已经把话题玩得很溜了,但服务这块往往容易模糊——它到底和话题有什么区别?什么时候该用服务而不是话题?服务的数据格式怎么定义?服务调用超时怎么办?这些问题我当初都踩过坑,这篇文章就把服务这个核心概念彻底拆开讲清楚。

这篇文章适合谁看:正在学 ROS2 的初学者、已经从 ROS1 迁移到 ROS2 的开发者、以及想搞清楚服务通信底层原理的机器人工程师。我会从服务的设计思路讲到代码实现,再讲排查技巧,最后给出一套可以直接抄作业的最佳实践。不管你是用 Humble 还是 Iron 版本,核心思路完全一致。

2. 服务通信的设计思路与话题的分工逻辑

2.1 服务是什么:一次请求,一次响应

服务的本质是同步的请求-响应模式。一个节点扮演服务端(Service Server),负责接收请求并返回结果;另一个节点扮演客户端(Service Client),负责发起请求并等待响应。整个过程是短连接式的:客户端发一个请求,服务端处理完,返回一个响应,通信结束。

这和话题完全不同。话题是发布-订阅模式,发布者持续广播,订阅者被动接收,双方不需要知道对方是谁。而服务是点对点通信,客户端必须明确知道服务端的存在,并指定服务名称来发起调用。

我习惯用一个生活化类比:话题像是电台广播,你打开收音机就能听,但没人管你有没有听到;服务像是打电话,你拨号、对方接听、你问一句、对方答一句,整个对话有来有回。前者适合持续数据流,比如传感器数据、速度指令;后者适合偶发的、需要确认结果的操作,比如“拍一张照片”“计算一个路径”“读取当前状态”。

2.2 为什么不用话题硬扛所有通信

很多初学者会问:既然话题这么灵活,为什么还要服务?我用一个真实例子说明。假设你写了一个机器人底盘驱动节点,需要支持“原地旋转90度”这个操作。如果用话题实现,你得定义两个话题:一个发指令、一个回报结果。麻烦在于结果怎么判断?你得在回报话题里不断监听状态,再自己维护一套状态机来判断动作是否完成。如果是服务,整个过程就是:调用服务、传入旋转角度、服务端执行完返回一个布尔值,告诉你成功还是失败。代码逻辑清晰得多。

再考虑一个更本质的问题:话题的消息是单向的、无状态的。服务天然具备事务性——调用要么成功,要么失败,这是工程上非常需要的语义。比如机器人请求一个抓取任务,如果抓取失败,客户端能立即得到反馈并决定下一步策略,而不需要靠超时去猜。

2.3 服务在什么场景下是首选

根据我的实际项目经验,下面这些场景优先选服务:

  • 查询类操作:获取当前电池电量、获取机器人位姿、获取地图中的某个坐标点信息。
  • 控制类操作:打开/关闭某个执行器、切换到某个导航目标点、启动/停止某个算法模块。
  • 计算类操作:路径规划、点云处理、图像特征提取。
  • 需要确认结果的操作:比如“保存地图”这种任务,你必须知道保存是否成功。

如果是连续的数据流、高频的周期性数据、一对多的广播场景,那还是老老实实用话题。服务和话题没有孰优孰劣,只有合不合适。

3. 核心细节解析:服务接口定义与通信协议层

3.1 服务接口的构成:请求与响应

服务的通信内容由srv 文件定义。一个 srv 文件由三部分组成:请求部分(Request)、分隔符---、响应部分(Response)。以 ROS2 内置的标准服务AddTwoInts为例,它的定义就是这样:

int64 a int64 b --- int64 sum

请求部分有两个字段ab,响应部分有一个字段sum。这个接口绝对清晰:客户端传入两个整数,服务端返回它们的和。

在实际项目中,你经常会自定义服务接口。比如设计一个“获取机器人当前角度”的服务,srv 文件可以这样写:

# 请求部分,可以留空 --- # 响应部分 float32 yaw float32 pitch float32 roll

请求部分留空意味着客户端只需要触发调用,不需要传任何参数。这种设计在处理“查询当前状态”类需求时特别好用。

3.2 接口命名规范与包管理

自定义 srv 接口时,建议放在独立的接口包中,比如my_robot_interfaces。这样做的好处是:客户端和服务端节点都依赖同一个接口包,避免了接口定义不一致导致的诡异问题。接口包的结构通常长这样:

my_robot_interfaces/ ├── CMakeLists.txt ├── package.xml ├── msg/ │ └── MyMessage.msg └── srv/ └── GetRobotState.srv

CMakeLists.txt中需要添加依赖,并显式声明要生成的 srv 文件:

find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "srv/GetRobotState.srv" )

package.xml中添加:

<build_depend>rosidl_default_generators</build_depend> <exec_depend>rosidl_default_generators</exec_depend> <member_of_group>rosidl_interface_packages</member_of_group>

这一步最容易出问题。很多初学者把 srv 文件写好了,但忘了改CMakeLists.txt里的rosidl_generate_interfaces参数,结果编译后找不到接口。编译接口包时,最好在终端里用colcon build --packages-select my_robot_interfaces单独构建该包,看到[Processing: my_robot_interfaces]相关的日志后再构建其他包。

3.3 服务通信协议层:底层到底是怎么传的

ROS2 服务通信的协议层基于RMW(ROS Middleware)实现,默认的 RMW 是DDS(Data Distribution Service)。DDS 本身就是一套完整的发布-订阅通信中间件,ROS2 的很多特性(比如 QoS、动态发现)其实都来自 DDS。服务通信在 DDS 的框架下,本质上也是借助 DDS 的底层数据通道来实现请求和响应的配对。

服务通信在协议栈上有一个重要特性:底层使用临时话题来实现请求和响应。当服务被创建时,RMW 会自动为这个服务创建两个内部话题:一个用于客户端发送请求,一个用于服务端返回响应。这个细节解释了为什么服务调用有时看起来不太像一个“直接的连接”——因为它确实不是同一条通道上完成的请求响应,而是通过两个隐藏的话题协作完成的。

对普通开发者来说,你不需要亲自处理这些底层细节,但理解这一点能帮助你排查一些奇怪的问题。比如,如果你在ros2 topic list里看到一些类似/service_name/_service_requests的内部话题,不要惊讶,那是服务通信协议层的正常表现。

3.4 QoS 策略在服务里的特殊规则

服务通信有自己的 QoS 策略规则,这一点经常被忽略。对于服务端,请求的 QoS 和响应的 QoS 是独立的;对于客户端,也一样。默认情况下,绝大多数 RMW 实现为服务配置的是transient localreliable的组合。

我记得有一次在工程现场,客户端调用服务频繁超时,后来发现是我把服务端的 QoS 历史深度策略改成了KEEP_LAST且队列深度设为 1,而客户端那边设置的请求频率又特别高,导致请求在 DDS 层被丢弃。通常不建议手动修改服务的 QoS 策略,保持默认值是最稳妥的。这个话题很深,但你现在只需要记住:服务的 QoS 配置比话题更敏感,没有充分理由不要改它

4. 实操过程:用 Python 和 C++ 实现一个完整服务通信

4.1 环境准备与项目结构

在动手写代码前,先确认你的 ROS2 环境已经装好。这里以 Ubuntu 22.04 搭配 ROS2 Humble 为例。安装完成后的验证命令:

source /opt/ros/humble/setup.bash ros2 --version

创建工作空间并创建功能包:

mkdir -p ~/ros2_ws/src cd ~/ros2_ws/src ros2 pkg create --build-type ament_python py_srv_demo ros2 pkg create --build-type ament_cmake cpp_srv_demo

如果你打算先做接口包,就在同一个src目录下创建接口包:

ros2 pkg create --build-type ament_cmake my_robot_interfaces

4.2 服务端实现:Python 版

我用 Python 写一个加法服务端,使用内置的AddTwoInts服务接口。这个例子虽然简单,但能覆盖服务端开发的所有关键点。

创建py_srv_demo/py_srv_demo/server.py文件:

import rclpy from rclpy.node import Node from example_interfaces.srv import AddTwoInts class AddTwoIntsServer(Node): def __init__(self): super().__init__('add_two_ints_server') self.srv = self.create_service( AddTwoInts, 'add_two_ints', self.add_two_ints_callback) self.get_logger().info('服务端已启动,等待客户端调用...') def add_two_ints_callback(self, request, response): self.get_logger().info( f'收到请求: a={request.a}, b={request.b}') response.sum = request.a + request.b self.get_logger().info(f'返回结果: sum={response.sum}') return response def main(args=None): rclpy.init(args=args) node = AddTwoIntsServer() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()

这里有几个细节值得注意:

第一,create_service的回调函数必须返回响应对象。如果你在回调里忘了return response,客户端会一直等待,直到超时。

第二,服务回调是阻塞运行的吗?不完全是。在 ROS2 的默认 executor 模式下,多个服务回调会串行执行。如果服务端有多个服务,某个回调执行时间过长,会阻塞其他回调。需要并行处理时,可以考虑使用多线程 executor。

第三,服务名称add_two_ints是全局的,在同一台机器上不能有两个服务端同时注册同名服务,否则会发生竞态。

4.3 客户端实现:Python 版

创建py_srv_demo/py_srv_demo/client.py文件:

import sys import rclpy from rclpy.node import Node from example_interfaces.srv import AddTwoInts class AddTwoIntsClient(Node): def __init__(self): super().__init__('add_two_ints_client') self.cli = self.create_client(AddTwoInts, 'add_two_ints') while not self.cli.wait_for_service(timeout_sec=1.0): self.get_logger().warn('等待服务端上线...') self.req = AddTwoInts.Request() def send_request(self, a, b): self.req.a = a self.req.b = b self.future = self.cli.call_async(self.req) rclpy.spin_until_future_complete(self, self.future) if self.future.result() is not None: return self.future.result().sum else: self.get_logger().error('服务调用失败') return None def main(): rclpy.init() node = AddTwoIntsClient() a = int(sys.argv[1]) b = int(sys.argv[2]) result = node.send_request(a, b) if result is not None: node.get_logger().info(f'{a} + {b} = {result}') node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()

客户端的核心步骤是:等待服务上线、构造请求、异步调用、等待结果。wait_for_service这一步非常关键,如果服务端还没启动,客户端直接调用会抛异常。等待时间可以根据业务需要调整,默认写 1 秒轮询一次比较合理。

4.4 C++ 版服务端与客户端

C++ 版本在语法上比 Python 更繁琐,但性能更好。在cpp_srv_demo/src目录下创建server.cpp

#include <rclcpp/rclcpp.hpp> #include <example_interfaces/srv/add_two_ints.hpp> using namespace std::chrono_literals; class AddTwoIntsServer : public rclcpp::Node { public: AddTwoIntsServer() : Node("add_two_ints_server") { server_ = this->create_service<example_interfaces::srv::AddTwoInts>( "add_two_ints", std::bind(&AddTwoIntsServer::handle_request, this, std::placeholders::_1, std::placeholders::_2)); RCLCPP_INFO(this->get_logger(), "C++ 服务端已启动"); } private: void handle_request( const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request, std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response) { response->sum = request->a + request->b; RCLCPP_INFO(this->get_logger(), "收到请求: %ld + %ld = %ld", request->a, request->b, response->sum); } rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr server_; }; int main(int argc, char **argv) { rclcpp::init(argc, argv); auto node = std::make_shared<AddTwoIntsServer>(); rclcpp::spin(node); rclcpp::shutdown(); return 0; }

C++ 服务端要注意的是回调函数签名:handle_request接受RequestResponse的智能指针。C++ 里 Response 是服务端直接实例化的,不需要你手动返回,修改 Response 指针指向的内容即可。

C++ 客户端创建client.cpp

#include <rclcpp/rclcpp.hpp> #include <example_interfaces/srv/add_two_ints.hpp> using namespace std::chrono_literals; class AddTwoIntsClient : public rclcpp::Node { public: AddTwoIntsClient() : Node("add_two_ints_client") { client_ = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints"); while (!client_->wait_for_service(1s)) { if (!rclcpp::ok()) { RCLCPP_ERROR(this->get_logger(), "客户端被中断"); return; } RCLCPP_WARN(this->get_logger(), "等待服务端上线..."); } } int call_add(int a, int b) { auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>(); request->a = a; request->b = b; auto future = client_->async_send_request(request); if (rclcpp::spin_until_future_complete(this->shared_from_this(), future) == rclcpp::FutureReturnCode::SUCCESS) { return future.get()->sum; } else { RCLCPP_ERROR(this->get_logger(), "服务调用失败"); return -1; } } private: rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_; }; int main(int argc, char **argv) { rclcpp::init(argc, argv); auto node = std::make_shared<AddTwoIntsClient>(); int result = node->call_add(3, 5); RCLCPP_INFO(node->get_logger(), "3 + 5 = %d", result); rclcpp::shutdown(); return 0; }

C++ 客户端有个常见坑:async_send_request的 future 必须等spin_until_future_complete返回成功后才能读取结果,如果直接调用future.get()可能遇到 future 尚未就绪的情况。

4.5 编译、运行与命令行验证

先玩一遍命令行工具,这是调试服务最有效的方式。启动一个服务端后,在另一个终端里查看服务列表:

ros2 service list

查看服务类型:

ros2 service type /add_two_ints

直接调用服务,相当于手动扮演客户端:

ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 10, b: 20}"

如果一切正常,你会看到类似输出:

waiting for service to become available... response: example_interfaces.srv.AddTwoInts_Response(sum=30)

这里有个小技巧:使用ros2 service call时,参数必须用双引号包住整个 JSON 内容,并且字段名要严格匹配 srv 定义。字段名拼错一个字,调用就会失败。这个工具在排查服务端逻辑问题时尤其好用——你可以完全不写客户端代码,先验证服务端是否正常工作。

5. 工具选型解析:服务调试三板斧

5.1ros2 service命令族的完整用法

ros2 service是排查服务通信问题的首选工具,它的子命令包括:

子命令作用
ros2 service list列出当前所有可用服务
ros2 service type <service_name>查看指定服务的接口类型
ros2 service find <type_name>按接口类型查找服务
ros2 service call <service_name> <type_name> <arguments>手动调用服务
ros2 service list -t同时列出服务名和类型

还有一个高阶用法:查看服务端和客户端的节点关系。启动服务端和客户端后,用rqt_graph查看节点图,你会看到服务端和客户端之间有一条边,边上的标签正是服务名。这条边对应的是服务连接,而不是话题连接。

5.2ros2 interface命令族:快速查看接口定义

调试服务接口时,ros2 interface命令帮了很大忙。查看内置服务接口的定义:

ros2 interface show example_interfaces/srv/AddTwoInts

查看自定义接口包中的服务:

ros2 interface show my_robot_interfaces/srv/GetRobotState

这个命令会直接输出 srv 文件的内容,不用去翻源码。如果你想验证接口包编译是否成功,先source install/setup.bash,再执行这个命令,能正常输出就说明接口已经被正确注册到系统中。

5.3rqt_service_caller图形化调试

如果你不喜欢纯命令行的交互方式,ROS2 还提供了一个图形化工具rqt_service_caller。安装命令:

sudo apt install ros-humble-rqt-service-caller

运行:

rqt_service_caller

在这个工具里,你可以下拉选择服务,工具会自动读取服务接口定义并生成参数输入框,填入参数后点击 Call 就能调用服务。这个工具的好处是直观,适合演示或者快速验证服务接口是否符合预期。

6. 常见问题与排查技巧实录

6.1 服务调用超时:服务端没上线还是网络隔离

我遇到最多的服务通信问题就是调用超时。客户端调用服务后一直等待,最后报出超时错误。

排查思路按优先级排列:

  1. 在客户端所在终端执行ros2 service list,确认服务是否可见。如果不可见,说明客户端和服务端不在同一通信域,或者服务端根本没起来。
  2. 在服务端所在终端确认服务端节点状态,观察有没有报错日志。
  3. 如果服务端和客户端分布在不同机器上,检查ROS_DOMAIN_ID是否一致。域 ID 不同会导致双方互相看不到对方。
  4. 检查防火墙。ROS2 的 DDS 通信依赖多种端口,如果防火墙阻止了多播或对端端口,服务发现就会失败。

有一个排查小技巧:用ros2 doctor命令检查 ROS2 环境是否健康,它会自动检测环境变量、网络设置和依赖问题。

6.2 服务端重启后客户端一直等待

有一种场景很典型:服务端因为某种原因重启了,客户端在启动时已经绑定过服务,重启后客户端不会自动重新发现服务,导致后续调用一直等待。

解决方案是在客户端的调用逻辑里加入重连机制。不要只在构造函数里检查一次服务可用状态,而是在每次调用前都检查:

if not self.cli.service_is_ready(): self.get_logger().warn('服务不在线,重新等待...') self.cli.wait_for_service(timeout_sec=2.0)

实测下来,这种策略能显著提升系统的鲁棒性。特别是在机器人实际运行中,某个功能模块动态重启是很常见的事。

6.3 多线程服务端与回调阻塞问题

默认的 single-threaded executor 中,所有回调都是串行的。如果一个服务回调里做了耗时操作(比如处理点云、等待硬件响应),其他服务回调就会被堵住。这时需要用多线程 executor:

from rclpy.executors import MultiThreadedExecutor from rclpy.callback_groups import ReentrantCallbackGroup rclpy.init() node = MyNode() executor = MultiThreadedExecutor(num_threads=4) executor.add_node(node) executor.spin()

同时,在创建服务时指定回调组:

self.srv = self.create_service( AddTwoInts, 'add_two_ints', self.callback, callback_group=ReentrantCallbackGroup())

这里要注意,ReentrantCallbackGroup表示同一组里的回调可以并发执行,这对成员变量的线程安全提出了要求。如果你的服务回调里修改了共享状态,务必加锁保护。我见过太多因为多线程回调并发修改同一变量导致的数据竞争问题。

6.4 服务接口不匹配导致调用失败

当你用ros2 service call调用服务时,接口类型必须完全一致,包括包名和类型名。比如:

ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 1, b: 2}"

这里的接口类型是example_interfaces/srv/AddTwoInts,少写一个包名前缀就会失败。这个问题在自定义接口时特别容易踩坑,因为自定义接口的包名通常比较长,手打容易出错。

6.5 服务调用频率过高被 DDS 丢弃

当客户端以非常高的频率调用服务时,可能会出现偶发的调用失败或超时。这通常是 DDS 层排队策略导致的。服务请求是按 QoS 策略排队的,默认队列深度有限。

解决方案有两种:一是降低调用频率,在实际工程中,大多数服务并不需要高频调用;二是检查服务端的 QoS 设置,确保历史深度策略能够容纳预期的并发调用。记住之前的建议:没有充分理由不要动 QoS。

6.6 实战速查表

现象可能原因解决方案
服务列表里看不到服务服务端未启动、域 ID 不一致、网络隔离检查ros2 service list、统一ROS_DOMAIN_ID、检查防火墙
客户端调用超时服务端崩溃、回调阻塞、服务名称错误确认服务端进程存活,检查服务端日志
调用返回但值不对srv 接口定义不匹配使用ros2 interface show对比接口定义
服务端日志显示收到了请求,但客户端超时响应对象未正确填充或未返回确保回调中返回了填充完成的响应对象
多服务同时调用卡顿单线程 executor 顺序执行改用多线程 executor

7. 从服务到微服务架构:ROS2 服务的宏观视角

你可能注意到热搜词里出现了“微服务架构”。ROS2 的服务设计理念和微服务架构确实有异曲同工之处。在微服务架构中,各个服务独立部署、独立扩展,通过轻量级通信机制互相协作;ROS2 中,每个节点可以看作一个 mini 服务,服务通信则是它们之间协作的一种方式。

在机器人软件架构中,这种“服务化”思路很有实际价值。比如一个完整的机器人系统,可以拆成“底盘服务”“导航服务”“视觉服务”“语音服务”等模块。每个模块暴露一组标准服务接口,其他模块通过服务调用完成交互。这样做的好处是模块之间解耦,单个模块可以独立替换、独立调试、独立重启。

我参与过一个仓储机器人的项目,最初所有逻辑都写在一个大节点里,改一个功能要重启整个节点,调试效率极低。后来按服务化思路重构,把底层硬件控制、路径规划、任务调度拆成独立节点,节点之间用服务通信,整个系统清晰了很多,故障排查也方便——哪个服务挂了,日志里一目了然。

服务通信本身没有魔法,但它背后蕴含的“各司其职、标准接口、可替换实现”思想,正是大型机器人软件架构的基石。

8. 实际工程中的服务选型建议

服务通信的工程实践,远比教条式的“接口定义+调用”复杂。根据我在多个机器人项目中的经验,这里给出几条务实建议。

第一,服务的粒度要适中。如果一个服务接口的参数超过五六个、响应字段超过七八个,往往说明服务职责过重。考虑拆分成多个小服务。反过来,如果服务调用频率极高且每次只传一个整数,也要考虑是不是应该用话题。

第二,客户端要有超时处理和重试机制。这在第 6 章已经详细说明。永远不要把服务调用当作“必然成功”的操作——机器人运行环境中,节点重启、网络抖动、硬件故障都可能发生。

第三,合理使用同步调用与异步调用call_async是异步调用,适合在回调函数中调用服务;call是同步调用(Python 中为call的同步版本,C++ 中通过async_send_request加等待实现),适合在独立线程中调用。在回调函数中做同步调用,容易导致死锁或者阻塞 executor。

第四,记录服务调用的关键日志。服务端记录收到请求的参数和返回结果,客户端记录调用耗时和结果状态。这些日志在排查问题时价值巨大。我习惯在服务端日志里加上调用者信息,虽然 ROS2 没有直接提供,但可以在请求参数里带上 client_id 字段。

第五,服务接口一经发布,尽量保持兼容。接口变更会导致所有客户端同步修改。机器人系统往往涉及多个模块协同,接口变动的影响范围可能远超预期。新增字段时考虑给默认值,删除字段时要格外谨慎。

9. 手上同时有 ROS1 经验的话,这里有几点迁移提示

如果你是从 ROS1 迁过来的,服务的核心概念差别不大,但实现细节有差异:

ROS1 的服务端使用ros::ServiceServer,ROS2 使用rclcpp::Service;ROS1 的回调返回布尔值,ROS2 的回调返回 Response 对象。ROS1 的客户端调用是call,ROS2 是async_send_request。ROS2 引入了wait_for_service机制,这在 ROS1 中是没有的——这也算是一个改进,让客户端可以优雅地处理服务端未就绪的情况。

ROS2 服务还有一个 ROS1 没有的特性:服务名支持命名空间。在多个机器人协同的场景下,可以为每个机器人设置不同的命名空间,这样每个机器人都可以有自己的同名服务,互不干扰。

还有一个经常被忽略的差异:ROS2 服务的发现机制依赖 DDS 的动态发现,如果网络环境禁止多播(比如某些工业现场),服务发现可能失败。这种情况下需要配置 DDS 的发现服务器(Discovery Server),把发现机制从多播切换为单播。这个话题展开很多,但至少你要知道有这个问题存在。

10. 实操心得:我最常用的一套服务调试流程

最后分享一个我实际开发中经常使用的完整调试流程,你可以直接照做。

第一步,用命令行快速验证服务端。启动服务端节点后,用ros2 service list确认服务已经注册,用ros2 service call手动调用确认返回结果正确。

第二步,验证客户端。启动客户端节点,观察能否正常调用。如果超时,按第 6 章的排查表逐步检查。

第三步,用rqt_graph检查节点连接。确认服务端和客户端之间没有多余的中转节点。

第四步,在代码中加入关键日志。包括服务端收到请求的时间、处理耗时、返回结果;客户端发送请求的时间、等待耗时、最终结果。

第五步,做异常测试。杀掉服务端进程,看客户端是否能正确报错或等待;重启服务端,看客户端能否恢复连接。这一步虽然费时间,但在真实环境中价值极大。

这个流程看起来简单,但能覆盖绝大多数服务通信问题。我见过不少开发者,花了大量时间在写代码上,出了问题却不知道从哪查起。其实只要掌握了这套验证方法,大部分问题都能在十分钟内定位。

服务这个机制,本身不复杂,但它在 ROS2 中的角色非常关键。搞懂了服务,你就掌握了机器人系统中“请求-应答”这类交互模式的标准解法。下一步可以继续学动作(Action),它是服务在长时间任务场景下的延伸,理解了服务再学动作,会轻松很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 7:30:08

RPCS3 自动更新:3 步配好,告别手动刷版本

RPCS3 自动更新&#xff1a;3 步配好&#xff0c;告别手动刷版本 【免费下载链接】rpcs3 PlayStation 3 emulator and debugger 项目地址: https://gitcode.com/GitHub_Trending/rp/rpcs3 还在手动下载新版再覆盖安装&#xff1f;RPCS3 的自动更新早就内置了&#xff1a…

作者头像 李华
网站建设 2026/9/10 7:28:46

红黑树原理与C++实现:图解插入删除旋转与平衡调整

1. 为什么红黑树成了面试的“硬通货”先聊点实际的。你打开任何一份 C 后端或基础架构岗位的面试题清单&#xff0c;红黑树几乎从未缺席。这还真不是面试官故意刁难——红黑树几乎就是现代计算机系统里“平衡与效率”这对矛盾的标准解。C 标准库里的std::map、std::set、std::m…

作者头像 李华
网站建设 2026/9/10 7:28:45

FPGA UART串口通信实战:从Verilog HDL代码到GW2A上板调试

简介&#xff1a;GW2A-LV18PG256C8实现UART串口通信的Verilog HDL驱动包&#xff0c;面向紫光同创FPGA开发者&#xff0c;解决在GW2A系列器件上快速搭建串口通信链路的问题。压缩包共62个文件&#xff0c;约693KB&#xff0c;核心包含UART发送与接收模块、顶层回环逻辑等Verilo…

作者头像 李华
网站建设 2026/9/10 7:27:27

农业大数据平台搭建实战:从Hadoop部署到精准农业应用落地

去年帮一个农业示范园区做土壤墒情监测项目&#xff0c;设备装完第一周我就意识到一个问题&#xff1a;几千个传感器每隔十分钟上报一条数据&#xff0c;加上园区里的气象站、虫情测报灯和无人机巡田影像&#xff0c;一个月攒下来的数据&#xff0c;公司原有的MySQL已经拖不动了…

作者头像 李华
网站建设 2026/9/10 7:27:02

神经网络就是多维数组运算:从张量到维度变换的底层解析

神经网络这个名词&#xff0c;很多人第一反应是"调包、调参、炼丹"。但真正把网络跑明白的人&#xff0c;都会有一个共识&#xff1a;所谓神经网络&#xff0c;本质上就是一堆多维数组在来回做运算。我见过太多初学者卡在"为什么输入要 reshape 成 (batch, seq,…

作者头像 李华