ROS服务器与客户端节点通讯 - (ROS学习笔记5)
服务由服务服务器(service server)和服务客户端(service client)组成,其中服务服务器仅在收到请求(request)时才会响应(response),而服务客户端则会发送请求并接收响应。与话题不同,服务是一次性消息通信。因此,当服务的请求和响应完成时,两个节点的连接会被断开。
这种服务通常在让机器人执行特定任务时用到。或者用于需要在特定条件下做出反应的节点。由于它是一次性的通信方式,因此对网络的负载很小,所以是一种非常有用的通信手段,例如被用作一种代替话题的通信手段。
创建功能包
以下命令创建ros_tutorials_service功能包。这个功能包依赖于message_generation、std_msgs和roscpp功能包,因此将他们用作依赖性选项。其中,message_generation是用于创建新消息的功能包,std_msgs是ROS的标准消息功能包,roscpp是在ROS中使用C/C++的客户端程序库,这些都必须在创建功能包之前安装好。用户可以在创建功能包时指定这些依赖关系,但也可以在创建功能包之后直接在package.xml中对其进行修改。
$ cd ~/catkin_ws/src
$ catkin_create_pkg ros_tutorials_service message_generation std_msgs roscpp

修改功能包配置文件(package.xml)
经过前面学习笔记4、5的经验,发现,这个文件其实可以不用改的,也就设置下版本号就行了。
修改构建配置文件(CMakeLists.txt)
$sudo gedit CMakeLists.txt

