服务通信详解

服务通信理论模型

服务通信较之于话题通信更简单些,理论模型如下图所示,该模型中涉及到三个角色:

  • ROS master(管理者)
  • Server(服务端)
  • Client(客户端)

ROS Master 负责保管 Server 和 Client 注册的信息,并匹配话题相同的 Server 与 Client ,帮助 Server 与 Client 建立连接,连接建立后,Client 发送请求信息,Server 返回响应信息。

整个流程由以下步骤实现:

如图所示:ROS Master相当于114平台,Server相当于服务公司(保洁公司),Client相当于客户(我)

0.Server注册

Server 启动后,会通过RPC在 ROS Master 中注册自身信息,其中包含提供的服务的名称。ROS Master 会将节点的注册信息加入到注册表中。相当于保洁公司在114平台注册自身信息(疏通下水道)

1.Client注册

Client 启动后,也会通过RPC在 ROS Master 中注册自身信息,包含需要请求的服务的名称。ROS Master 会将节点的注册信息加入到注册表中。相当于我需要访问114平台,注册自己所需的服务(疏通下水道)。

2.ROS Master实现信息匹配

ROS Master 会根据注册表中的信息匹配Server和 Client,并通过 RPC 向 Client 发送 Server 的 TCP 地址信息。相当于114平台匹配话题,并将保洁公司的电话提供给我

3.Client发送请求

Client 根据步骤2 响应的信息,使用 TCP 与 Server 建立网络连接,并发送请求数据。相当于我打电话到保洁公司

4.Server发送响应

Server 接收、解析请求的数据,并产生响应结果返回给 Client。保洁公司产生响应

5.注意

  1. 客户端请求被处理时,需要保证服务器已经启动
  2. 服务端和客户端都可以存在多个

服务通信自定义srv

需求:

服务通信中,客户端提交两个整数至服务端,服务端求和并响应结果到客户端,请创建服务器与客户端通信的数据载体

流程:

srv 文件内的可用数据类型与 msg 文件一致,且定义 srv 实现流程与自定义 msg 实现流程类似:

  1. 按照固定格式创建srv文件

  2. 编辑配置文件

  3. 编译生成中间文件

0、创建功能包  会生成include  src   CmakeLists.txt  package.xml四个文件

cd ~/catkin_ws/src
 
catkin_create_pkg plumbing_sever_client std_msgs rospy roscpp

然后在plumbing_sever_client/src路径下新建demo01_server.cpp、demo02_client.cpp的.txt文件 ,而后在plumbing_sever_client下新建srv文件夹,在srv下新建AddInts.srv文本。

1.定义srv文件

服务通信中,数据分成两部分,请求与响应,在 srv 文件中请求和响应使用---分割,具体实现如下:

功能包下新建 srv 目录,添加 AddInts.srv 文件,内容:

# 客户端请求时发送的两个数字
int32 num1
int32 num2
---
# 服务器响应发送的数据
int32 sum

2.编辑配置文件

package.xml中添加编译依赖与执行依赖

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

  <!-- exce_depend 以前对应的是 run_depend 现在非法-->

CMakeLists.txt编辑 srv 相关配置

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
# 需要加入 message_generation,必须有 std_msgs
## Generate services in the 'srv' folder
add_service_files(
  FILES
  AddInts.srv
)

## Generate added messages and services with any dependencies listed here
generate_messages(
  DEPENDENCIES
  std_msgs
)

注意: 官网没有在 catkin_package 中配置 message_runtime,经测试配置也可以

3.编译

编译后的中间文件查看:

C++ 需要调用的中间文件(.../catkin_ws/devel/include/plumbing_sever_client/xxx.h)如下

 后续调用相关srv时,是从这些中间文件调用的

服务通信自定义srv调用A(C++)

需求:

      编写服务通信,客户端提交两个整数至服务端,服务端求和并响应结果到客户端。

分析:

在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:

  1. 服务端
  2. 客户端
  3. 数据

流程:

  1. 编写服务端实现
  2. 编写客户端实现
  3. 编辑配置文件
  4. 编译并执行

1.服务端

#include "ros/ros.h"
#include "plumbing_sever_client/AddInts.h"
/*
    服务端实现:解析客户端提交的数据,并运算再产生响应
          1、包含头文件
          2、初始化ROS节点
          3、创建节点句柄
          4、创建一个服务对象
          5、处理请求并产生响应
          6、spin()
*/

