手把手做ROS2机器人系统开发(五):C++实现服务器与客户端
上一讲完成了发布者/订阅者程序,掌握了话题通信模型。本章节继续讲解 ROS2 的第二种核心通信方式——服务通信(Service),即"请求/响应"模型。该模型适用于需要实时返回结果的任务,例如:机器人运动指令下发、参数查询、业务逻辑调用等。本章以"两个整数相加"服务为例,编写一个标准的服务器/客户端
手把手做ROS2机器人系统开发(五):C++实现服务器与客户端
教程背景
上一讲完成了发布者/订阅者程序,掌握了话题通信模型。本章节继续讲解 ROS2 的第二种核心通信方式——服务通信(Service),即"请求/响应"模型。该模型适用于需要实时返回结果的任务,例如:机器人运动指令下发、参数查询、业务逻辑调用等。本章以"两个整数相加"服务为例,编写一个标准的服务器/客户端程序模板。
一、程序编写
1. 创建软件包
继续将软件包创建在 src 目录下,方便统一存放和管理:
ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces
2. 编译软件包
回到工作空间根目录进行编译,注意路径:
colcon build --packages-select cpp_srvcli
3. 软件配置
编程环境配置(VS Code 头文件目录等)参考本系列第三、四讲:
- 第三讲《搭建VS Code编程环境》:https://blog.csdn.net/guimotion/article/details/130352572
- 第四讲《C++实现发布者与订阅者》:https://blog.csdn.net/guimotion/article/details/130353926
4. 服务器程序编写
代码路径:src/cpp_srvcli/src/add_two_ints_server.cpp
实现两个数相加的服务端:
/**
* @file add_two_ints_server.cpp
* @author gmotion (motion_gui@126.com)
* @brief 发布服务器节点
* @version 0.1
* @date 2022-06-10
* @copyright Copyright (c) 2022
*/
#include <memory> //内存管理
#include <string> //字符串
#include <rclcpp/rclcpp.hpp>
//这里使用示例中的接口,后续章节会重点讲解如何自定义专用接口
#include <example_interfaces/srv/add_two_ints.hpp>
//添加服务信息
void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request>reques,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response>response)
{
response->sum = reques->a + reques->b;
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"Incoming request\n a:%ld " "b: %ld",
reques->a,reques->b);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"sending back response: [%ld] ",
(long int)response->sum);
}
//主运行程序
int main(int argc,char * argv[])
{
rclcpp::init(argc,argv);
//创建服务名称节点
std::shared_ptr<rclcpp::Node>node = rclcpp::Node::make_shared("add_two_ints_server");
//创建服务函数
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service=
node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints",&add);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"Ready to add two ints.");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
5. 客户端程序编写
代码路径:src/cpp_srvcli/src/add_two_ints_client.cpp
客户端向服务器发起请求并打印计算结果:
/**
* @file add_two_ints_client.cpp
* @author gmotion (motion_gui@126.com)
* @brief 客户端-请求服务器计算
* @version 0.1
* @date 2022-06-10
* @copyright Copyright (c) 2022
*/
#include <chrono> //处理时间类
#include <cstdlib> //标准函数库
#include <memory> //内存管理
#include <rclcpp/rclcpp.hpp>
#include <example_interfaces/srv/add_two_ints.hpp>
using namespace std::chrono_literals;
//主程序函数
int main(int argc,char * argv[])
{
rclcpp::init(argc,argv);
//参数数量发生错误
//传入两个计算对应的数值-用于请求服务器及计算
if(argc !=3)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"usage: add_two_ints_client X Y");
return 1;
}
//建立node节点
std::shared_ptr<rclcpp::Node>node = rclcpp::Node::make_shared("add_two_ints_client");
//建立客户端
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
//建立请求发送参数
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = atoll(argv[1]);
request->b = atoll(argv[2]);
//等待服务
while (!client->wait_for_service(1s))
{
if (!rclcpp::ok())
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"service no avaiable,wait again...");
}
}
//发送运行请求
auto result = client->async_send_request(request);
//等待运行结果
if(rclcpp::spin_until_future_complete(node,result) == rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"Sum: %ld",result.get()->sum);
}
else
{
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"Failed to call service add_two_ints");
}
rclcpp::shutdown();
return 0;
}
6. 软件包设置
编辑 src/cpp_srvcli/package.xml:
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>cpp_srvcli</name>
<version>0.0.0</version>
<description>C++ client server tutorial</description>
<maintainer email="motion_gui@126.com">gmotion</maintainer>
<license>Apache License 2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>example_interfaces</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
7. 设置编译选项
编辑 src/cpp_srvcli/CMakeLists.txt:
cmake_minimum_required(VERSION 3.8)
project(cpp_pubsub)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)
install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
set(ament_cmake_copyright_FOUND TRUE)
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
提示:本示例直接沿用上讲的 CMakeLists 模板,重点在于理解服务通信逻辑。实际项目中请根据
src/cpp_srvcli/src/下的文件正确配置add_executable。
二、程序测试
1. 编译程序
指定单一软件包编译:
colcon build --packages-select cpp_srvcli
2. 开启节点测试运行
服务器终端:
. install/setup.bash
ros2 run cpp_srvcli server
客户端终端(传入 12 和 10 两个参数):
. install/setup.bash
ros2 run cpp_srvcli client 12 10
3. 执行效果展示
客户端输出 Sum: 22,服务器端打印收到请求与返回结果,程序准确执行,达到预期结果。
小结
本章节实现了 ROS2 服务通信的完整示例:服务器负责计算响应,客户端发起请求并等待结果。服务通信与话题通信共同构成 ROS2 应用开发的两大基石。下一章节讲解如何创建用户自定义的 msg 和 srv 接口文件。
SEO关键词: ROS2 C++服务通信, ROS2 Service教程, rclcpp服务器客户端, ROS2请求响应模型, ROS2功能包开发
