ROS2笔记之服务通信和基于参数的服务通信区别
code review!
参考书籍
------ 《ROS2机器人开发从入门到实践》Page 92
文章目录
- ROS2笔记之服务通信和基于参数的服务通信区别
-
- 1.服务通信和基于参数的服务通信概述
- 2.主要区别
- 3.服务通信模式
-
- [3.1 服务端实现](#3.1 服务端实现)
- [3.2 客户端实现](#3.2 客户端实现)
- [3.3 实用示例:加法服务](#3.3 实用示例:加法服务)
-
- [3.3.1 服务端](#3.3.1 服务端)
- [3.3.2 客户端](#3.3.2 客户端)
- 4.参数通信模式
-
- [4.1 参数监听与更新](#4.1 参数监听与更新)
- [4.2 高级参数处理](#4.2 高级参数处理)
- 5.命令行使用对比
-
- [5.1 服务通信](#5.1 服务通信)
- [5.2 参数通信](#5.2 参数通信)
- 6.两种通信模式的比较
-
- [6.1 服务通信特点](#6.1 服务通信特点)
- [6.2 参数通信特点](#6.2 参数通信特点)
- [6.3 选择指南](#6.3 选择指南)
1.服务通信和基于参数的服务通信概述
-
服务通信
用于同步的请求-响应交互,客户端发送请求,服务端处理后返回响应,适合需要即时反馈的操作。例如,触发一个动作、获取传感器结果等。
-
参数通信
用于配置和动态更新节点的内部状态,不存在请求-响应的交互。参数的更新通常通过外部工具(如
ros2 param set
)或者节点内部更新来完成,适合调节节点行为、控制阈值等场景。
2.主要区别
特性 | 服务通信 | 基于参数的通信 |
---|---|---|
交互模式 | 请求-响应模式 | 获取/设置模式 |
使用场景 | 执行计算、触发行为、查询状态 | 配置管理、状态存储 |
通信方向 | 双向通信(客户端→服务端→客户端) | 读取/写入参数服务器 |
实时性 | 同步调用,等待响应 | 随时可访问,可设置变更回调 |
数据复杂性 | 支持复杂的自定义消息类型 | 支持基本类型和数组 |
生命周期 | 服务随节点创建和销毁 | 参数可持久化,超出节点生命周期 |
3.服务通信模式
服务通信是一种请求-响应的通信模式,适用于需要双向交互的场景。
3.1 服务端实现
cpp
#include "rclcpp/rclcpp.hpp"
#include "std_srvs/srv/trigger.hpp"
using Trigger = std_srvs::srv::Trigger;
class ServiceServer : public rclcpp::Node
{
public:
ServiceServer() : Node("service_server")
{
service_ = this->create_service<Trigger>(
"trigger_service",
std::bind(&ServiceServer::handle_service, this, std::placeholders::_1, std::placeholders::_2)
);
RCLCPP_INFO(this->get_logger(), "Service server started.");
}
private:
void handle_service(
const std::shared_ptr<Trigger::Request> request,
std::shared_ptr<Trigger::Response> response)
{
(void)request; // 未使用请求内容
response->success = true;
response->message = "Service executed successfully";
RCLCPP_INFO(this->get_logger(), "Service request handled.");
}
rclcpp::Service<Trigger>::SharedPtr service_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<ServiceServer>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
3.2 客户端实现
cpp
#include "rclcpp/rclcpp.hpp"
#include "std_srvs/srv/trigger.hpp"
using Trigger = std_srvs::srv::Trigger;
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto node = rclcpp::Node::make_shared("service_client");
auto client = node->create_client<Trigger>("trigger_service");
while (!client->wait_for_service(std::chrono::seconds(1))) {
RCLCPP_INFO(node->get_logger(), "Waiting for service to be ready...");
}
auto request = std::make_shared<Trigger::Request>();
auto future_result = client->async_send_request(request);
if (rclcpp::spin_until_future_complete(node, future_result) ==
rclcpp::FutureReturnCode::SUCCESS)
{
auto response = future_result.get();
RCLCPP_INFO(node->get_logger(), "Response: %s", response->message.c_str());
}
else {
RCLCPP_ERROR(node->get_logger(), "Failed to call service");
}
rclcpp::shutdown();
return 0;
}
3.3 实用示例:加法服务
3.3.1 服务端
cpp
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
class AddServer : public rclcpp::Node {
public:
AddServer() : Node("add_server") {
server_ = create_service<example_interfaces::srv::AddTwoInts>(
"add_two_ints", std::bind(&AddServer::add, this, std::placeholders::_1, std::placeholders::_2));
}
private:
void add(const example_interfaces::srv::AddTwoInts::Request::SharedPtr request,
example_interfaces::srv::AddTwoInts::Response::SharedPtr response) {
response->sum = request->a + request->b;
RCLCPP_INFO(get_logger(), "收到请求: %d + %d = %d", request->a, request->b, response->sum);
}
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr server_;
};
3.3.2 客户端
cpp
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
class AddClient : public rclcpp::Node {
public:
AddClient() : Node("add_client") {
client_ = create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
callService(3, 5);
}
private:
void callService(int a, int b) {
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = a;
request->b = b;
auto future = client_->async_send_request(request);
auto result = future.get(); // 等待响应
RCLCPP_INFO(get_logger(), "结果: %d", result->sum);
}
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;
};
4.参数通信模式
参数通信用于节点配置和状态共享,适合需要动态调整节点行为的场景。
4.1 参数监听与更新
cpp
#include "rclcpp/rclcpp.hpp"
class ParamNode : public rclcpp::Node
{
public:
ParamNode() : Node("parameter_node")
{
// 声明参数,初始值为 10
this->declare_parameter<int>("my_param", 10);
// 每秒钟读取一次参数值
timer_ = this->create_wall_timer(
std::chrono::seconds(1),
std::bind(&ParamNode::timer_callback, this)
);
RCLCPP_INFO(this->get_logger(), "Parameter node started.");
}
private:
void timer_callback()
{
int param_value = this->get_parameter("my_param").as_int();
RCLCPP_INFO(this->get_logger(), "Current parameter value: %d", param_value);
}
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<ParamNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
4.2 高级参数处理
cpp
#include "rclcpp/rclcpp.hpp"
class ParamNode : public rclcpp::Node {
public:
ParamNode() : Node("param_node") {
// 声明参数
declare_parameter("max_speed", 50.0);
declare_parameter("robot_name", "robot1");
// 获取参数
double max_speed = get_parameter("max_speed").as_double();
std::string name = get_parameter("robot_name").as_string();
RCLCPP_INFO(get_logger(), "参数值: max_speed=%f, name=%s", max_speed, name.c_str());
// 设置参数
auto result = set_parameter(rclcpp::Parameter("max_speed", 75.0));
if (result.successful) {
RCLCPP_INFO(get_logger(), "参数已更新");
}
// 参数变化回调
param_callback_ = add_on_set_parameters_callback(
std::bind(&ParamNode::paramCallback, this, std::placeholders::_1));
}
private:
rcl_interfaces::msg::SetParametersResult paramCallback(
const std::vector<rclcpp::Parameter> ¶meters) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
for (const auto ¶m : parameters) {
RCLCPP_INFO(get_logger(), "参数'%s'变更为: %s",
param.get_name().c_str(), param.value_to_string().c_str());
}
return result;
}
OnSetParametersCallbackHandle::SharedPtr param_callback_;
};
5.命令行使用对比
5.1 服务通信
bash
# 列出可用的服务
ros2 service list
# 查看服务接口类型
ros2 service type /add_integers
# 调用服务
ros2 service call /add_integers example_interfaces/srv/AddTwoInts "{a: 5, b: 10}"
5.2 参数通信
bash
# 列出所有参数
ros2 param list
# 获取参数值
ros2 param get /parameter_node max_velocity
# 设置参数值
ros2 param set /parameter_node max_velocity 5.0
# 保存参数到文件
ros2 param dump /parameter_node
# 从文件加载参数
ros2 param load /parameter_node params.yaml
6.两种通信模式的比较
6.1 服务通信特点
- 同步通信:提供请求-响应模式
- 适用场景:需要确认操作完成后才能继续的任务,如计算服务
- 使用方式:创建服务器和客户端,客户端发送请求并等待响应
6.2 参数通信特点
- 配置管理:主要用于节点配置和状态共享
- 动态调整:允许运行时更改节点行为
- 命令行支持 :可以通过ROS2命令行工具直接修改(如
ros2 param set
) - 参数回调:可以监听参数变化并触发相应行为
6.3 选择指南
- 对于需要节点间直接交互的场景,使用服务通信
- 对于配置和状态管理,使用参数通信
- 参数通信更适合用户或操作者动态调整系统行为
参数系统实际上在底层也使用了服务通信机制,但对用户提供了更简单的API,使得参数管理变得更加方便。