快速体验ROS通信之服务(Service)机制

一、参考资料

【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:

  1. 上述实现流程中,前三步使用 RPC 协议,最后两步使用 TCP/UDP 协议,默认TCP。
  2. 客户端请求时,必须保证服务端已经启动。
  3. 服务名相同的客户端可以有多个,服务端只能有1个。
  4. 与话题通信不同,服务通信过程中,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;
}
相关推荐
疯狂的doctor3 天前
conda和ros2 humble共存,rclpy引入失败
conda·ros
程序员龙一6 天前
百度Apollo Cyber RT底层原理解析
自动驾驶·ros·apollo·cyber rt
再遇当年6 天前
ubuntu22.04安装中文输入法且与ros2与clion不冲突
ubuntu·ros·输入法·中文输入法·fcitx5
明月满西楼7 天前
在Ubuntu 20.04实现ROS1 Noetic 与 ROS2 Foxy 多版本共存及切换
ubuntu·ros·noetic·foxy
Robot侠8 天前
ROS1从入门到精通 20:性能优化与最佳实践
图像处理·人工智能·计算机视觉·性能优化·机器人·ros
Robot侠8 天前
ROS1从入门到精通 15: 机器人视觉 - 图像处理与计算机视觉
图像处理·人工智能·计算机视觉·机器人·ros·机器人操作系统
Robot侠9 天前
ROS1从入门到精通 10:URDF机器人建模(从零构建机器人模型)
人工智能·机器人·ros·机器人操作系统·urdf机器人建模
Robot侠9 天前
ROS1从入门到精通 12:导航与路径规划(让机器人自主导航)
人工智能·机器人·自动驾驶·ros·路径规划·gazebo·导航
Robot侠9 天前
ROS1从入门到精通 9: TF坐标变换(机器人的空间认知)
人工智能·机器人·ros·机器人操作系统·tf坐标变换