ROS2的核心概念C-服务
一、服务
服务可以实现类似你问我答的同步通信效果,此时可以在需要某个数据的时候,发一个查询的请求,然后尽快得到目标的最新位置,这样的通信模型和话题单向传输有所不同,变成了发送一个请求,反馈一个应答的形式,这种通信机制在ROS中成为服务(Service)。
二、服务模型
1、客户端/服务器模型
发送请求和反馈应答:
从服务的实现机制上来看,这种你问我答的形式叫做客户端/服务器模型,简称为CS模型,客户端在需要某些数据的时候,针对某个具体的服务,发送请求信息,服务器端收到请求之后,就会进行处理并反馈应答信息。
浏览器的应用场景:
这种通信机制在生活中也很常见,比如我们经常浏览的各种网页,此时你的电脑浏览器就是客户端,通过域名或者各种操作,向网站服务器发送请求,服务器收到之后返回需要展现的页面数据。

图1 服务的发送和应答
三、同步通信
这个过程一般要求越快越好,假设服务器半天没有反应,你的浏览器一直转圈圈,那有可能是服务器宕机了,或者是网络不好,所以相比话题通信,在服务通信中,客户端可以通过接收到的应答信息,判断服务器端的状态,我们也称之为同步通信。
四、一对多通信
在服务通信中服务器端是唯一,而客户端可以不唯一,比如古月居这个网站,服务器是唯一存在的,并没有多个完全一样的古月居网站,但是可以访问古月居网站的客户端是不唯一的,大家每一个人都可以看到同样的界面。所以服务通信模型中,服务器端唯一,但客户端可以不唯一。

图2 一个服务器和多个客户端
五、服务接口
和话题通信类似,服务通信的核心还是要传递数据,数据变成了两个部分,一个请求的数据,比如请求苹果位置的命令,还有一个反馈的数据,比如反馈苹果坐标位置的数据,这些数据和话题消息一样,在ROS中也是要标准定义的,话题使用.msg文件定义,服务使用的是.srv文件定义。
六、案例

图3 加法器
通过服务实现一个加法求解器的功能,当我们需要计算两个加数的求和结果时,就通过客户端节点,将两个加数封装成请求数据,针对服务“add_two_ints”发送出去,提供这个服务的服务器端节点,收到请求数据后,开始进行加法计算,并将求和结果封装成应答数据,反馈给客户端,之后客户端就会得到想要的结果。

