赞
踩
说明:本文基于ubuntu22.04和ros2 humble版本
服务分为客户端和服务端,平时我们用的手机APP都可以成为客户端,而APP服务器对于软件来说就是服务端。
客户端发送请求给服务端,服务端可以根据客户端的请求做一些处理,然后返回结果给客户端。
服务-客户端模型(请求-响应模型)
服务和话题的不同之处,话题是没有返回的,适用于单向或大量的数据传递。而服务是双向的,客户端发送请求,服务端响应请求。
服务的一些注意事项:
- 同一个服务(名称相同)有且只能有一个节点来提供
- 同一个服务可以被多个客户端调用
启动服务端
打开终端,运行下面的命令,打开一个服务节点
这个服务的功能是将两个数字相加,给定a,b两个数,返回sum也就是ab之和。
ros2 run examples_rclpy_minimal_service service
查看服务列表
ros2 service list
手动调用服务
在新开启的终端中输入下面的命令(注意a:、b:后的空格)
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"
#查看服务列表
ros2 service list
#手动调用服务
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"
#查看服务接口类型
ros2 service type /add_two_ints
#查找使用某一接口的服务
ros2 service find example_interfaces/srv/AddTwoInts
因为还没有学习如何自定义接口,所以我们先借着上一节的两数相加的示例接口,利用rclcpp提供的接口实现两数相加的服务端和客户端。
在工作空间,源文件夹下打开终端,运行下面指令
ros2 pkg create example_service_rclcpp --build-type ament_cmake --dependencies rclcpp touch example_service_rclcpp/src/service_server_01.cpp
touch example_service_rclcpp/src/service_client_01.cpp
编写源文件,以面向对象方式创建两个节点
service_server_01.cpp
- #include "rclcpp/rclcpp.hpp"
-
- class ServiceServer01 : public rclcpp::Node {
- public:
- ServiceServer01(std::string name) : Node(name) {
- RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
- }
-
- private:
- };
-
- int main(int argc, char** argv) {
- rclcpp::init(argc, argv);
- auto node = std::make_shared<ServiceServer01>("service_server_01");
- rclcpp::spin(node);
- rclcpp::shutdown();
- return 0;
- }

service_client_01.cpp
- #include "rclcpp/rclcpp.hpp"
-
- class ServiceClient01 : public rclcpp::Node {
- public:
- // 构造函数,有一个参数为节点名称
- ServiceClient01(std::string name) : Node(name) {
- RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
- }
- private:
- };
-
- int main(int argc, char** argv) {
- rclcpp::init(argc, argv);
- /*创建对应节点的共享指针对象*/
- auto node = std::make_shared<ServiceClient01>("service_client_01");
- /* 运行节点,并检测退出信号*/
- rclcpp::spin(node);
- rclcpp::shutdown();
- return 0;
- }

