![[ROS 系列学习教程] ROS服务(Service)通信:从模型解析到实战进阶](http://pic.xiahunao.cn/yaotu/[ROS 系列学习教程] ROS服务(Service)通信:从模型解析到实战进阶)
1. ROS服务通信模型解析第一次接触ROS服务通信时我误以为它和话题通信差不多。直到在机器人项目中被坑了几次才发现这完全是两种不同的通信机制。服务通信最核心的特点是请求-响应模型——就像你去餐厅点餐服务员必须明确回应菜品已下单或今日售罄。1.1 核心角色与通信流程ROS服务通信涉及三个关键角色Master相当于餐厅的接待台记录哪些服务可用Server后厨团队实际处理请求Client顾客发起服务请求具体通信流程是这样的以机器人控制为例服务端注册就像餐厅开业时在美团上线/move_robot_service服务会向ROS Master注册自己的位置IP端口和服务菜单srv格式客户端查询当导航节点需要移动机械臂时它会询问Master哪里有机械臂控制服务建立直连Master把服务端的地址告诉客户端之后它们就可以绕过Master直接通话请求响应客户端发送目标坐标服务端返回执行结果# 典型服务定义示例AddTwoInts.srv int64 a # 请求参数 int64 b --- # 分隔线 int64 sum # 响应参数1.2 与话题通信的关键差异去年做物流机器人时我曾混淆这两种通信方式结果导致机械臂失控。这里总结下关键区别特性服务(Service)话题(Topic)通信模式一对一类似客服系统一对多类似广播同步性同步阻塞必须等待响应异步非阻塞数据流向双向请求响应单向只发布不回复适用场景即时性强的指令如急停持续数据流如传感器数据提示当需要确保指令被立即处理时比如紧急停止一定要用服务通信。我在调试机械臂时曾因错用话题通信导致响应延迟差点造成碰撞事故。2. 从零实现Hello World服务2.1 创建服务包首先建立工作空间如果已有可跳过mkdir -p ~/service_ws/src cd ~/service_ws/src catkin_create_pkg service_demo roscpp rospy std_msgs关键文件结构service_demo/ ├── srv/ # 存放服务定义文件 │ └── HelloWorld.srv ├── scripts/ # Python脚本 │ ├── server.py │ └── client.py └── src/ # C源码 ├── server.cpp └── client.cpp2.2 定义服务接口在srv/HelloWorld.srv中定义string request # 客户端发送的请求内容 --- string response # 服务端返回的响应修改CMakeLists.txt添加编译支持find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation # 新增 ) add_service_files( FILES HelloWorld.srv ) generate_messages( DEPENDENCIES std_msgs )2.3 C实现服务端src/server.cpp#include ros/ros.h #include service_demo/HelloWorld.h bool handleRequest(service_demo::HelloWorld::Request req, service_demo::HelloWorld::Response resp) { ROS_INFO(收到请求: %s, req.request.c_str()); resp.response 你好 req.request; return true; } int main(int argc, char **argv) { ros::init(argc, argv, hello_server); ros::NodeHandle nh; ros::ServiceServer service nh.advertiseService( /hello_world, handleRequest); ROS_INFO(服务已启动); ros::spin(); return 0; }客户端src/client.cpp#include ros/ros.h #include service_demo/HelloWorld.h int main(int argc, char **argv) { ros::init(argc, argv, hello_client); if (argc ! 2) { ROS_WARN(请传入请求参数); return 1; } ros::NodeHandle nh; ros::ServiceClient client nh.serviceClientservice_demo::HelloWorld( /hello_world); service_demo::HelloWorld srv; srv.request.request argv[1]; if (client.call(srv)) { ROS_INFO(收到响应: %s, srv.response.response.c_str()); } else { ROS_ERROR(服务调用失败); return 1; } return 0; }2.4 Python实现服务端scripts/server.py#!/usr/bin/env python import rospy from service_demo.srv import HelloWorld, HelloWorldResponse def handle_request(req): rospy.loginfo(收到请求: %s, req.request) return HelloWorldResponse(你好 req.request) if __name__ __main__: rospy.init_node(hello_server_py) service rospy.Service(/hello_world_py, HelloWorld, handle_request) rospy.loginfo(Python服务已启动) rospy.spin()客户端scripts/client.py#!/usr/bin/env python import sys import rospy from service_demo.srv import HelloWorld if __name__ __main__: rospy.init_node(hello_client_py) rospy.wait_for_service(/hello_world_py) try: hello_client rospy.ServiceProxy(/hello_world_py, HelloWorld) resp hello_client(sys.argv[1]) rospy.loginfo(收到响应: %s, resp.response) except rospy.ServiceException as e: rospy.logerr(服务调用失败: %s, e)3. 机器人任务调度实战3.1 场景设计假设我们要控制一个仓储机器人执行以下任务流程客户端发送任务指令如取货A03服务端协调底盘和机械臂完成底盘移动到目标货架机械臂执行抓取返回最终状态定义任务服务接口RobotTask.srvstring task_id # 任务ID string destination # 目标位置 --- bool success # 执行结果 string message # 状态信息3.2 服务端实现关键点bool executeTask(robot_control::RobotTask::Request req, robot_control::RobotTask::Response resp) { // 1. 移动底盘 if(!moveBase(req.destination)) { resp.success false; resp.message 移动失败; return true; } // 2. 控制机械臂 ArmResult arm_res controlArm(req.task_id); if(!arm_res.success) { resp.success false; resp.message 抓取失败: arm_res.error_msg; return true; } // 3. 返回结果 resp.success true; resp.message 任务完成; return true; }3.3 客户端超时处理实际项目中必须添加超时机制try: # 等待服务可用超时5秒 rospy.wait_for_service(/robot_task, timeout5) task_client rospy.ServiceProxy(/robot_task, RobotTask) # 设置调用超时3秒 resp task_client(A03, timeoutrospy.Duration(3)) except rospy.ROSException as e: rospy.logerr(任务调度超时: %s, e)4. 高级技巧与性能优化4.1 服务复用模式在机器人多模块协作时可以采用服务复用策略// 在节点初始化时创建服务客户端 ros::ServiceClient arm_client nh.serviceClientArmControl( /arm_control); // 复用同一个客户端 void handleTask1(...) { arm_client.call(req1); } void handleTask2(...) { arm_client.call(req2); }4.2 异步服务调用Python中使用多线程实现非阻塞调用from threading import Thread def async_call(): try: resp service_client(arg) print(收到响应:, resp) except Exception as e: print(调用失败:, e) Thread(targetasync_call).start() print(主线程继续执行...)4.3 性能监控技巧通过rosservice工具分析服务状态# 查看服务响应时间 rosservice hz /robot_task # 检查服务连接 rosservice info /robot_task # 模拟调用测试 rosservice call /robot_task A03