ROS2服务通信开发指南:从基础到实践
1. ROS2服务通信基础概念在机器人系统中服务(Service)是一种重要的通信机制它采用请求-响应模式实现节点间的同步交互。与话题(Topic)的发布-订阅模式不同服务通信是双向的、同步的适用于需要即时响应的场景。服务由三部分组成服务接口定义(.srv文件)包含请求(request)和响应(response)的数据结构服务端(Server)接收请求并返回响应客户端(Client)发送请求并等待响应ROS2中的服务基于DDS的RPC机制实现底层采用高效的序列化协议。相比ROS1ROS2的服务通信具有以下改进支持服务质量(QoS)配置更完善的超时处理机制原生支持多节点调用2. 开发环境准备2.1 安装ROS2环境推荐使用Ubuntu 22.04 LTS和ROS2 Humble版本这是当前的LTS(长期支持)组合。安装步骤如下# 设置locale sudo apt update sudo apt install locales sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALLen_US.UTF-8 LANGen_US.UTF-8 export LANGen_US.UTF-8 # 添加ROS2仓库 sudo apt install software-properties-common sudo add-apt-repository universe sudo apt update sudo apt install curl -y sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release echo $UBUNTU_CODENAME) main | sudo tee /etc/apt/sources.list.d/ros2.list /dev/null # 安装ROS2基础包 sudo apt update sudo apt install ros-humble-desktop2.2 创建工作空间mkdir -p ~/ros2_ws/src cd ~/ros2_ws colcon build source install/setup.bash2.3 创建功能包cd ~/ros2_ws/src ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces这里我们使用了example_interfaces包它包含了一些常用的接口定义包括我们将要使用的AddTwoInts.srv。3. 定义服务接口虽然我们可以使用现成的AddTwoInts.srv但为了完整理解我们先看看如何自定义服务接口在功能包目录下创建srv文件夹mkdir -p ~/ros2_ws/src/cpp_srvcli/srv创建AddTwoInts.srv文件int64 a int64 b --- int64 sum这个简单的服务接口定义了两个整数相加的功能客户端发送两个整数a和b服务端返回它们的和。注意服务接口文件分为三部分用---分隔。第一部分是请求(request)内容第二部分是响应(response)内容。4. 实现服务端节点4.1 创建服务端源码文件在src目录下创建add_two_ints_server.cpp#include rclcpp/rclcpp.hpp #include example_interfaces/srv/add_two_ints.hpp #include memory using std::placeholders::_1; using std::placeholders::_2; class AddTwoIntsServer : public rclcpp::Node { public: AddTwoIntsServer() : Node(add_two_ints_server) { service_ this-create_serviceexample_interfaces::srv::AddTwoInts( add_two_ints, std::bind(AddTwoIntsServer::handle_add_two_ints, this, _1, _2)); RCLCPP_INFO(this-get_logger(), Service server ready.); } private: void handle_add_two_ints( const std::shared_ptrexample_interfaces::srv::AddTwoInts::Request request, std::shared_ptrexample_interfaces::srv::AddTwoInts::Response response) { response-sum request-a request-b; RCLCPP_INFO(this-get_logger(), Incoming request\na: %ld b: %ld, request-a, request-b); RCLCPP_INFO(this-get_logger(), Sending back response: [%ld], response-sum); } rclcpp::Serviceexample_interfaces::srv::AddTwoInts::SharedPtr service_; }; int main(int argc, char **argv) { rclcpp::init(argc, argv); auto node std::make_sharedAddTwoIntsServer(); rclcpp::spin(node); rclcpp::shutdown(); return 0; }4.2 代码解析节点初始化rclcpp::init()初始化ROS2 C客户端库创建AddTwoIntsServer节点继承自rclcpp::Node服务创建create_service方法创建服务指定服务类型(AddTwoInts)、服务名称(add_two_ints)和回调函数使用std::bind绑定成员函数作为回调请求处理handle_add_two_ints是服务回调函数接收请求(request)和响应(response)对象计算两个整数的和并存入response-sum使用RCLCPP_INFO输出日志信息事件循环rclcpp::spin()保持节点运行等待服务调用rclcpp::shutdown()在节点结束时清理资源5. 实现客户端节点5.1 创建客户端源码文件在src目录下创建add_two_ints_client.cpp#include rclcpp/rclcpp.hpp #include example_interfaces/srv/add_two_ints.hpp #include chrono #include cstdlib #include memory using namespace std::chrono_literals; class AddTwoIntsClient : public rclcpp::Node { public: AddTwoIntsClient() : Node(add_two_ints_client) { client_ this-create_clientexample_interfaces::srv::AddTwoInts(add_two_ints); // 等待服务可用 while (!client_-wait_for_service(1s)) { if (!rclcpp::ok()) { RCLCPP_ERROR(this-get_logger(), Interrupted while waiting for service.); return; } RCLCPP_INFO(this-get_logger(), Service not available, waiting again...); } } void send_request(int64_t a, int64_t b) { auto request std::make_sharedexample_interfaces::srv::AddTwoInts::Request(); request-a a; request-b b; auto result client_-async_send_request(request); // 等待结果 if (rclcpp::spin_until_future_complete(this-get_node_base_interface(), result) rclcpp::FutureReturnCode::SUCCESS) { RCLCPP_INFO(this-get_logger(), Sum: %ld, result.get()-sum); } else { RCLCPP_ERROR(this-get_logger(), Failed to call service add_two_ints); } } private: rclcpp::Clientexample_interfaces::srv::AddTwoInts::SharedPtr client_; }; int main(int argc, char **argv) { rclcpp::init(argc, argv); if (argc ! 3) { RCLCPP_ERROR(rclcpp::get_logger(rclcpp), Usage: add_two_ints_client X Y); return 1; } auto client std::make_sharedAddTwoIntsClient(); client-send_request(atoll(argv[1]), atoll(argv[2])); rclcpp::shutdown(); return 0; }5.2 代码解析节点初始化创建客户端节点AddTwoIntsClientcreate_client创建客户端实例指定服务类型和名称服务等待wait_for_service循环等待服务端可用检查rclcpp::ok()确保ROS2系统正常运行请求发送创建请求对象并设置参数async_send_request异步发送请求spin_until_future_complete等待响应返回参数处理从命令行读取两个整数参数调用send_request方法发送请求6. 构建与运行6.1 修改CMakeLists.txt在功能包的CMakeLists.txt中添加以下内容add_executable(server src/add_two_ints_server.cpp) ament_target_dependencies(server rclcpp example_interfaces) add_executable(client src/add_two_ints_client.cpp) ament_target_dependencies(client rclcpp example_interfaces) install(TARGETS server client DESTINATION lib/${PROJECT_NAME})6.2 修改package.xml确保package.xml包含以下依赖dependrclcpp/depend dependexample_interfaces/depend6.3 构建功能包cd ~/ros2_ws colcon build --packages-select cpp_srvcli source install/setup.bash6.4 运行服务端ros2 run cpp_srvcli server6.5 运行客户端新开一个终端source ~/ros2_ws/install/setup.bash ros2 run cpp_srvcli client 2 3你应该会看到类似以下输出[INFO] [add_two_ints_server]: Service server ready. [INFO] [add_two_ints_server]: Incoming request a: 2 b: 3 [INFO] [add_two_ints_server]: Sending back response: [5] [INFO] [add_two_ints_client]: Sum: 57. 高级主题与最佳实践7.1 服务超时处理在实际应用中应该为服务调用设置合理的超时时间。修改客户端代码auto result client_-async_send_request(request); auto future_status result.wait_for(5s); // 5秒超时 if (future_status std::future_status::ready) { RCLCPP_INFO(this-get_logger(), Sum: %ld, result.get()-sum); } else { RCLCPP_ERROR(this-get_logger(), Service call timed out); }7.2 服务质量(QoS)配置ROS2允许为服务配置QoS策略例如可靠性(Reliability)和持久性(Durability)// 在服务端 auto qos rclcpp::QoS(rclcpp::KeepLast(10)).reliable(); service_ this-create_serviceexample_interfaces::srv::AddTwoInts( add_two_ints, std::bind(AddTwoIntsServer::handle_add_two_ints, this, _1, _2), qos); // 在客户端 auto qos rclcpp::QoS(rclcpp::KeepLast(10)).reliable(); client_ this-create_clientexample_interfaces::srv::AddTwoInts(add_two_ints, qos);7.3 多线程服务处理默认情况下ROS2使用单线程执行器。要处理多个并发请求可以使用多线程执行器// 修改服务端main函数 int main(int argc, char **argv) { rclcpp::init(argc, argv); auto node std::make_sharedAddTwoIntsServer(); // 使用多线程执行器 rclcpp::executors::MultiThreadedExecutor executor; executor.add_node(node); executor.spin(); rclcpp::shutdown(); return 0; }7.4 服务接口版本控制当服务接口需要变更时应该考虑版本兼容性。常见做法创建新版本的服务接口(如AddTwoIntsV2.srv)同时支持新旧接口一段时间逐步迁移客户端到新接口最终废弃旧接口8. 常见问题排查8.1 服务不可用现象客户端一直等待服务输出Service not available可能原因服务端未启动服务名称拼写错误网络问题导致节点无法发现彼此解决方案使用ros2 service list检查服务是否已注册确认服务端节点正常运行检查服务名称是否一致(包括大小写)确保所有节点使用相同的ROS_DOMAIN_ID8.2 请求超时现象客户端报Service call timed out可能原因服务端处理时间过长网络延迟服务端崩溃解决方案增加客户端超时时间检查服务端处理逻辑是否有阻塞操作优化服务端算法性能添加服务端心跳检测机制8.3 数据类型不匹配现象服务调用失败日志显示序列化错误可能原因客户端和服务端使用的接口定义不一致字段类型或名称变更未同步更新解决方案使用ros2 interface show检查接口定义确保所有节点重新编译在团队开发中将接口定义放在单独的功能包中共享9. 性能优化建议批量处理对于高频小数据请求考虑设计批量处理接口异步设计避免在服务回调中执行耗时操作必要时使用异步处理负载均衡对于高负载服务可以部署多个服务端实例结果缓存对计算密集型且参数重复的服务实现结果缓存监控指标添加服务调用时间、成功率等监控指标10. 实际应用案例10.1 机器人导航服务# 请求 geometry_msgs/PoseStamped target_pose float32 speed_limit --- # 响应 bool success string message float32 estimated_duration这种服务可用于机器人导航系统客户端发送目标位置和速度限制服务端返回路径规划结果。10.2 设备控制服务# 请求 uint8 device_id uint8 command float32[] params --- # 响应 bool success float32[] readings适用于硬件设备控制场景如机械臂、传感器等设备的精确控制。10.3 数据处理服务# 请求 sensor_msgs/Image image string processing_algorithm --- # 响应 sensor_msgs/Image processed_image float32 processing_time用于图像处理等计算密集型任务客户端发送原始数据和算法选择服务端返回处理结果。