CMakeLists.txt
- add_executable(service_client_01 src/service_client_01.cpp)
- ament_target_dependencies(service_client_01 rclcpp)
-
- add_executable(service_server_01 src/service_server_01.cpp)
- ament_target_dependencies(service_server_01 rclcpp)
-
- install(TARGETS
- service_server_01
- DESTINATION lib/${PROJECT_NAME}
- )
-
- install(TARGETS
- service_client_01
- DESTINATION lib/${PROJECT_NAME}
- )
在工作空间目录下,进行编译测试
colcon build --packages-select example_service_rclcpp
# 运行 service_server_01
source install/setup.bash
ros2 run example_service_rclcpp service_server_01
# 打开新终端运行 service_client_01
source install/setup.bash
ros2 run example_service_rclcpp service_client_01
两数相加我们需要利用ROS2自带的
example_interfaces
接口,使用命令行可以查看这个接口的定义ros2 interface show example_interfaces/srv/AddTwoInts
ament_cmake
类型功能包导入消息接口分为三步:
- 在
CMakeLists.txt
中导入,具体是先find_packages
再ament_target_dependencies
。- 在
packages.xml
中导入,具体是添加depend
标签并将消息接口写入。- 在代码中导入,C++中是
#include"消息功能包/xxx/xxx.hpp"
。
CMakeLists.txt
- # 这里我们一次性把服务端和客户端对example_interfaces的依赖都加上
- find_package(example_interfaces REQUIRED)
-
- add_executable(service_client_01 src/service_client_01.cpp)
- ament_target_dependencies(service_client_01 rclcpp example_interfaces)
-
- add_executable(service_server_01 src/service_server_01.cpp)
- ament_target_dependencies(service_server_01 rclcpp example_interfaces)
packages.xml
<depend>example_interfaces</depend>
代码源文件
#include "example_interfaces/srv/add_two_ints.hpp"
- #include "example_interfaces/srv/add_two_ints.hpp"
- #include "rclcpp/rclcpp.hpp"
-
- class ServiceServer01 : public rclcpp::Node {
- public:
- ServiceServer01(std::string name) : Node(name)
- {
- RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
- // 创建服务
- add_ints_server_ =
- this->create_service<example_interfaces::srv::AddTwoInts>(
- "add_two_ints_srv", std::bind(&ServiceServer01::handle_add_two_ints,
- this, std::placeholders::_1, std::placeholders::_2));
- }
-
- private:
- // 声明一个服务
- rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr
- add_ints_server_;
-
- // 收到请求的处理函数
- void handle_add_two_ints(
- const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
- std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
- {
- RCLCPP_INFO(this->get_logger(), "收到a: %ld b: %ld", request->a,
- request->b);
- response->sum = request->a + request->b;
- };
- };

create_service,参考rclcpp API文档
- ServiceT,消息接口
example_interfaces::srv::AddTwoInts
- service_name,服务名称
- callback,回调函数,使用成员函数作为回调函数,std::bind进行转换
- qos_profile,服务质量配置文件,默认
rmw_qos_profile_services_default
- group,调用服务的回调组,默认
nullptr
colcon build --packages-select example_service_rclcpp
source install/setup.bash
ros2 run example_service_rclcpp service_server_01
打开新的终端
# 查看声明的服务
ros2 service list
# 使用命令行进行服务调用
ros2 service call /add_two_ints_srv example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"
函数
async_send_request()
同时传入两个参数
- request,请求的消息,这里用于放a,b两个数。
- CallBack,回调函数,异步接收服务器的返回的函数。
这个函数是用于等待服务上线的,这个函数并不在rclcpp::Client中定义,而是在其父类中定义的。
参数就一个,等待的时间,返回值是bool类型的,上线了就是true,不上线就是false。
之所以会用的这个函数的原因是,再发送请求之前保证服务端启动了,避免发送一个请求出去而无人响应的尴尬局面。
- #include "example_interfaces/srv/add_two_ints.hpp"
-
- class ServiceClient01 : public rclcpp::Node {
- public:
- // 构造函数,有一个参数为节点名称
- ServiceClient01(std::string name) : Node(name)
- {
- RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
- // 创建客户端
- client_ = this->create_client<example_interfaces::srv::AddTwoInts>
- ("add_two_ints_srv");
- }
-
- void send_request(int a, int b) {
- RCLCPP_INFO(this->get_logger(), "计算%d+%d", a, b);
-
- // 1.等待服务端上线
- while (!client_->wait_for_service(std::chrono::seconds(1)))
- {
- //等待时检测rclcpp的状态
- if (!rclcpp::ok())
- {
- RCLCPP_ERROR(this->get_logger(), "等待服务的过程中被打断...");
- return;
- }
- RCLCPP_INFO(this->get_logger(), "等待服务端上线中");
- }
-
- // 2.构造请求的
- auto request =
- std::make_shared<example_interfaces::srv::AddTwoInts_Request>();
- request->a = a;
- request->b = b;
-
- // 3.发送异步请求,然后等待返回,返回时调用回调函数
- client_->async_send_request(
- request, std::bind(&ServiceClient01::result_callback_, this,
- std::placeholders::_1));
- };
-
- private:
- // 声明客户端
- rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;
-
- void result_callback_(
- rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedFuture
- result_future)
- {
- auto response = result_future.get();
- RCLCPP_INFO(this->get_logger(), "计算结果:%ld", response->sum);
- }
- };
-
-
- int main(int argc, char** argv)
- {
- rclcpp::init(argc, argv);
- /*创建对应节点的共享指针对象*/
- auto node = std::make_shared<ServiceClient01>("service_client_01");
- //增加这一行,node->send_request(5, 6);,计算5+6结果
- node->send_request(5, 6);
- /* 运行节点,并检测退出信号*/
- rclcpp::spin(node);
- rclcpp::shutdown();
- return 0;
- }

void result_callback_( )
回调函数的参数是客户端AddTwoInts
类型的SharedFuture
对象,定义如下
利用C++11的新特性std::shared_future
创建的SharedResponse
类模板。
类模板 std::shared_future
提供访问异步操作结果的机制,使用get
函数即可获取结果。
在工作空间目录下开启终端
colcon build --packages-select example_service_rclcpp
source install/setup.bash
ros2 run example_service_rclcpp service_client_01
#开启新的终端
source install/setup.bash
ros2 run example_service_rclcpp service_server_01
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。