1. ROS2服务端与客户端开发概述在机器人系统开发中服务(Service)是实现节点间同步通信的重要机制。与话题(Topic)的发布/订阅模式不同服务采用请求-响应模型适用于需要确认执行结果的场景。比如控制机械臂移动到指定位置后需要返回是否成功的状态或者请求传感器数据时要求立即返回测量值。ROS2中的服务基于DDS的RPC机制实现底层采用IDL定义接口。一个完整的服务包含两个部分服务端(Server)监听服务请求并返回响应客户端(Client)发送请求并等待响应C作为ROS2官方支持的核心语言之一其服务开发接口提供了良好的类型安全和性能表现。下面我们将通过一个完整的示例演示如何用C实现ROS2服务通信。2. 开发环境准备2.1 基础环境配置在开始编写代码前需要确保已正确安装ROS2环境。推荐使用Ubuntu 22.04 LTS和ROS2 Humble版本组合# 设置软件源 sudo apt update sudo apt install curl gnupg lsb-release 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 $(source /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-desktop验证安装是否成功source /opt/ros/humble/setup.bash ros2 run demo_nodes_cpp talker2.2 创建工作空间建议为每个项目创建独立的工作空间mkdir -p ~/ros2_ws/src cd ~/ros2_ws colcon build提示每次打开新终端时需要先执行source /opt/ros/humble/setup.bash和source ~/ros2_ws/install/setup.bash来初始化环境。3. 创建服务接口3.1 定义服务类型首先需要创建自定义服务接口。在ROS2中服务接口使用.srv文件定义包含请求和响应两部分# 在~/ros2_ws/src创建功能包 cd ~/ros2_ws/src ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces # 创建srv目录和文件 mkdir -p cpp_srvcli/srv touch cpp_srvcli/srv/AddTwoInts.srv编辑AddTwoInts.srv文件int64 a int64 b --- int64 sum3.2 配置构建系统修改CMakeLists.txt和package.xml以包含服务定义# 在CMakeLists.txt中添加 find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} srv/AddTwoInts.srv )!-- 在package.xml中添加 -- build_dependrosidl_default_generators/build_depend exec_dependrosidl_default_runtime/exec_depend member_of_grouprosidl_interface_packages/member_of_group编译功能包cd ~/ros2_ws colcon build --packages-select cpp_srvcli4. 实现服务端节点4.1 创建服务端代码在src目录下创建add_two_ints_server.cpp#include rclcpp/rclcpp.hpp #include cpp_srvcli/srv/add_two_ints.hpp using AddTwoInts cpp_srvcli::srv::AddTwoInts; class ServerNode : public rclcpp::Node { public: ServerNode() : Node(add_two_ints_server) { service_ create_serviceAddTwoInts( add_two_ints, [this](const std::shared_ptrAddTwoInts::Request request, std::shared_ptrAddTwoInts::Response response) { RCLCPP_INFO(get_logger(), 收到请求: %ld %ld, request-a, request-b); response-sum request-a request-b; }); } private: rclcpp::ServiceAddTwoInts::SharedPtr service_; }; int main(int argc, char **argv) { rclcpp::init(argc, argv); auto node std::make_sharedServerNode(); RCLCPP_INFO(node-get_logger(), 服务端已启动); rclcpp::spin(node); rclcpp::shutdown(); return 0; }4.2 配置服务端构建修改CMakeLists.txt添加可执行文件add_executable(server src/add_two_ints_server.cpp) ament_target_dependencies(server rclcpp cpp_srvcli) install(TARGETS server DESTINATION lib/${PROJECT_NAME})5. 实现客户端节点5.1 创建客户端代码在src目录下创建add_two_ints_client.cpp#include rclcpp/rclcpp.hpp #include cpp_srvcli/srv/add_two_ints.hpp using AddTwoInts cpp_srvcli::srv::AddTwoInts; class ClientNode : public rclcpp::Node { public: ClientNode() : Node(add_two_ints_client) { client_ create_clientAddTwoInts(add_two_ints); while (!client_-wait_for_service(std::chrono::seconds(1))) { RCLCPP_WARN(get_logger(), 等待服务端上线...); } } int64_t send_request(int64_t a, int64_t b) { auto request std::make_sharedAddTwoInts::Request(); request-a a; request-b b; auto future client_-async_send_request(request); if (rclcpp::spin_until_future_complete(shared_from_this(), future) ! rclcpp::FutureReturnCode::SUCCESS) { RCLCPP_ERROR(get_logger(), 请求失败); return -1; } return future.get()-sum; } private: rclcpp::ClientAddTwoInts::SharedPtr client_; }; int main(int argc, char **argv) { if (argc ! 3) { std::cerr 用法: argv[0] a b std::endl; return 1; } rclcpp::init(argc, argv); auto node std::make_sharedClientNode(); auto result node-send_request(std::stol(argv[1]), std::stol(argv[2])); RCLCPP_INFO(node-get_logger(), 计算结果: %ld, result); rclcpp::shutdown(); return 0; }5.2 配置客户端构建在CMakeLists.txt中添加add_executable(client src/add_two_ints_client.cpp) ament_target_dependencies(client rclcpp cpp_srvcli) install(TARGETS client DESTINATION lib/${PROJECT_NAME})6. 编译与测试6.1 完整编译项目cd ~/ros2_ws colcon build --packages-select cpp_srvcli source install/setup.bash6.2 运行服务端ros2 run cpp_srvcli server6.3 运行客户端测试新开终端执行source ~/ros2_ws/install/setup.bash ros2 run cpp_srvcli client 12 34预期输出[INFO] [add_two_ints_client]: 计算结果: 467. 高级配置与调试技巧7.1 QoS配置ROS2允许通过QoS策略控制服务质量// 服务端 rmw_qos_profile_t qos_profile { RMW_QOS_POLICY_HISTORY_KEEP_LAST, 10, RMW_QOS_POLICY_RELIABILITY_RELIABLE, RMW_QOS_POLICY_DURABILITY_VOLATILE, RMW_QOS_DEADLINE_DEFAULT, RMW_QOS_LIFESPAN_DEFAULT, RMW_QOS_POLICY_LIVELINESS_SYSTEM_DEFAULT, RMW_QOS_LIVELINESS_LEASE_DURATION_DEFAULT, false }; service_ create_serviceAddTwoInts( add_two_ints, [this](...) {...}, qos_profile);7.2 多线程处理默认情况下ROS2使用单线程执行器。对于高并发场景可以使用多线程执行器rclcpp::executors::MultiThreadedExecutor executor; executor.add_node(node); executor.spin();7.3 常见问题排查服务不可见检查服务端节点是否正常运行使用ros2 service list查看服务列表确保客户端和服务端使用相同的QoS配置请求超时增加wait_for_service的超时时间检查网络连接和防火墙设置类型不匹配确保.srv文件修改后重新编译使用ros2 interface show interface检查接口定义8. 性能优化建议避免阻塞调用服务回调函数应尽快返回耗时操作应使用异步方式处理合理设置QoS实时性要求高的场景使用RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT关键数据使用RMW_QOS_POLICY_RELIABILITY_RELIABLE资源管理及时释放不再使用的服务和客户端避免在循环中频繁创建/销毁节点日志优化生产环境中降低日志级别使用RCLCPP_DEBUG替代频繁的RCLCPP_INFO在实际机器人项目中服务通常用于实现关键控制指令和状态查询。我曾在一个机械臂项目中遇到服务响应延迟的问题最终发现是因为服务回调中进行了大量计算。将计算任务转移到工作线程后性能提升了5倍以上。