一、参考资料
【ROS1】08-ROS通信机制------服务通信_ros1中服务-CSDN博客
ROS基础(二):ros通讯之服务(service)机制-CSDN博客
04 Service通信编程 - 知乎
二、rosservice相关介绍
1. topic vs. service
| 服务通信 (Service) | 话题通信 (Topic) | |
|---|---|---|
| 通信模型 | 请求/响应 | 发布/订阅 |
| 数据流向 | 双向 | 单向 |
| 同步性 | 同步 - 客户端会阻塞等待 | 异步 - 发布后立即返回 |
| 通信模型 | 发布+订阅 | 客户端+服务器端 |
| 反馈机制 | 无 | 有 |
| 缓冲区 | 有 | 无 |
| 节点关系 | 一(server)对多(client) | 一对多/多对多 |
| 传输数据格式 | *.srv |
*.msg |
| 数据流类型 | 离散的、事务性的 | 连续的数据流 |
| 主要目的 | 执行远程调用、获取确切结果 | 持续广播状态、传感器数据 |
| 适合场景 | 数据传输 | 逻辑处理 |
2. service适用场景
- 查询数据: "机器人,你现在的坐标是多少?"
- 触发动作: (1)"机械臂,移动到指定位置。" (2) "相机,拍一张照片。"
- 执行计算: "路径规划器,帮我计算一条从A到B的最优路径。"
- 更改状态: "机器人,切换到自动驾驶模式。"
3. Master/Server/Client角色
- Master (管理者):负责保管Server和Client的注册信息,并匹配服务名称相同的Server和Client,帮助他们建立连接。连接建立后,Client可以发送请求信息,Server收到请求后返回响应信息。
- Server(服务端)
- 提供特定功能或数据的节点。
- 它会"打广告"说:"我能提供XX服务!"(例如,"我能计算两个整数的和")。
- 它平时处于待命状态,一旦接收到请求,就执行相应的任务,并返回一个结果。
- Client(客户端)
- 需要某个特定功能或数据的节点。
- 它会向服务器发送一个具体的请求(例如,"请帮我计算 3 和 5 的和")。
- 发送请求后,它会暂停自己的工作,一直等待,直到收到服务器的响应。
4. service通讯流程