图4 功能包内部结构
1、创建服务接口的功能包
进入工作空间study_ws中src路径下:
cd ~/study_ws/src
创建功能包demo_interface:
ros2 pkg create --build-type ament_cmake demo_interface
进入功能包路径下,创建srv目录:
①cd ./demo_interface ②mkdir srv
创建srv文件AddTwoInts.srv:
touch AddTwoInts.srv
对AddTwoInts.srv写入内容:
cat > srv/AddTwoInts.srv << 'EOF'
> int64 a
> int64 b
> ---
> int64 sum
> EOF
工作原理:
cat > 文件名 - 开始创建文件
<< 'EOF' - 表示接下来的内容直到遇到EOF为止,都写入文件
中间的内容 - 实际要写入文件的内容
最后的EOF - 结束标记,告诉系统内容写完了,可以用任何词代替EOF
2、功能包demo_interface的文件
AddTwoInts.srv:
int64 a
int64 b
---
int64 sum
CMakeLists.txt:
cmake_minimum_required(VERSION 3.8)
project(demo_interface)
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(rosidl_default_generators REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
# 声明服务文件
set(srv_files
"srv/AddTwoInts.srv"
)
# 生成接口代码
rosidl_generate_interfaces(${PROJECT_NAME}
${srv_files}
DEPENDENCIES
)
# 导出依赖
ament_export_dependencies(rosidl_default_runtime)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
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>demo_interface</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="sean@todo.todo">sean</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<!-- 添加必要的依赖 -->
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<!-- 声明这是一个接口包(关键!) -->
<member_of_group>rosidl_interface_packages</member_of_group>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
3、功能包learning_pkg_c的文件
add_two_ints_server.cpp:
/**
* @file add_two_ints_server.cpp
* @brief ROS2服务示例-提供两个整数相加的服务器处理功能
* @author
*/
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "demo_interface/srv/add_two_ints.hpp"
using namespace std::placeholders;
class AddServer : public rclcpp::Node
{
public:
AddServer(const std::string& node_name) : Node(node_name)
{
RCLCPP_INFO(this->get_logger(), "%s节点启动,欢迎使用双数据过滤器!", node_name.c_str());
// 创建服务器对象(服务接口类型、服务名、服务器回调函数)
srv_ = this->create_service<demo_interface::srv::AddTwoInts>(
"add_two_int",
std::bind(&AddServer::adder_callback, this, _1, _2));
}
private:
// 服务回调函数
void adder_callback(
const std::shared_ptr<demo_interface::srv::AddTwoInts::Request> request,
std::shared_ptr<demo_interface::srv::AddTwoInts::Response> response)
{
// 完成加法求和计算,将结果放到反馈的数据中
response->sum = request->a + request->b;
// 输出日志信息,提示已经完成加法求和计算
RCLCPP_INFO(this->get_logger(), "Incoming request: %ld b: %ld",
request->a, request->b);
}
rclcpp::Service<demo_interface::srv::AddTwoInts>::SharedPtr srv_;
};
int main(int argc, char* argv[])
{
// ROS2 C++接口初始化
rclcpp::init(argc, argv);
// 创建ROS2节点对象并进行初始化
auto node = std::make_shared<AddServer>("add2Ints_server_node");
// 循环等待ROS2退出
rclcpp::spin(node);
// 销毁节点对象
rclcpp::shutdown();
return 0;
}
add_two_ints_client.cpp:
/**
* @file add_two_ints_client.cpp
* @brief ROS2服务示例-发送两个加数,请求加法器计算
*/
#include <chrono>
#include <memory>
#include <iostream>
#include "rclcpp/rclcpp.hpp"
#include "demo_interface/srv/add_two_ints.hpp"
using namespace std::chrono_literals;
class AddClient : public rclcpp::Node
{
public:
AddClient(const std::string& name) : Node(name)
{
// 创建服务客户端对象(服务接口类型,服务名)
client_ = this->create_client<demo_interface::srv::AddTwoInts>("add_two_int");
// 创建服务请求的数据对象
request_ = std::make_shared<demo_interface::srv::AddTwoInts::Request>();
}
// 发送服务请求
void send_request(int a, int b)
{
request_->a = a;
request_->b = b;
// 循环等待服务器端成功启动
while (!client_->wait_for_service(1s))
{
RCLCPP_WARN(this->get_logger(), "服务器不可用,等待中...");
}
// 异步方式发送服务请求,并绑定回调函数
auto future_result = client_->async_send_request(
request_,
std::bind(&AddClient::request_result_callback, this, std::placeholders::_1));
}
private:
// 请求结果接收回调函数
void request_result_callback(rclcpp::Client<demo_interface::srv::AddTwoInts>::SharedFuture future)
{
auto response = future.get();
// 将收到的反馈信息打印输出
RCLCPP_INFO(this->get_logger(), "Result of add_two_int: %ld + %ld = %ld",
request_->a, request_->b, response->sum);
// 销毁节点对象并关闭
rclcpp::shutdown();
}
rclcpp::Client<demo_interface::srv::AddTwoInts>::SharedPtr client_;
std::shared_ptr<demo_interface::srv::AddTwoInts::Request> request_;
};
int main(int argc, char* argv[])
{
// 检查命令行参数
if (argc != 3)
{
std::cerr << "使用方法: " << argv[0] << " <数字A> <数字B>" << std::endl;
return 1;
}
// ROS2 C++接口初始化
rclcpp::init(argc, argv);
// 创建ROS2节点对象并进行初始化
auto node = std::make_shared<AddClient>("add2Ints_client_node");
// 解析命令行参数并发送服务请求
int a = std::atoi(argv[1]);
int b = std::atoi(argv[2]);
node->send_request(a, b);
// 循环等待回调完成
rclcpp::spin(node);
return 0;
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.8)
project(learning_pkg_c)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# 查找依赖包
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(demo_interface REQUIRED)
# 编译服务端节点
add_executable(add_server src/add_two_ints_server.cpp)
ament_target_dependencies(add_server rclcpp demo_interface)
# 编译客户端节点
add_executable(add_client src/add_two_ints_client.cpp)
ament_target_dependencies(add_client rclcpp demo_interface)
# 安装可执行文件 - 使用 ${PROJECT_NAME} 而不是 ${learning_pkg_c}
install(TARGETS
add_server
add_client
DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
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>learning_pkg_c</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="sean@todo.todo">sean</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>demo_interface</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
4、编译所有功能包并运行
切换路径到工作空间根目录下:cd ~/study_ws
可以先编译单个功能包,检查接口包是否安装成功:
(单个功能包的编译命令:colcon build --packages-select 功能包名)
colcon build --packages-select demo_interface
ros2 interface package demo_interface
编译创建的功能包:colcon build
设置环境变量:source install/local_setup.sh
5、运行结果:

图5 服务端

图6 客户端
七、服务命令的常用操作
查看服务列表:ros2 service list
查看服务数据类型:ros2 service type <service_name>
发送服务请求:ros2 service call <service_name> <service_type> <service_data>

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)