在 ROS (Robot Operating System) 中,Service
是一种通信机制,允许节点之间进行同步的请求-响应交互。与 Publisher/Subscriber
模式不同,Service
提供了阻塞式调用,使客户端节点可以发起请求并等待服务器节点的响应。这种模式常用于需要即时响应的数据请求或指令的场景。
ROS Service 的工作原理
- Server(服务器):一个 ROS 节点可以作为服务器提供某个
Service
,并在接收到请求时执行相应操作。 - Client(客户端):另一个节点可以作为客户端发起请求,向指定
Service
发送数据,并接收服务器的响应。
每个 Service
都包含一个请求和一个响应数据结构,通常在 .srv
文件中定义。请求和响应类型可以是基本数据类型(如 int32
、float64
等)或自定义的复杂类型。
1. ROS Service 的基本组成
一个 ROS Service 由以下几个部分组成:
- Service 名称:用于标识服务,以便客户端和服务器能够匹配。
- Request:客户端发送给服务器的数据。
- Response:服务器处理后返回给客户端的结果。
2. 定义 ROS Service
在 ROS 中,我们使用 .srv
文件来定义 Service
的请求和响应数据格式。一个 .srv
文件通常包含两个部分:请求和响应,以 ---
分隔。
示例:定义一个求和的服务
首先我们先创建一个my_service的功能包,进入到自己的ros工作空间下面,执行:
catkin_create_pkg my_service roscpp std_msgs message_generation
在建立的功能包下再创建一个srv文件夹,用来存放“.srv”文件
假设我们想定义一个名为 AddTwoInts.srv
的服务,它接收两个整数并返回它们的和:
AddTwoInts.srv
文件内容:
int64 a
int64 b
---
int64 sum
在此示例中:
- 请求部分(在
---
之前):包含两个int64
类型的整数a
和b
。 - 响应部分(在
---
之后):包含一个int64
类型的整数sum
,用于存储返回的和。
3. 实现 ROS Service 服务器端
服务器节点会创建服务并定义服务回调函数,当接收到请求时,该回调函数将被调用。
在功能包的src目录下我们创建一个add_two_ints_server.cpp文件
#include "ros/ros.h"
#include "my_service/AddTwoInts.h" // 自动生成的头文件,包含服务类型
// 服务回调函数,接收请求和响应
bool add(my_service::AddTwoInts::Request &req,
my_service::AddTwoInts::Response &res) {
res.sum = req.a + req.b; // 计算并返回和
ROS_INFO("Request: a=%ld, b=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("Sending back response: [%ld]", (long int)res.sum);
return true;
}
int main(int argc, char **argv) {
ros::init(argc, argv, "add_two_ints_server");
ros::NodeHandle n;
// 创建服务
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
解释
advertiseService
:注册并启动一个名为"add_two_ints"
的服务。add
函数:处理请求的回调函数。函数接收请求对象req
和响应对象res
,并在响应对象中设置返回值。
4. 实现 ROS Service 客户端
客户端节点会发送请求并等待服务器的响应。
在功能包的src目录下我们创建一个add_two_ints_client.cpp文件
#include "ros/ros.h"
#include "my_service/AddTwoInts.h"
#include <cstdlib>
int main(int argc, char **argv) {
ros::init(argc, argv, "add_two_ints_client");
if (argc != 3) {
ROS_INFO("usage: add_two_ints_client X Y");
return 1;
}
ros::NodeHandle n;
ros::ServiceClient client = n.serviceClient<my_service::AddTwoInts>("add_two_ints");
my_service::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
if (client.call(srv)) {
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
} else {
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}
return 0;
}
解释
serviceClient
:创建一个服务客户端,连接到"add_two_ints"
服务。srv.request
:填充请求数据。client.call(srv)
:调用服务并等待响应。调用成功时,响应值存储在srv.response
中。
5. 启动 ROS Service 节点
编译和运行
假设项目目录结构如下:
my_service/
├── src/
│ ├── add_two_ints_server.cpp
│ └── add_two_ints_client.cpp
├── srv/
│ └── AddTwoInts.srv
└── CMakeLists.txt
└── package.xml
在 CMakeLists.txt
中添加以下配置:
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
message_generation
)
add_service_files(
FILES
AddTwoInts.srv
)
generate_messages(
DEPENDENCIES
std_msgs
)
catkin_package(
CATKIN_DEPENDS roscpp std_msgs message_runtime
)
add_executable(add_two_ints_server src/add_two_ints_server.cpp)
add_dependencies(add_two_ints_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_executable(add_two_ints_client src/add_two_ints_client.cpp)
add_dependencies(add_two_ints_client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
检查package.xml,确保以下编译依赖项正确
<buildtool_depend>catkin</buildtool_depend>
<build_depend>message_generation</build_depend>
<build_depend>roscpp</build_depend>
<build_depend>std_msgs</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>message_generation</exec_depend>
编译和运行节点:
cd ~/catkin_ws
catkin_make
source devel/setup.bash
roscore
rosrun my_service add_two_ints_server
rosrun my_service add_two_ints_client 5 10
一个终端先运行roscore启动ros master,另起一个终端开启service服务端:
再起一个终端开启客户端:
此时客户端就已经打印出了服务端返回的结果,服务端也打印了相应的log
这样就顺利完成了客户端的以此service请求。
6. Service 的典型应用场景
- 查询和获取数据:当节点需要获取一次性的数据(如传感器状态、当前坐标等)时,可以通过
Service
请求获取。 - 命令执行:可通过
Service
发起某种命令(如启动/停止某个过程)。 - 调试和参数设置:可以使用
Service
在运行时动态调整节点参数。
7. 与 Topic 的区别
特性 | Topic | Service |
---|---|---|
通信方式 | 发布-订阅(异步) | 请求-响应(同步) |
通信频率 | 通常用于高频率数据流(如传感器数据) | 通常用于低频率、即时响应需求 |
数据方向 | 单向流数据(Publisher → Subscriber) | 双向传输(Client ↔ Server) |
持久性 | 持续通信 | 请求-响应一次通信 |
使用场景 | 数据流、监控等 | 控制命令、数据查询等 |
总结
ROS Service 提供了一种便捷的请求-响应通信方式,适用于低频、请求即时处理的场景。通过 .srv
文件定义请求和响应结构,Service
提供了一种标准化的同步通信机制,可用于执行特定命令或获取数据。