service通讯流程:
0)advertise:服务端注册
服务端(Server)向管理者(Master)注册信息,包括RPC地址和Service名字。Master会将服务端的注册信息加入到注册表中。
1)客户端注册
客户端(Client)向管理者(Master)注册信息,包括Service名字。Master会将客户端(Client)的注册信息加入到注册表中。
2)Master匹配信息:牵线搭桥
管理者(Master)通过查询注册表发现有匹配的服务端(Server)和客户端(Client),则通过RPC向客户端(Client)发送服务端(Server)的 TCP/UDP 地址信息。
3)客户端发送请求信息
客户端根据服务端的 TCP/UDP 地址与服务端建立网络连接,并发送请求信息。
4)服务端响应请求
服务端收到请求数据后,通过处理产生响应数据,通过 TCP/UDP 返回给客户端。
Note:
- 上述实现流程中,前三步使用 RPC 协议,最后两步使用 TCP/UDP 协议,默认TCP。
- 客户端请求时,必须保证服务端已经启动。
- 服务名相同的客户端可以有多个,服务端只能有1个。
- 与话题通信不同,服务通信过程中,ROS Master必须处于启动状态。
5. rosservice常用指令
bash
# 显式服务列表
rosservice list
# 打印服务信息
rosservice info [service_name]
# 打印服务类型
rosservice type [service_name]
# 打印ROSPRC uri
roservice uri [service_name]
# 按服务类型查找服务 [service_type]
rosservice find
# 显式服务参数
rosservice args [service_name]
# 带参数请求服务
rosservice call [[service_name]] [args参数]
6. 常用srv类型
常见srv类型 · 中国大学MOOC---------《机器人操作系统入门》讲义
srv数据类型:
bash
uint8
int32
msg数据类型:
bash
bool
string
uint8
uint16
int32
float32
float64
AddTwoInts.srv
bash
#对两个整数求和,虚线前是输入量,后是返回量
#文件位置:自定义srv文件
int32 a
int32 b
---
int32 sum
Empty.srv
bash
#文件位置:std_srvs/Empty.srv
#代表一个空的srv类型
---
GetMap.srv
bash
#文件位置:nav_msgs/GetMap.srv
#获取地图,注意请求部分为空
---
nav_msgs/OccupancyGrid map
GetPlan.srv
bash
#文件位置:nav_msgs/GetPlan.srv
#得到一条从当前位置到目标点的路径
geometry_msgs/PoseStamped start #起始点
geometry_msgs/PoseStamped goal #目标点
float32 tolerance #到达目标点的x,y方向的容错距离
---
nav_msgs/Path plan
SetBool.srv
bash
#文件位置:std_srvs/SetBools.srv
bool data # 启动或者关闭硬件
---
bool success # 标示硬件是否成功运行
string message # 运行信息
SetCameraInfo.srv
bash
#文件位置:sensor_msgs/SetCameraInfo.srv
#通过给定的CameraInfo相机信息,来对相机进行标定
sensor_msgs/CameraInfo camera_info #相机信息
---
bool success #如果调用成功,则返回true
string status_message #给出调用成功的细节
SetMap.srv
bash
#文件位置:nav_msgs/SetMap.srv
#以初始位置为基准,设定新的地图
nav_msgs/OccupancyGrid map
geometry_msgs/PoseWithCovarianceStamped initial_pose
---
bool success
TalkerListener.srv
bash
#文件位置: 自定义srv文件
---
bool success # 标示srv是否成功运行
string message # 信息,如错误信息等
Trigger.srv
bash
#文件位置:std_srvs/Trigger.srv
---
bool success # 标示srv是否成功运行
string message # 信息,如错误信息等
7. srv 文件
服务自定义文件通常放在功能包的 srv 文件夹下,文件扩展名为 .srv 。服务包含请求(request)数据 和应答(response)数据 ,中间用三个小短线 --- 隔开。
默认的srv文件:
bash
yoyo@yoyo:/media/sda3/catkin_ws$ ls /opt/ros/noetic/share/std_srvs/srv/
Empty.srv SetBool.srv Trigger.srv
查看srv:
bash
yoyo@yoyo:/media/sda3/catkin_ws$ rossrv show std_srvs/Empty
---
yoyo@yoyo:/media/sda3/catkin_ws$ rossrv show std_srvs/SetBool
bool data
---
bool success
string message
yoyo@yoyo:/media/sda3/catkin_ws$ rossrv show std_srvs/Trigger
---
bool success
string message
三、快速体验service机制
1. 简单示例一
项目目录:
bash
yoyo@yoyo:/media/sda3/catkin_ws$ tree -L 5 src/service_hello_world/
src/service_hello_world/
├── CMakeLists.txt
├── include
│ └── service_hello_world
├── package.xml
├── src
│ ├── service_hello_world_client.cpp
│ └── service_hello_world_server.cpp
└── srv
└── AddTwoInts.srv
1.1 创建并初始化功能包
bash
yoyo@yoyo:/media/sda3/catkin_ws/src$ catkin_create_pkg service_hello_world roscpp rospy std_srvs std_msgs message_generation
Created file service_hello_world/package.xml
Created file service_hello_world/CMakeLists.txt
Created folder service_hello_world/include/service_hello_world
Created folder service_hello_world/src
Successfully created files in /media/sda3/catkin_ws/src/service_hello_world. Please adjust the values in package.xml.
创建后,目录结构如下:
bash
yoyo@yoyo:/media/sda3/catkin_ws/src$ tree -L 3 service_hello_world/
service_hello_world/
├── CMakeLists.txt
├── include
│ └── service_hello_world
├── package.xml
└── src
1.2 创建 srv 文件
在srv目录下创建 AddTwoInts.srv 文件:
bash
int64 a
int64 b
---
int64 sum
1.3 修改 package.xml 文件
新增一下两行,一个是编译依赖,一个是运行依赖。
xml
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
1.4 修改 CMakeLists.txt
在 find_package 中添加 message_generation 包依赖:
cmake
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_srvs
std_msgs # 新增
message_generation # 新增
)
在 add_service_files 中添加srv文件:
cmake
# 取消注释并新增.srv文件
# 无须添加.srv文件路径,编译时会自动查找功能包下的srv文件
add_service_files(
FILES
AddTwoInts.srv # 新增
)
在 generate_messages 中添加生成信息的依赖包std_msgs:
bash
# 取消注释
generate_messages(
DEPENDENCIES
std_msgs # Or other packages containing msgs
)
在 catkin_package 中添加 message_runtime 依赖包:
bash
# 取消注释
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES service_hello_world
CATKIN_DEPENDS roscpp rospy std_srvs std_msgs message_runtime
# DEPENDS system_lib
)
添加依赖:
cmake
add_executable(${PROJECT_NAME}_server src/service_hello_world_server.cpp)
target_link_libraries(${PROJECT_NAME}_server
${catkin_LIBRARIES}
)
cmake
add_executable(${PROJECT_NAME}_client src/service_hello_world_client.cpp)
target_link_libraries(${PROJECT_NAME}_client
${catkin_LIBRARIES}
)
1.5 编写server
在 src 路径下,创建 service_hello_world_server.cpp 以实现服务端:
c++
#include <ros/ros.h>
#include <service_hello_world/AddTwoInts.h>
// 服务处理函数。当收到请求时,ROS会调用这个函数。
// 函数的返回值是 bool 类型,如果成功处理返回 true,否则返回 false。
// 参数是请求对象(req)和响应对象(res)的引用。
bool add(service_hello_world::AddTwoInts::Request &req, service_hello_world::AddTwoInts::Response &res)
{
// 从请求对象中取出数据
res.sum = req.a + req.b;
// ROS_INFO 用于在终端打印日志信息,类似于C++的cout
ROS_INFO("请求: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("发送响应: sum=%ld", (long int)res.sum);
return true; // 表示服务成功执行
}
int main(int argc, char **argv)
{
setlocale(LC_ALL,"");
// 1. 初始化ROS节点
ros::init(argc, argv, "add_two_ints_server");
// 2. 创建节点句柄
ros::NodeHandle n;
// 3. 创建一个名为 "add_two_ints" 的服务
// 它会调用 add 函数来处理请求
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
ROS_INFO("服务已就绪,等待客户端请求...");
// 4. 进入循环,等待回调函数的触发
ros::spin();
return 0;
}
1.6 编写client
在 src 路径下,创建 service_hello_world_client.cpp 以实现客户端:
c++
#include <ros/ros.h>
#include <service_hello_world/AddTwoInts.h>
#include <cstdlib> // 用于 atoll 函数
int main(int argc, char **argv)
{
setlocale(LC_ALL,"");
// 初始化ROS节点
ros::init(argc, argv, "add_two_ints_client");
// 检查命令行参数是否正确
if (argc != 3)
{
ROS_INFO("用法: add_two_ints_client X Y");
return 1;
}
// 创建节点句柄
ros::NodeHandle n;
// 创建一个客户端,连接到名为 "add_two_ints" 的服务
// serviceClient 会一直尝试连接,直到成功
ros::ServiceClient client = n.serviceClient<service_hello_world::AddTwoInts>("add_two_ints");
// 创建一个服务对象 srv
service_hello_world::AddTwoInts srv;
// 将命令行参数转换为 long long (int64) 并填充到请求中
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
// 调用服务
// client.call() 是一个阻塞操作。它会发送请求并等待,直到收到响应。
// 如果服务调用成功,call() 返回 true,响应数据会填充到 srv.response 中。
// 如果失败,call() 返回 false。
client.waitForExistence();
if (client.call(srv))
{
ROS_INFO("响应 Sum: %ld", (long int)srv.response.sum);
}
else
{
ROS_ERROR("调用服务失败");
return 1;
}
return 0;
}
1.7 catkin_make编译
bash
# 编译
catkin_make --pkg service_hello_world
# 清理
catkin_make clean
1.8 启动roscore
bash
# 设置环境变量
source devel/setup.bash
# 启动ros Master
roscore
1.9 启动server/client
bash
# 启动服务端
rosrun service_hello_world service_hello_world_server
# 启动客户端
rosrun service_hello_world service_hello_world_client
客户端输出:
bash
yoyo@yoyo:/media/sda3/catkin_ws$ rosrun service_hello_world service_hello_world_client 15 3
[INFO] [1756950548.532127230]: 响应 Sum: 18
服务端输出:
bash
yoyo@yoyo:/media/sda3/catkin_ws$ rosrun service_hello_world service_hello_world_server
[INFO] [1756950535.043012525]: 服务已就绪,等待客户端请求...
[INFO] [1756950548.531915271]: 请求: x=15, y=3
[INFO] [1756950548.531974091]: 发送响应: sum=18
2. 简单示例二
04 ROS Client-Service-Server实例-阿里云开发者社区
以下仅记录关键步骤,其他步骤同示例一。
2.1 创建srv文件
在srv目录下创建 Person.srv 文件:
bash
string name
uint8 age
uint8 sex
uint8 unknown = 0
uint8 male= 1
uint8 female= 2
---
string result
2.2 编写server
c
/*
/show_person server
ServiceDataType is learning_service::Person
*/
#include<ros/ros.h>
#include"learning_service/Person.h"
//Service CALLBACKFUNCTION
bool PersonCallBackFunction(learning_service::Person::Request &req,learning_service::Person::Response &res)
{
//show request data
ROS_INFO("Person name:%s age:%d sex:%d",req.name.c_str(),req.age,req.sex);
//set response data
res.result="response:OK";
return true;
}
int main(int argc,char** argv)
{
//ROSINIT
ros::init(argc,argv,"person_server");
//Create NodeHandle
ros::NodeHandle n;
//Create a server /show_person
//register the CALLBACKFUNCTION
//Server provide the Service /show_person
//when server receive the request from Client
//Server will call the CBF
ros::ServiceServer person_service= n.advertiseService("/show_person",PersonCallBackFunction);
//waiting for request and call the CBF
ROS_INFO("READY TO SHOW PERSON INFORMATION");
ros::spin();
return 0;
}
2.3 编写client
c
/*
This node will request service /show_person
ServiceDataType is learning_service::Person
*/
#include<ros/ros.h>
#include "learning_service/Person.h"
int main(int argc,char **argv)
{
//ROSINIT
ros::init(argc,argv,"person_client");
//Create NodeHandle
ros::NodeHandle n;
//Searching for service /show_person
//Create a Client and connect it with service /show_person
ros::service::waitForService("/show_person");
ros::ServiceClient person_client = n.serviceClient<learning_service::Person>("/show_person");
//Initialize the date which would be send to service /show_person
learning_service::Person srv;
srv.request.name="Herman123";
srv.request.age=20;
srv.request.sex=learning_service::Person::Request::male;
//send request to Service /show_person
ROS_INFO("Call service to show person[name:%s age:%d sex:%d]",srv.request.name.c_str(),srv.request.age,srv.request.sex);
person_client.call(srv);
//show the response
ROS_INFO("Result:%s",srv.response.result.c_str());
return 0;
}
3. 自定义复杂srv
3.1 创建srv文件
在srv目录下创建 ComplexService.srv 文件:
bash
# ComplexService.srv
# 请求部分:自定义嵌套消息
UserInfo user_info # 用户信息
---
# 响应部分:自定义响应消息
ResponseInfo response_info # 响应信息
3.2 自定义msg消息类型
在 msg 目录下创建 UserInfo.msg 文件:
bash
# UserInfo.msg
string name
int32 age
string address
在 msg 目录下创建 ResponseInfo.msg 文件:
bash
# ResponseInfo.msg
bool success
string message
3.3 修改 package.xml 文件
xml
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
3.4 修改 CmakeLists.txt 文件
cmake
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# 添加自定义服务和消息文件
add_service_files(
FILES
ComplexService.srv
)
add_message_files(
FILES
UserInfo.msg
ResponseInfo.msg
)
generate_messages(
DEPENDENCIES
std_msgs
)
catkin_package(
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
)
3.5 编写Server
c
/*
/show_person server
ServiceDataType is learn_service::Person
*/
#include <ros/ros.h>
#include <learn_service/Person.h>
//Service CALLBACKFUNCTION
bool PersonCallBackFunction(learn_service::Person::Request &req,learn_service::Person::Response &res)
{
//show request data
ROS_INFO("Person name:%s age:%d sex:%d",req.name.c_str(),req.age,req.sex);
//set response data
res.result="response:OK";
return true;
}
int main(int argc,char** argv)
{
//ROSINIT
ros::init(argc,argv,"person_server");
//Create NodeHandle
ros::NodeHandle n;
//Create a server /show_person
//register the CALLBACKFUNCTION
//Server provide the Service /show_person
//when server receive the request from Client
//Server will call the CBF
ros::ServiceServer person_service= n.advertiseService("/show_person",PersonCallBackFunction);
//waiting for request and call the CBF
ROS_INFO("READY TO SHOW PERSON INFORMATION");
ros::spin();
return 0;
}
3.6 编写Client
c
#include <ros/ros.h>
#include <learn_service/ComplexService.h>
int main(int argc, char **argv)
{
ros::init(argc, argv, "complex_service_client");
ros::NodeHandle n;
ros::ServiceClient client = n.serviceClient<learn_service::ComplexService>("complex_service");
learn_service::ComplexService srv;
// 填充请求
srv.request.user_info.name = "John Doe";
srv.request.user_info.age = 25;
srv.request.user_info.address = "1234 Main St, Some City";
if (client.call(srv)) {
ROS_INFO("Response: %s", srv.response.response_info.message.c_str());
} else {
ROS_ERROR("Failed to call service complex_service");
}
return 0;
}