每个服务只能有一个服务器,但可以有多个客户端。
一、Service 通讯机制
服务是 ROS 节点的另一种通信方式。服务基于调用 - 响应模型,而非主题的发布者 - 订阅者模型。主题允许节点订阅数据流并获取持续更新,而服务仅在客户端明确调用时才提供数据。
在 ROS 2 中,服务指的是远程过程调用。换句话说,一个节点可以对另一个节点进行远程过程调用,该节点将执行计算并返回结果。
在 ROS 2 中,由于客户端通常需要等待结果,因此服务应该能够快速返回。服务不应该用于长时间运行的进程,尤其是在异常情况下可能需要被抢占的进程。如果你的服务需要执行长时间运行的计算,请考虑使用 Action。
一个服务由两部分组成:服务器和客户端。
服务器是接受远程过程请求并执行某些计算的实体,客户端是一个请求远程服务器代替其执行计算的实体。
服务的执行流程如下图:
二、创建自定义服务 C++ 版
2.1 创建功能包
cd ros2_learning/src
ros2 pkg create --build-type ament_cmake hello_world_service_cpp
其中,
使用 --build-type 指定编译系统为 ament_cmake
hello_world_service_cpp:自定义功能包名称
生成的目录结构如下:
hello_world_service_cpp
├── CMakeLists.txt
├── include
│ └── hello_world_service_cpp
├── LICENSE
├── package.xml
└── src
2.2 编辑源文件
我们编写一个服务端(server)和一个客户端(client),实现计算两个整型数字的和。
在 hello_world_service_cpp/include 目录下新增 server.h 文件,文件内容如下:
#pragma once
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
using AddTwoInts = example_interfaces::srv::AddTwoInts;
class AddTwoIntsServer : public rclcpp::Node {
public:
AddTwoIntsServer();
private:
void handle_add_two_ints(const std::shared_ptr<AddTwoInts::Request> request,
std::shared_ptr<AddTwoInts::Response> response);
rclcpp::Service<AddTwoInts>::SharedPtr service_;
};
在 hello_world_service_cpp/src 目录下新增 server.cpp 文件,文件内容如下:
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include "hello_world_service_cpp/server.h"
AddTwoIntsServer::AddTwoIntsServer() : Node("add_two_ints_server") {
// 创建服务
service_ = this->create_service<AddTwoInts>("add_two_ints",
std::bind(&AddTwoIntsServer::handle_add_two_ints, this,
std::placeholders::_1, std::placeholders::_2));
RCLCPP_INFO(this->get_logger(), "AddTwoInts 服务端已启动...");
}
void AddTwoIntsServer::handle_add_two_ints(
const std::shared_ptr<AddTwoInts::Request> request,
std::shared_ptr<AddTwoInts::Response> response) {
RCLCPP_INFO(this->get_logger(), "收到请求:%ld + %ld", request->a, request->b);
response->sum = request->a + request->b;
RCLCPP_INFO(this->get_logger(), "返回结果:%ld", response->sum);
}
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto server = std::make_shared<AddTwoIntsServer>();
rclcpp::spin(server);
rclcpp::shutdown();
return 0;
}
在 hello_world_service_cpp/include 目录下新增 client.h 文件,文件内容如下:
#pragma once
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include <chrono>
using namespace std::chrono_literals;
using AddTwoInts = example_interfaces::srv::AddTwoInts;
class AddTwoIntsClient : public rclcpp::Node {
public:
AddTwoIntsClient();
~AddTwoIntsClient();
bool send_request(int a, int b);
private:
rclcpp::Client<AddTwoInts>::SharedPtr client_;
};
在 hello_world_service_cpp/src 目录下新增 client.cpp 文件,文件内容如下:
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include "hello_world_service_cpp/client.h"
AddTwoIntsClient::AddTwoIntsClient() : Node("add_two_ints_client") {
client_ = this->create_client<AddTwoInts>("/add_two_ints");
}
AddTwoIntsClient::~AddTwoIntsClient() {}
bool AddTwoIntsClient::send_request(int a, int b) {
// 等待服务端
if (!client_->wait_for_service(5s)) {
RCLCPP_ERROR(this->get_logger(), "等待服务端超时");
return false;
}
auto request = std::make_shared<AddTwoInts::Request>();
request->a = a;
request->b = b;
RCLCPP_INFO(this->get_logger(), "发送请求:%ld + %ld", request->a, request->b);
auto result = client_->async_send_request(request);
if (rclcpp::spin_until_future_complete(this->shared_from_this(), result) ==
rclcpp::FutureReturnCode::SUCCESS) {
auto response = result.get();
RCLCPP_INFO(this->get_logger(), "计算结果:%ld", response->sum);
} else {
RCLCPP_ERROR(this->get_logger(), "请求失败");
}
return false;
}
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<AddTwoIntsClient>();
if (argc >= 3) {
int a = std::stoi(argv[1]);
int b = std::stoi(argv[2]);
RCLCPP_INFO(node->get_logger(), "请求参数:%d + %d", a, b);
node->send_request(a, b);
}
rclcpp::shutdown();
return 0;
}
2.3 编辑编译配置文件 CMakeLists.txt
默认生成的 CMakeLists.txt 文件内容如下:
由于新增了 server 和 client,所以要配置该文件的编译规则。
找到 ros2_learning/src/hello_world_service_cpp/CMakeLists.txt,修改如下:
修改说明如下:
find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)
# 指定头文件目录
include_directories(include)
# 指定源文件,生成可执行文件
add_executable(add_two_ints_server src/server.cpp)
# 指定可执行文件的依赖项
ament_target_dependencies(add_two_ints_server rclcpp example_interfaces)
add_executable(add_two_ints_client src/client.cpp)
ament_target_dependencies(add_two_ints_client rclcpp example_interfaces)
# 定义安装规则,指定可执行文件的安装目录
install(TARGETS add_two_ints_server add_two_ints_client DESTINATION lib/${PROJECT_NAME})
# 添加 ament 依赖导出
ament_export_dependencies(rclcpp)
ament_export_dependencies(example_interfaces)
2.4 编译工程
进入到工作空间 ros2_learning 目录,执行如下指令编译该工程:
colcon build
2.5 运行节点
ROS2 提供了 run 命令,可以根据包名和节点名,在任何目录执行。
但需要先设置环境变量,即让系统可以找到节点,进入到工作空间目录,执行如下指令:
source install/setup.bash
执行如下命令分别启动服务端和客户端节点:
ros2 run hello_world_service_cpp add_two_ints_server
ros2 run hello_world_service_cpp add_two_ints_client 3 7
启动节点后,打印如下:


