上一篇博客我们介绍了ROS的通信方式中的topic(话题)通信,我们知道topic是ROS中的一种单向的异步通信方式。然而有些时候单向的通信满足不了通信要求,比如当一些节点只是临时而非周期性的需要某些数据,如果用topic通信方式时就会消耗大量不必要的系统资源,造成系统的低效率高功耗。
这种情况下,就需要有另外一种请求-查询式的通信模型。这篇博客我们来介绍ROS通信中的另一种通信方式——service(服务)。

service方式在通信模型上与topic做了区别。Service通信是双向的,它不仅可以发送消息,同时还会有反馈。所以service包括两部分,一部分是请求方(Clinet),另一部分是应答方/服务提供方(Server)。这时请求方(Client)就会发送一个request,要等待server处理,反馈回一个reply,这样通过类似“请求-应答”的机制完成整个服务通信。

这种通信方式的示意图如下:
Node B是server(应答方),提供了一个服务的接口,叫做/Service,我们一般都会用string类型来指定service的名称,类似于topic。Node A向Node B发起了请求,经过处理后得到了反馈。

Service是同步通信方式,所谓同步就是说,此时Node A发布请求后会在原地等待reply,直到Node B处理完了请求并且完成了reply,Node A才会继续执行。Node A等待过程中,是处于阻塞状态的成通信。这样的通信模型没有频繁的消息传递,没有冲突与高系统资源的占用,只有接受请求才执行服务,简单而且高效。

我们对比一下这两种最常用的通信方式,加深我们对两者的理解和认识,具体见下表:

注意:远程过程调用(Remote Procedure Call,RPC),可以简单通俗的理解为在一个进程里调用另一个进程的函数。在下面的具体示例中也有体现。

客户端Client的编程实现

首先我们来实现下面的服务模型,主要的功能就是实现在我们的小海龟仿真客户端下添加一只新的小海龟。serve端是小海龟仿真器,client端则是本节中我们需要实现的功能。client主要用来发布一个生成小海龟的请求给serve端,serve端在完成请求后会反馈一个response给client端。它们之间的通讯桥梁就是我们的Service,在这个例子中就是spawn,整个的管理者依旧是Rosmaster。


与上篇博客一样,我们依旧创建一个功能包:

cd catkin_ws/src/
catkin_create_pkg learning_service roscpp rospy std_msgs geometry_msgs  turtlesim


进入src文件夹,新建一个turtle_spawn.cpp,将以下代码拷贝进去:

/*** 该例程将请求/spawn服务,服务数据类型turtlesim::Spawn*/#include <ros/ros.h>
#include <turtlesim/Spawn.h>int main(int argc, char** argv)
{// 初始化ROS节点ros::init(argc, argv, "turtle_spawn");// 创建节点句柄ros::NodeHandle node;// 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的serviceros::service::waitForService("/spawn");ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");// 初始化turtlesim::Spawn的请求数据turtlesim::Spawn srv;srv.request.x = 2.0;srv.request.y = 2.0;srv.request.name = "turtle2";// 请求服务调用ROS_INFO("Call service to spwan turtle[x:%0.6f, y:%0.6f, name:%s]", srv.request.x, srv.request.y, srv.request.name.c_str());add_turtle.call(srv);// 显示服务调用结果ROS_INFO("Spwan turtle successfully [name:%s]", srv.response.name.c_str());return 0;
};

需要指出的是,ros::service::waitForService("/spawn");这行语句是一个阻塞型的API,如果没有这个spawn的例程就会一直等待下去,就跟上网一样,如果请求的服务器不存在的话,我们是不可能连接成功的。add_turtle.call(srv);也是一个阻塞型函数,它会把请求发出去,然后就一直在那等待服务器给的反馈;跟上网浏览网页也是一个道理,我们访问网站,经常会有一个小圆圈在那里一直转,一直等到有响应以后才会刷新出来。


随后是我们的一些编译的规则,跟之前一样,在CMakeLists.txt的相应位置添加以下语句:

add_executable(turtle_spawn src/turtle_spawn.cpp)
target_link_libraries(turtle_spawn ${catkin_LIBRARIES})


随后回到catkin_ws进行编译即可,在相关的目录下可以看到编译完成的可执行文件:

由于每次都要刷新环境变量很麻烦,所以我们可以直接把环境变量写到.bashrc中:首先打开终端,进入catkin_ws,然后用pwd显示当前目录:


然后用显示的目录+devel/setup.bash,就是我们要像.bashrc中添加的路径。最后在home目录下同时按住ctrl+h,显示隐藏文件,找到.bashrc文件并且打开,拖到最下面,添加:

source /home/wh/catkin_ws/devel/setup.bash

这样操作之后,就不需要每次都source了。

现在我们可以直接打开新终端,启动该roscore,然后执行:rosrun turtlesim turtlesim_noderosrun learning_service turtle_spawn 就可以看的我们想要的结果:

服务端server的编程实现

这一节我们来实现服务端server的功能。首先是server通过Topic来给小海龟发送速度的指令,client端相当于一个开关,发布request给server,从而控制server,让server要不要给小海龟发指令。其中所用的Service的名字是我们自己定义的/turtle_command,数据类型是ROS中自带的Trigger(触发)。本节既涉及到serve的实现,也涉及到topic的实现,所以本节的代码还是相对比较复杂的。


首先依旧是在src文件中创建turtle_command_server.cpp文件,将以下代码copy进去:

/*** 该例程将执行/turtle_command服务,服务数据类型std_srvs/Trigger*/#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <std_srvs/Trigger.h>ros::Publisher turtle_vel_pub;
bool pubCommand = false;// service回调函数,输入参数req,输出参数res
bool commandCallback(std_srvs::Trigger::Request  &req,std_srvs::Trigger::Response &res)
{pubCommand = !pubCommand;// 显示请求数据ROS_INFO("Publish turtle velocity command [%s]", pubCommand==true?"Yes":"No");// 设置反馈数据res.success = true;res.message = "Change turtle command state!";return true;
}int main(int argc, char **argv)
{// ROS节点初始化ros::init(argc, argv, "turtle_command_server");// 创建节点句柄ros::NodeHandle n;// 创建一个名为/turtle_command的server,注册回调函数commandCallbackros::ServiceServer command_service = n.advertiseService("/turtle_command", commandCallback);// 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);// 循环等待回调函数ROS_INFO("Ready to receive turtle command.");// 设置循环的频率ros::Rate loop_rate(10);while(ros::ok()){// 查看一次回调函数队列ros::spinOnce();// 如果标志为true,则发布速度指令if(pubCommand){geometry_msgs::Twist vel_msg;vel_msg.linear.x = 0.5;vel_msg.angular.z = 0.2;turtle_vel_pub.publish(vel_msg);}//按照循环频率延时loop_rate.sleep();}return 0;
}

主要注意serve的创建以及里面commandCallback的机制。

随后就可以进行编译了,当然了,需要事先配置好CMakeLists.txt里的相关规则:

add_executable(turtle_command_server src/turtle_command_server.cpp)
target_link_libraries(turtle_command_server ${catkin_LIBRARIES})


最后就可以进行编译并且执行了:

cd ~/catkin_ws
catkin_make
source devel/setup.bash//这一步可以省略了,因为我们已经在.bashrc中设置过了
roscore
rosrun turtlesim turtlesim_node
rosrun learning_service turtle_command_server
rosservice call /turtle_command "{}"



随后只要反复输入rosservice call /turtle_command "{}"便可以控制小海龟的启停:

服务数据的定义和使用

上面两节我们使用了ROS定义好的spawn和trigger两种服务类型数据,本节介绍如何根据自己的需求定制服务数据并且使用。

定义

通过RosMaster管理本节需要实现的Client和Server,Client端要做的是发布一个显示一个人信息的Request,并且把个人信息用Service的数据格式发出去;然后在Serve端我们就会收到这样的Request,同时会包含一个人的基本信息,包括性别年龄等,通过日志显示出来后,会通过Response反馈显示结果;
其中用到的Service叫做show_person,是我们自己定义的,数据类型是我们自定义的learning_service::Person

参考上一篇博客我们自定义的.msg文件,这里主要定义的就是.srv文件,主要的区别在于service需要一个response,所以我们需要用—做分割,—以上的是request数据,—以下的是response数据,这样ros在编译时就会产生对应的头文件。


