ROS学习笔记6——ROS通信机制2(服务通信)

服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即: 一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。用于偶然的、对时时性有要求、有一定逻辑处理需求的数据传输场景。

一、服务通信理论模型图

ROS学习笔记6——ROS通信机制2(服务通信)_第1张图片

二、角色

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

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

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

三、流程

0、Server注册

advertise Service("bar",foo:1234)

Server 启动后,会通过RPC在 ROS Master 中注册自身信息,其中包含提供的服务的名称。ROS Master 会将节点的注册信息加入到注册表中。

1、Client注册

lookupService("bar")

Client 启动后,也会通过RPC在 ROS Master 中注册自身信息,包含需要请求的服务的名称。ROS Master 会将节点的注册信息加入到注册表中。

2、ROS Master实现信息匹配

{foo:3456}

ROS Master 会根据注册表中的信息匹配Server和 Client,并通过 RPC 向 Client 发送 Server 的TCP 地址信息。

3、Client发送请求

request data (args)

Client 根据步骤2 响应的信息,使用 TCP 与 Server 建立网络连接,并发送请求数据。

4.Server发送响应

reply data

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

四、注意

        1、保证顺序,客户端发起请求时,服务器需要已经启动

        2、客户端和服务器都可以存在多个

五、服务通信自定义srv

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

5.1 定义srv文件

在功能包下创建srv目录,添加文件AddInts.srv.

ROS学习笔记6——ROS通信机制2(服务通信)_第2张图片ROS学习笔记6——ROS通信机制2(服务通信)_第3张图片

5.2 编辑配置文件

5.2.1 package.xml

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

5.2.2 CMakeLists.txt编辑 srv 相关配置

a、find_package

编译自定义的功能包时需要依赖find_package中的功能包

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
# 需要加入 message_generation,必须有 std_msgs

b、add_service_files

在“srv”文件夹中生成服务

add_service_files(
  FILES
  AddInts.srv
)

c、 generate_messages

生成消息是依赖于std_mags。因为自定义msg也是由std_msgs 中一些原生的数据类型组成的

generate_messages(
  DEPENDENCIES
  std_msgs
)

d、catkin_package

编译find_package中的依赖包时,依赖于catkin_package中的功能包

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES plumbing_server_client
 CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)
5.2.3 编译

快捷键ctrl+alt+B,配置方法之前分享过。

编译后的中间文件会放在工作空间的dev目录下,C++的中间文件在include中,python的在lib中,

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

六、自定义srv调用实例

6.1 vscode 配置

为了方便代码提示以及避免误抛异常,需要先配置 vscode,将前面生成的 head 文件路径配置进 c_cpp_properties.json 的 includePath属性:

6.2 服务端

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

//回调函数
bool doNums(plumbing_server_client::AddInts::Request &request,
            plumbing_server_client::AddInts::Response &response)
{
    //处理请求
    int num1 = request.num1;
    int num2 = request.num2;
    ROS_INFO("收到的请求数据:num1 = %d,num2 = %d",num1,num2);
    //组织响应
    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,"server");//保证节点名称唯一

    // 3、创建 ROS节点 句柄
    ros::NodeHandle nh;

    // 4、创建 服务 对象
    ros::ServiceServer server = nh.advertiseService("AddInts",doNums);

    ROS_INFO("服务器启动");
    // 5、回调函数 处理请求并产生响应

    // 6、由于请求有多个,需要调用 ros::spin()
    ros::spin();
    return 0;
}

6.3 客户端

#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"

/*
        客户端实现:提交两个整数,并处理响应的结果
        1、包含头文件
        2、初始化 ROS 节点
        3、创建 ROS节点 句柄
        4、创建 客户端 对象
        5、提交请求并处理响应

        实现参数的动态提交:
        1、格式:rosrun xxx xxx 1 2
        2、节点执行时,需要获取命令中的参数,并组织进request

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

int main(int argc, char *argv[])
{
    /* code */
    setlocale(LC_ALL,"");
    //获取命令中的参数
    if(argc != 3)
    {
        ROS_INFO("提交的参数个数不对");
        return 1;
    }
    
    ros::init(argc,argv,"client");;
    ros::NodeHandle nh;
    /*
        泛型< >: 提交的消息类型  srv
        参数1: 要提交的话题
    */
    ros::ServiceClient client = nh.serviceClient("AddInts");

    //提交请求并处理响应
    plumbing_server_client::AddInts ai;
    //组织请求
    ai.request.num1 = atoi(argv[1]);
    ai.request.num2 = atoi(argv[2]);
    //处理响应
    //调用判断服务器状态的函数
    //方法1
    client.waitForExistence();
    //方法2  参数:等待的服务
    //ros::service::waitForService("AddInts");
    bool flag = client.call(ai);
    if(flag)
    {
        ROS_INFO("正常响应了!");
        //获取响应结果
        ROS_INFO("响应结果 = %d",ai.response.sum);
    }
    else{
        ROS_INFO("处理失败了!");
    }

    return 0;
}

6.4 配置 CMakeLists.txt

除了常规的add_executable和target_link_libraries配置之外。还需要配置add_dependencies,这是为了编译srv文件之后,再编译包含srv文件的cpp文件,保证不会出现逻辑错误。

add_dependencies(server ${PROJECT_NAME}_gencpp)
add_dependencies(client ${PROJECT_NAME}_gencpp)

add_executable(server src/server.cpp)
add_executable(client src/client.cpp)

target_link_libraries(server ${catkin_LIBRARIES})
target_link_libraries(client ${catkin_LIBRARIES})

6.5 执行

  • 开启roscore

  • 需要先启动服务端:rosrun 包名 服务端

rosrun plumbing_server_client server
  • 然后再调用客户端 :rosrun 包名 客户端 参数1 参数2

rosrun plumbing_server_client client 1 2

6.6 结果

ROS学习笔记6——ROS通信机制2(服务通信)_第4张图片

6.7 注意

a、若先启动客户端则会报错

ROS学习笔记6——ROS通信机制2(服务通信)_第5张图片

b、解决办法:

在客户端发送请求前添加:

client.waitForExistence();
或:
ros::service::waitForService("AddInts");

可以让客户端启动后挂起,等待服务器启动 

ROS学习笔记6——ROS通信机制2(服务通信)_第6张图片

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

 

你可能感兴趣的:(学习,笔记,机器人,linux,人工智能)