之前的文章,介绍过ROS2 中话题(Topic)通信,本篇来介绍另一种通信方式——服务(Service)
1 Service功能简介
ROS2 的 Service 是一问一答的请求 - 应答(Request-Response)通信模型。
与Topic的对比来看:
Topic:流式单向广播
-
-
- Publisher 不停发,Subscriber 被动接收没有 “返回结果”适合持续数据流(摄像头、IMU、里程计)
-
Service:触发式同步交互
Client(客户端)主动发起一次请求(带参数)
Server(服务端)监听服务,收到请求执行业务逻辑
Server 生成应答数据返回给 Client
本次通信结束,一次请求对应一次回复
简单来说:Topic 是 “广播数据流”,Service 是 “调用函数,带入参、带回返回值”
2 API接口
2.1 服务端
2.1.1 create_service
template<typename ServiceT>
Service::SharedPtr create_service(
const std::string & service_name,
ServiceCallbackType callback,
const rclcpp::QoS & qos_profile = rclcpp::ServicesQoS()
);
参数介绍
service_name:服务名字,字符串,全局话题名,推荐加/开头,如"add_two_ints"
callback:回调函数,收到客户端请求就执行。 回调函数固定签名:
void callback(
const std::shared_ptr<XXXService::Request> request,
std::shared_ptr<XXXService::Response> response
);
request:只读,客户端传过来的数据,request->a,
request->bresponse:输出参数,引用传递,你在回调里写response->sum = xxx,ROS2 自动把 response 发回客户端
qos_profile:QoS 质量策略,Service 默认rclcpp::ServicesQoS(),一般不用修改。
创建举例
service_ = this->create_service<test_02_service::srv::AddTwoInts>(
"add_two_ints",
std::bind(&AddTwoIntServer::service_callback, this, _1, _2)
);
std::bind 绑定类成员函数,_1对应 request,_2对应 response。
2.1.2 spin
rclcpp::spin(node);
服务端必须 spin,spin 会持续监听事件,等待客户端请求,触发回调。如果没有 spin,服务端不会收到任何请求。
2.2 客户端
2.2.1 create_client
template<typename ServiceT>
Client::SharedPtr create_client(
const std::string & service_name,
const rclcpp::QoS & qos_profile = rclcpp::ServicesQoS()
);
参数介绍
service_name:必须和服务端服务名一模一样,大小写敏感qos:服务 QoS,默认即可
创建举例
client_ = this->create_client<test_02_service::srv::AddTwoInts>("add_two_ints");
2.2.2 wait_for_service
等待服务端上线
bool wait_for_service(std::chrono::duration<Rep, Period> timeout)
返回值
- 返回 true:服务已经就绪,可以发请求返回 false:超时没找到服务(服务端没启动、名字写错、网络不通)
建议每次发请求前调用,避免直接发送请求失败。
示例
if (!client_->wait_for_service(1s))
{
RCLCPP_ERROR(this->get_logger(), "服务不可用!");
return false;
}
2.2.3 async_send_request
异步发送请求
std::shared_future<typename ServiceT::Response::SharedPtr>
async_send_request(typename ServiceT::Request::SharedPtr request);
-
- 传入:
request
-
- 请求结构体指针,填充 a、b 等参数返回:
future
-
- 异步结果句柄,
不会阻塞当前线程,只是把请求发出去。
ROS2 推荐使用async 异步接口,不推荐旧版本同步阻塞调用
2.2.4 spin_until_future_complete
一边 spin 节点处理事件,一边等待 future 应答返回
FutureReturnCode spin_until_future_complete(
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
const std::shared_future<ResponseT::SharedPtr> & future,
std::chrono::duration<Rep,Period> timeout = std::chrono::duration<Rep,Period>::max()
)
返回枚举:
rclcpp::FutureReturnCode::SUCCESS:成功收到应答
TIMEOUT:等待超时,没收到回复
INTERRUPTED:被中断
拿到 future 结果:auto response = result_future.get();
3 代码示例
3.1 时序图
3.2 工程创建
cd ~/dev_ws/src
ros2 pkg create --build-type ament_cmake test_04_service --dependencies rclcpp rosidl_default_generators
cd test_04_service
此时查看工程目录
xxpcb@xxpcb-ubuntu20:~/dev_ws/src/test_04_service$ tree
.
├── CMakeLists.txt
├── include
│ └── test_04_service
├── package.xml
└── src
3 directories, 2 files
xxpcb@xxpcb-ubuntu20:~/dev_ws/src/test_04_service$
然后创建代码
touch src/service_server.cpp
touch src/service_client.cpp
还有创建srv文件
mkdir srv
touch srv/AddTwoInts.srv
srv中写入如下内容:
# Request(Client → Server)
int64 a
int64 b
---
# Response(Server → Client)
int64 sum
这里---上面的客户端请求的参数, 下面是服务端回复的结果
此时的工程目录
xxpcb@xxpcb-ubuntu20:~/dev_ws/src/test_04_service$ tree
.
├── CMakeLists.txt
├── include
│ └── test_04_service
├── package.xml
├── src
│ ├── service_client.cpp
│ └── service_server.cpp
└── srv
└── AddTwoInts.srv
4 directories, 5 files
xxpcb@xxpcb-ubuntu20:~/dev_ws/src/test_04_service$
3.3 服务端代码
service_server.cpp
主要逻辑是:
- create_service创建服务端service_callback进行请求处理rclcpp::spin
#include <rclcpp/rclcpp.hpp>
#include "test_04_service/srv/add_two_ints.hpp"
using namespace std::placeholders;
class AddTwoIntServer : public rclcpp::Node
{
public:
AddTwoIntServer() : Node("add_two_ints_server")
{
service_ = this->create_service<test_04_service::srv::AddTwoInts>(
"add_two_ints",
std::bind(&AddTwoIntServer::service_callback, this, _1, _2)
);
RCLCPP_INFO(this->get_logger(), "服务端已启动,等待客户端请求...");
}
private:
void service_callback(
const std::shared_ptr<test_04_service::srv::AddTwoInts::Request> request,
std::shared_ptr<test_04_service::srv::AddTwoInts::Response> response
)
{
response->sum = request->a + request->b;
RCLCPP_INFO(this->get_logger(),
"收到请求: a=%ld, b=%ld → sum=%ld",
request->a, request->b, response->sum);
}
rclcpp::Service<test_04_service::srv::AddTwoInts>::SharedPtr service_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<AddTwoIntServer>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
3.4 客户端代码
service_client.cpp
主要逻辑是:
- create_client创建客户端send_request发送请求
-
- async_send_request发送异步请求spin_until_future_complete
#include <rclcpp/rclcpp.hpp>
#include "test_04_service/srv/add_two_ints.hpp"
usingnamespacestd::chrono_literals;
class AddTwoIntClient :public rclcpp::Node
{
public:
AddTwoIntClient() : Node("add_two_ints_client")
{
client_ = this->create_client<test_04_service::srv::AddTwoInts>("add_two_ints");
}
bool send_request(int64_t a, int64_t b)
{
if (!client_->wait_for_service(1s))
{
RCLCPP_ERROR(this->get_logger(), "服务不可用!");
returnfalse;
}
auto request = std::make_shared<test_04_service::srv::AddTwoInts::Request>();
request->a = a;
request->b = b;
auto result_future = client_->async_send_request(request);
if (rclcpp::spin_until_future_complete(this->get_node_base_interface(), result_future)
== rclcpp::FutureReturnCode::SUCCESS)
{
auto response = result_future.get();
RCLCPP_INFO(this->get_logger(), "收到应答 sum = %ld", response->sum);
returntrue;
}
else
{
RCLCPP_ERROR(this->get_logger(), "请求失败!");
returnfalse;
}
}
private:
rclcpp::Client<test_04_service::srv::AddTwoInts>::SharedPtr client_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<AddTwoIntClient>();
node->send_request(100, 200);
rclcpp::shutdown();
return0;
}
3.5 运行结果
在CMake中添加:
# 生成srv接口
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/AddTwoInts.srv"
)
# 服务端可执行文件
add_executable(service_server src/service_server.cpp)
ament_target_dependencies(service_server
rclcpp
)
# 把本包生成的srv接口链接到目标
rosidl_target_interfaces(service_server
${PROJECT_NAME} "rosidl_typesupport_cpp")
# 客户端可执行文件
add_executable(service_client src/service_client.cpp)
ament_target_dependencies(service_client
rclcpp
)
# 把本包生成的srv接口链接到目标
rosidl_target_interfaces(service_client
${PROJECT_NAME} "rosidl_typesupport_cpp")
install(TARGETS
service_server
service_client
DESTINATION lib/${PROJECT_NAME}
)
package.xml中添加一句
<member_of_group>rosidl_interface_packages</member_of_group>
然后编译
cd ~/dev_ws
rm -rf build log install
colcon build --packages-select test_04_service
source install/setup.bash
运行服务端
ros2 run test_04_service service_server
另一窗口, 运行客户端
source install/setup.bash
ros2 run test_04_service service_client
运行结果如下:
4 总结
本篇介绍了ROS2中客户端-服务器的机制, 并通过C++示例代码演示使用方法.
55