首先在learning_service文件夹中创建srv文件夹,随后在srv文件夹中touch Person.srv,如下图所示:

然后打开srv文件,加以下代码添加进去:

string name
uint8  age
uint8  sexuint8 unknown = 0
uint8 male    = 1
uint8 female  = 2---
string result

随后进行编译,同样的,需要在CMakeLists.txt和package.xml中添加相关的编译设置。
在package.xml中添加功能包依赖

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

在CMakeLists.txt添加编译选项

find_package( ……message_generation)
add_service_files(FILES Person.srv)
generate_messages(DEPENDENCIES std_msgs)
catkin_package(…… message_runtime)




然后回到catkin_ws下进行catkin_make进行编译:

可以在相关目录下找到编译生成的文件:

使用

随后我们就可以在自己的代码中来调用这些头文件了。
在下图所示目录下创建两个cpp文件,代码在下面:

/*** 该例程将请求/show_person服务,服务数据类型learning_service::Person*/#include <ros/ros.h>
#include "learning_service/Person.h"int main(int argc, char** argv)
{// 初始化ROS节点ros::init(argc, argv, "person_client");// 创建节点句柄ros::NodeHandle node;// 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的serviceros::service::waitForService("/show_person");ros::ServiceClient person_client = node.serviceClient<learning_service::Person>("/show_person");// 初始化learning_service::Person的请求数据learning_service::Person srv;srv.request.name = "Tom";srv.request.age  = 20;srv.request.sex  = learning_service::Person::Request::male;// 请求服务调用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);// 显示服务调用结果ROS_INFO("Show person result : %s", srv.response.result.c_str());return 0;
};
/*** 该例程将执行/show_person服务,服务数据类型learning_service::Person*/#include <ros/ros.h>
#include "learning_service/Person.h"// service回调函数,输入参数req,输出参数res
bool personCallback(learning_service::Person::Request  &req,learning_service::Person::Response &res)
{// 显示请求数据ROS_INFO("Person: name:%s  age:%d  sex:%d", req.name.c_str(), req.age, req.sex);// 设置反馈数据res.result = "OK";return true;
}int main(int argc, char **argv)
{// ROS节点初始化ros::init(argc, argv, "person_server");// 创建节点句柄ros::NodeHandle n;// 创建一个名为/show_person的server,注册回调函数personCallbackros::ServiceServer person_service = n.advertiseService("/show_person", personCallback);// 循环等待回调函数ROS_INFO("Ready to show person informtion.");ros::spin();return 0;
}



随后再进行编译即可,同样的,需要在CMakeLists.txt中添加相关的编译规则:

add_executable(person_server src/person_server.cpp)
target_link_libraries(person_server ${catkin_LIBRARIES})
add_dependencies(person_server ${PROJECT_NAME}_gencpp)add_executable(person_client src/person_client.cpp)
target_link_libraries(person_client ${catkin_LIBRARIES})
add_dependencies(person_client ${PROJECT_NAME}_gencpp)


随后再进行编译即可:

cd ~/catkin_ws
catkin_make
source devel/setup.bash
roscore
rosrun learning_service person_server
rosrun learning_service person_client

编译成功后可以在相关目录下看到生成的文件:

运行成功如下:

当然这里也可以先运行client端,因为有一个waitForService,所以可以达到一样的效果,只不过涉及到一个谁先谁等谁的问题:

说个题外话,当运行了很多ros程序时,要记得及时把roscore关掉,因为里面有一个参数服务器,存储了很多个参数,所以有时候可能会莫名其妙的报错,这是新手经常遇见的一个问题,所以要记得新开一个例程时要关掉roscore然后重新启动。

至此,我们已经完成了Topic和Service两种通信机制的学习,下篇博客我们将学习参数服务器的使用与编程方法。