bool doNums(plumbing_sever_client::AddInts::Request &request,plumbing_sever_client::AddInts::Response &response)
{
     //1、处理请求
     int num1 = request.num1;
     int num2 = request.num2;
     ROS_INFO("收到的请求数据:num1 = %d, num2 = %d",num1, num2);
     //2、组织响应
     int sum = num1 + num2;
     response.sum = sum;
     ROS_INFO("求和结果:sum = %d",sum);

     return true;
}

int main(int argc,char *argv[])
{
      setlocale(LC_ALL,"");
      //2、初始化ROS节点
      ros::init(argc,argv,"heiShui"); //节点名称需要保持唯一
      //3、创建节点句柄
      ros::NodeHandle nh;
      //4、创建一个服务对象
      ros::ServiceServer server = nh.advertiseService("addInts",doNums);
      ROS_INFO("服务器端启动");
      //5、处理请求并产生响应
      //6、spin()
      ros::spin();

      return 0;
}

2.客户端

#include "ros/ros.h"
#include "plumbing_sever_client/AddInts.h"
/*
      客户端:提交两个整数,并处理响应的结果
          1、包含头文件
          2、初始化ROS节点
          3、创建节点句柄
          4、创建一个客户端对象
          5、提交请求并处理响应
    
       实现参数的动态提交:
            1、格式:rosrun  xxxxx  xxxxx  12  34
            2、节点执行时,需要获取命令中的参数,并组织进  request

       问题:
              如果先启动客户端,那么会请求异常
       需求:
              如果先启动客户端,不要直接抛出异常,而是挂起,等服务器启动后,再正常请求
       解决:
               在ROS中内置了相关函数,这些函数可以让客户端启动后挂起,等待服务器启动
               client.waitForExistence();
               ros::service::waitForService("服务话题");
 
*/

int main(int argc, char *argv[])
{
      setlocale(LC_ALL,"");
      //优化实现,获取命令中参数
      if(argc != 3)
      {
       ROS_INFO("提交的参数个数不对");
       return 1;
      }
      //2、初始化ROS节点
      ros::init(argc,argv,"dabao");
      //3、创建节点句柄
      ros::NodeHandle nh;
      //4、创建一个客户端对象
      ros::ServiceClient client = nh.serviceClient<plumbing_sever_client::AddInts>("addInts");
      //5、提交请求并处理响应
      plumbing_sever_client::AddInts ai;
      //5-1组织请求
      ai.request.num1 = atoi(argv[1]);
      ai.request.num2 = atoi(argv[2]);
      //5-2处理响应
      //调用判断服务器状态的函数
      //函数1
      //client.waitForExistence();
      ros::service::waitForService("addInts");
      bool flag = client.call(ai);
      if(flag)
      {
       ROS_INFO("响应成功!");
       //获取结果
       ROS_INFO("响应结果 = %d",ai.response.sum);
      } 
      else
     {
       ROS_INFO("处理失败. . . .");
     }
      return 0;
}

其中argc是表示传入main函数的参数个数,argv表示传入main函数的参数序列或指针,其中argv[0]指向输入的程序路径及名称,以后为参数。所以实际参数个数是argc-1.atoi()函数是字符串转整型功能 

3.配置 CMakeLists.txt

add_executable(demo01_server src/demo01_server.cpp)
add_executable(demo02_client src/demo02_client.cpp)


## Add cmake target dependencies of the executable
## same as for the library above
add_dependencies(demo01_server ${PROJECT_NAME}_gencpp)
add_dependencies(demo02_client ${PROJECT_NAME}_gencpp)


target_link_libraries(demo01_server
  ${catkin_LIBRARIES}
)
target_link_libraries(demo02_client
  ${catkin_LIBRARIES}
)

4.执行

流程:

  • 需要先启动服务:rosrun plumbing_sever_client demo01_server
  • 然后再调用客户端 rosrun plumbing_sever_client demo02_client

注意:

如果先启动客户端,那么会导致运行失败

优化:

在客户端发送请求前添加:client.waitForExistence();

或:ros::service::waitForService("AddInts");

这是一个阻塞式函数,只有服务启动成功后才会继续执行

此处可以使用 launch 文件优化,但是需要注意 args 传参特点

5.效果图

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值