修改内容如下:
add_service_files(
FILES
SrvTutorial.srv
# Service1.srv
# Service2.srv
)
generate_messages(
DEPENDENCIES
std_msgs
)
catkin_package(
# INCLUDE_DIRS include
LIBRARIES ros_tutorials_service
# CATKIN_DEPENDS message_generation roscpp std_msgs
CATKIN_DEPENDS roscpp std_msgs
# DEPENDS system_lib
)
add_executable(service_server src/service_server.cpp)
add_executable(service_client src/service_client.cpp)
add_dependencies(service_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(service_client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(service_server ${catkin_LIBRARIES})
target_link_libraries(service_client ${catkin_LIBRARIES})
创建服务文件与节点
创建服务器文件
CMakeLists.txt文件中加了下面的选项。
add_service_files(FILES SrvTutorial.srv)
这是在构建本次的节点中使用的SrvTutorial.srv时所包含的内容。现在您还没有创建
SrvTutorial.srv,请按以下顺序创建它。
$ roscd ros_tutorials_service → 移动到功能包目录
$ mkdir srv → 在功能包中创建一个名为srv的新服务目录
$ cd srv → 转到创建的srv目录
$ gedit SrvTutorial.srv → 新建和修改SrvTutorial.srv文件

内容很简单。让我们以int64格式设计服务请求(request)a、b,和结果服务响应(response)result,如下所示。“---”是分隔符,用于分隔请求和响应。除了请求和响应之间有一个分隔符之外,它与上述话题的消息相同。
添加如下内容:
int64 a
int64 b
---
int64 result

创建服务服务器节点
在CMakeLists.txt文件中添加了如下选项来生成可执行文件。
add_executable(service_server src/service_server.cpp)
换句话说,是构建service_server.cpp文件来创建service_server可执行文件。我们按以下顺序编写一个具有服务服务器节点功能的程序吧。
$ roscd ros_tutorials_service/src → 移动到功能包的源代码目录src
$ gedit service_server.cpp → 创建和修改源文件


插入如下代码:
// ROS的基本头文件
#include "ros/ros.h"
// SrvTutorial服务头文件(构建后自动生成)
#include "ros_tutorials_service/SrvTutorial.h"
// 如果有服务请求,将执行以下处理
// 将服务请求设置为req,服务响应则设置为res。
bool calculation(ros_tutorials_service::SrvTutorial::Request &req,
ros_tutorials_service::SrvTutorial::Response &res)
{
// 在收到服务请求时,将a和b的和保存在服务响应值中
res.result = req.a + req.b;
// 显示服务请求中用到的a和b的值以及服务响应result值
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: %ld", (long int)res.result);
return true;
}
// 节点主函数
int main(int argc, char **argv)
{
// 初始化节点名称
ros::init(argc, argv, "service_server");
// 声明节点句柄
ros::NodeHandle nh;
// 声明服务服务器
// 声明利用ros_tutorials_service功能包的SrvTutorial服务文件的
// 服务服务器ros_tutorials_service_server
// 服务名称是ros_tutorial_srv,且当有服务请求时,执行calculation函数。
ros::ServiceServer ros_tutorials_service_server = nh.advertiseService("ros_tutorial_srv",
calculation);
ROS_INFO("ready srv server!");
// 等待服务请求
ros::spin();
return 0;
}


创建客户端文件与节点
在CMakeLists.txt文件中添加了一个选项来生成可执行文件。
add_executable(service_client src/service_client.cpp)
换句话说,是通过构建service_client.cpp文件来创建service_client可执行文件。我们按以下顺序编写一个执行服务客户端节点功能的程序吧。
$ roscd ros_tutorials_service/src → 移动到功能包的源代码目录src
$ gedit service_client.cpp → 创建和修改源文件

添加如下代码:
// ROS的基本头文件
#include "ros/ros.h"
// SrvTutorial服务头文件(构建后自动生成)
#include "ros_tutorials_service/SrvTutorial.h"
// 使用atoll函数所需的库
#include <cstdlib>
// 节点主函数
int main(int argc, char **argv)
{
// 初始化节点名称
ros::init(argc, argv, "service_client");
// 处理输入值错误
if (argc != 3)
{
ROS_INFO("cmd : rosrun ros_tutorials_service service_client arg0 arg1");
ROS_INFO("arg0: double number, arg1: double number");
return 1;
}
// 声明与ROS系统通信的节点句柄
ros::NodeHandle nh;
// 声明客户端,声明利用ros_tutorials_service功能包的SrvTutorial服务文件的
// 服务客户端ros_tutorials_service_client。
// 服务名称是"ros_tutorial_srv"
ros::ServiceClient ros_tutorials_service_client =
nh.serviceClient<ros_tutorials_service::SrvTutorial>("ros_tutorial_srv");
// 声明一个使用SrvTutorial服务文件的叫做srv的服务
ros_tutorials_service::SrvTutorial srv;
// 在执行服务客户端节点时用作输入的参数分别保存在a和b中
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
// 请求服务,如果请求被接受,则显示响应值
if (ros_tutorials_service_client.call(srv))
{
ROS_INFO("send srv, srv.Request.a and b: %ld, %ld", (long int)srv.request.a, (long int)srv.request.b);
ROS_INFO("receive srv, srv.Response.result: %ld", (long int)srv.response.result);
}
else
{
ROS_ERROR("Failed to call service ros_tutorial_srv");
return 1;
}
return 0;
}
然后保存退出

构建节点
使用以下命令构建ros_tutorials_service功能包的服务文件、服务服务器节点和客户端节点。ros_tutorials_service功能包的源文件位于“~/catkin_ws/src/ros_tutorials_service/src”目录中,服务文件位于“~/catkin_ws/src/ros_tutorials_service/srv”目录中。
$ cd ~/catkin_ws && catkin_make → 转到catkin目录并运行catkin构建
生成的文件位于“~/catkin_ws/build”目录和“~/catkin_ws/devel”目录。目录“~/catkin_ws/build”包含catkin构建中使用的配置,“~/catkin_ws/devel/lib/ros_tutorials_service”包含可执行文件,“~/catkin_ws/devel/include/ros_tutorials_service” 保存从消息文件自动生成的服务头文件。如果您想知道这些文件,请进入各自的目录查看。

运行测试
运行服务器
$ roscore
$ rosrun ros_tutorials_service service_server

运行客户端
$ rosrun ros_tutorials_service service_client 2 3

从上面编写的代码可知,在运行服务客户端时输入的执行参数2和3会被作为服务请求值。结果,2和3分别请求服务作为a和b值,后来作为结果值,作为响应值收到了这两者的总和。在这种情况下,它只是用作执行参数,但在实际使用中,可以用指令代替,可以将要计算的值和触发变量用作服务请求值。
rosservice call命令的用法
服 务 请 求 可 以 由 s e r v i c e_c l i e n t 等 服 务 客 户 端 节 点 来 执 行 , 但 有 一 种 使 用“rosservice call”或者rqt的serviceCaller的方法。我们来看看如何使用rosservice call吧。
在下面的命令中执行rosservice call命令之后,写入相应的服务名称,例如/ros_tutorial_srv,然后写入服务请求所需的参数即可。
$ rosservice call /ros_tutorial_srv 10 2
result: 12

在前面的例子中,我们如下面的服务文件,将int64类型的a和b设置为请求,所以我们输入了10和2作为参数。服务响应的结果是int64的result以12的值返回。
int64 a
int64 b
---
int64 result