ROS保姆级教程(二)--Service通讯方式实现相关推荐

  1. Unified Functional Testing(UFT)15.0.2入门保姆级教程(二),图文详解。QTP

    UFT入门之验证点和参数化 UFT15.0.2教程之侦测器(ObjectSpy)及脚本录制 请移步:Unified Functional Testing(UFT)15.0.2入门保姆级教程(一),图文 ...

  2. Mybatis实现增删改查 -- Mybatis快速入门保姆级教程(二)

    文章目录 前言 五.配置文件完成增删改查 1.学习目标 2.入门案例环境准备 3.查询--查询所有 4.查询--根据id查询 5. 查询--条件查询 6.查询--多条件动态查询 7.条件查询--单条件 ...

  3. 【UE Unreal Camera】【保姆级教程二】手把手教你通过UE获取摄像头帧数据

    概述   在UE 摄像头教程一中,我们已经通过Unreal自带的媒体播放器打开了摄像头,并且将摄像头的数据展示在了游戏画面中.当然这只是最基本的功能,一般情况下,我们需要对摄像头的画面数据进行处理,比 ...

  4. 【allegro 17.4软件操作保姆级教程二】布局前准备

  5. js对象、数组、字符串操作总结(保姆级教程)

    对象操作 1. 扩展运算符 作用是遍历某个对象或者数组 testMethod() {// 三个点 ... 俗称扩展运算符或延展运算符,需要注意的是扩展运算符在拷贝的时候只能深拷贝第一层,第二层及以下都 ...

  6. MySQL数据库篇---对数据库,数据库中表,数据库中表的记录进行添修删查操作---保姆级教程

    MySQL数据库知识点整理,保姆级教程 MySQL数据库存储方式 sql简介 SQL分类 DDL: 数据定义语言 DCL: 数据控制语言 DML:数据操控语言 DQL: 数据查询语言 SQL的使用 S ...

  7. Unified Functional Testing(UFT)15.0.2入门保姆级教程(一),图文详解。QTP

    UFT入门之侦测器(ObjectSpy)及录制第一个脚本 实验说明 1.Quick Test Pro(QTP)11.5后更名为Unified Functional Testing(UFT) 2. 实验 ...

  8. 玩转群晖NAS套件系列二:synology Drive的安装使用保姆级教程!

    本章介绍: 上一章节我们讲解<玩转群晖NAS套件系列一:cloud sync套件的安装与使用保姆级教程!>,此教程堪称史上手把手的保姆教程,受到广大网友的一致好评, 今天在这里介绍syno ...

  9. ac2100 反弹shell无法粘贴_手把手带你玩转NAS 篇二十一:小米Redmi AC2100路由器刷机padavan保姆级教程...

    手把手带你玩转NAS 篇二十一:小米Redmi AC2100路由器刷机padavan保姆级教程 2020-05-14 18:49:24 224点赞 1790收藏 241评论 你是AMD Yes党?还是 ...

最新文章

  1. Azure China (7) 使用WebMetrix将Web Site发布至Azure China
  2. 聊聊那块近10万块钱的铁皮
  3. SNMP学习笔记之SNMPv3的配置和认证以及TroubleShooting
  4. 【Opencv-Tools(一)】OpenCV中使用多线程处理图像
  5. Red Hat Enterprise 5 server 上安装 memcached 的问题记录
  6. [编程题] 按照左右半区的方式重新组合单链表
  7. HDU1312 Red and Black(dfs+连通性问题)
  8. MATLAB基本运算
  9. Html可以输入的下拉框设计
  10. 如何在线免费对PDF文档进行解密
  11. 双卡手机管理短信通知 | 屏蔽短信通知
  12. 太牛了,搜狐快站上线微信插件 电商插件升级
  13. 浅谈偏光镜使用与选购[机器视觉系列]
  14. 工业相机QE-量子转换效率
  15. vue-cli的webpack模板项目配置文件分析[转]
  16. TAZ生成实践(Intel芯片Mac Python 3.7.9)
  17. VC2010 无法启动程序 系统找不到指定文件
  18. 单片机led灯闪烁实验总结_新款LED型便携式实验室高强度紫外线灯对比说明
  19. SVN:working copy locked解决方法
  20. powerdesigner

热门文章

  1. 周明:NLP进步将如何改变搜索体验
  2. 高级产品经理十八种能力
  3. AJAX的概括(异步传输)
  4. DB2 元换算成万元 (除以/10000)
  5. java中的this
  6. Unity 判断点击的是否是UI
  7. android 开机动画更换,教程:如何把安卓开机动画,换成谷歌新Logo
  8. Mace-micro引擎编译与测试
  9. k8s UAT改环境
  10. CSS布局的三种方式