【ROS】—— ROS通信机制——服务通信(三)

news2024/11/16 19:30:01

文章目录

  • 前言
  • 1. 服务通信理论模型
  • 2. 服务通信自定义srv
    • 2.1 定义srv文件
    • 2.2 编辑配置文件
    • 2.3 编译
  • 3. 服务通信自定义srv调用(C++)
    • 3.1 vscode配置
    • 3.2 服务端
    • 3.3 客户端
    • 3.4 配置 CMakeLists.txt
  • 4. 服务通信自定义srv调用(python)
    • 4.1 vscode配置
    • 4.2 服务端
    • 4.3 客户端
    • 4.4 配置 CMakeLists.txt

前言

📢本系列将依托赵虚左老师的ROS课程,写下自己的一些心得与笔记。
📢课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ
📢讲义链接:http://www.autolabor.com.cn/book/ROSTutorials/index.html
📢 文章可能存在疏漏的地方,恳请大家指出。

1. 服务通信理论模型

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

概念
以请求响应的方式实现不同节点之间数据交互的通信模式。

作用
用于偶然的、对时时性有要求、有一定逻辑处理需求的数据传输场景。


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

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

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


在这里插入图片描述

整个流程由以下步骤实现:
0.Server注册
Server 启动后,会通过RPC在 ROS Master 中注册自身信息,其中包含提供的服务的名称。ROS Master 会将节点的注册信息加入到注册表中。

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

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

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

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

注意:

1.客户端请求被处理时,需要保证服务器已经启动;

2.服务端和客户端都可以存在多个。

ps:话题通信是单向式的,listener是被动的接受数据;服务通信是应答式的,只有客户端发布相关请求,服务端才会进行相关应答,给出消息。

2. 服务通信自定义srv

srv = 请求 +相应

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

  1. 按照固定格式创建srv文件
  2. 编辑配置文件
  3. 编译生成中间文件

2.1 定义srv文件

服务通信中,数据分成两部分,请求与响应,在 srv 文件中请求和响应使用—分割,具体实现如下:
功能包下新建 srv 目录,添加 xxx.srv 文件,内容:

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

2.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

add_service_files(
  FILES
  AddInts.srv
)

generate_messages(
  DEPENDENCIES
  std_msgs
)

2.3 编译

编译后的中间文件查看:

C++ 需要调用的中间文件(…/工作空间/devel/include/包名/xxx.h)在这里插入图片描述

Python 需要调用的中间文件(…/工作空间/devel/lib/python3/dist-packages/包名/srv)在这里插入图片描述

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

流程:

  • 编写服务端实现;
  • 编写客户端实现;
  • 编辑配置文件;
  • 编译并执行。

3.1 vscode配置

与之前的配置方式一致(若之前的工作空间配置过则不用更改)。【ROS】—— ROS通信机制——话题通信(二)

3.2 服务端

#include "ros/ros.h"
#include "publisher/addints.h"
/*
    需求: 
        编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
        客户端再解析

    服务器实现:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 ROS 句柄
        4.创建 服务 对象
        5.回调函数处理请求并产生响应
        6.由于请求有多个,需要调用 ros::spin()

*/

//回调函数处理请求并产生响应
bool donums(publisher::addints::Request &request,
                             publisher::addints::Response &response)
{
    //1.处理请求
    int num1 = request.num1;
    int num2 = request.num2;
    ROS_INFO("收到的请求:num1 = %d, num2=%d",num1,num2);
    //逻辑处理
    if (num1 < 0 || num2 < 0)
    {
        ROS_ERROR("提交的数据异常:数据不可以为负数");
        return false;
    }
    //2.组织响应
    int sum = num1 +num2;
    response.sum = sum;
    ROS_INFO("求得的结果: sum = %d",sum);
    return true;
}
int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    ROS_INFO("服务端启动");
    //初始化 ROS 节点
    ros::init(argc,argv,"server");
    //创建 ROS 句柄
    ros::NodeHandle nh;
    //创建 服务 对象
    ros::ServiceServer server = nh.advertiseService("addints",donums);
    // 由于请求有多个,需要调用 ros::spin()
    ros::spin();
    return 0;
}

rosrun启动服务端后,可以通过rosservice进行测试。

 rosservice call /addints "num1: 2
num2: 4" 

结果
在这里插入图片描述

3.3 客户端

#include "ros/ros.h"
#include "publisher/addints.h"
/*
    需求: 
        编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
        客户端再解析

    服务器实现:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 ROS 句柄
        4.创建 客户端 对象
        5.请求服务,接收响应

*/

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    //初始化 ROS 节点
    ros::init(argc,argv,"client");
    //创建 ROS 句柄
    ros::NodeHandle nh;
    //创建客户端对象
    ros::ServiceClient client = nh.serviceClient<publisher::addints>("addints");
    //提交请求并处理响应
    publisher::addints add; 
    //组织请求
    add.request.num1  = 100;
    add.request.num2 = 200;
    //处理响应
    bool flag = client.call(add);
    // 7.处理响应
    if (flag)
    {
        ROS_INFO("请求正常处理,响应结果:%d",add.response.sum);
    }
    else
    {
        ROS_ERROR("请求处理失败....");
        return 1;
    }

    return 0;
}

在这里插入图片描述动态方式提交请求

#include "ros/ros.h"
#include "publisher/addints.h"
/*
    需求: 
        编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
        客户端再解析

    服务器实现:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 ROS 句柄
        4.创建 客户端 对象
        5.请求服务,接收响应

*/

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
            // 调用时动态传值,如果通过 launch 的 args 传参,需要传递的参数个数 +3
    if (argc != 3)
    // if (argc != 5)//launch 传参(0-文件路径 1传入的参数 2传入的参数 3节点名称 4日志路径)
    {
        ROS_ERROR("请提交两个整数");
        return 1;
    }
    //初始化 ROS 节点
    ros::init(argc,argv,"client");
    //创建 ROS 句柄
    ros::NodeHandle nh;
    //创建客户端对象
    ros::ServiceClient client = nh.serviceClient<publisher::addints>("addints");
    //等待请求
    ros::service::waitForService("addints");
    //提交请求并处理响应
    publisher::addints add; 
    //组织请求
    add.request.num1  = atoi(argv[1]);
    add.request.num2 = atoi(argv[2]);
    //处理响应
    bool flag = client.call(add);
    // 7.处理响应
    if (flag)
    {
        ROS_INFO("请求正常处理,响应结果:%d",add.response.sum);
    }
    else
    {
        ROS_ERROR("请求处理失败....");
        return 1;
    }

    return 0;
}

在这里插入图片描述注意:
看看这个代码if (argc != 3),为什么argc需要是3个参数呢?
原因:第一个参数argc[0]是程序的名称,第二个参数argc[1]、第三个参数argc[2]才是传入的参数。

3.4 配置 CMakeLists.txt

add_executable(AddInts_Server src/AddInts_Server.cpp)
add_executable(AddInts_Client src/AddInts_Client.cpp)

add_dependencies(AddInts_Server ${PROJECT_NAME}_gencpp)
add_dependencies(AddInts_Client ${PROJECT_NAME}_gencpp)

target_link_libraries(AddInts_Server
  ${catkin_LIBRARIES}
)
target_link_libraries(AddInts_Client
  ${catkin_LIBRARIES}
)

流程:
需要先启动服务:rosrun 包名 服务
然后再调用客户端 :rosrun 包名 客户端 参数1 参数2

结果:
会根据提交的数据响应相加后的结果。

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

优化:
在客户端发送请求前添加:client.waitForExistence();
或:ros::service::waitForService("AddInts");
这是一个阻塞式函数,只有服务启动成功后才会继续执行
此处可以使用 launch 文件优化,但是需要注意 args 传参特点

4. 服务通信自定义srv调用(python)

流程:

  1. 编写服务端实现;
  2. 编写客户端实现;
  3. 为python文件添加可执行权限;
  4. 编辑配置文件;
  5. 编译并执行。

4.1 vscode配置

与之前的配置方式一致(若之前的工作空间配置过则不用更改)。【ROS】—— ROS通信机制——话题通信(二)

4.2 服务端

#! /usr/bin/env python
#-- coding:UTF-8 --

import rospy
from publisher.srv import addints, addintsRequest, addintsResponse
"""
    需求: 
        编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
        客户端再解析

    服务器端实现:
        1.导包
        2.初始化 ROS 节点
        3.创建服务对象
        4.回调函数处理请求并产生响应
        5.spin 函数

"""
#参数:封装了请求数据
#返回值:响应数据
def donum(request):
    num1 = request.num1
    num2 = request.num2

    sum = num1 + num2
    response = addintsResponse()
    response.sum = sum
    rospy.loginfo("提交的数据:num1 = %d, num2 = %d, sum = %d",request.num1, request.num2, sum)
    return response

if __name__ == "__main__" :
    # 初始化 ROS 节点
    rospy.init_node("server")
    # 创建服务对象    
    server = rospy.Service("addints",addints,donum)
    rospy.loginfo("启动服务端")
    rospy.spin()

同样测试一下
在这里插入图片描述

4.3 客户端

#! /usr/bin/env python
#-- coding:UTF-8 --

import rospy
from publisher.srv import addints, addintsRequest, addintsResponse
import sys
"""
    需求: 
        编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
        客户端再解析

    客户端实现:
        1.导包
        2.初始化 ROS 节点
        3.创建请求对象
        4.发送请求
        5.接收并处理响应

    优化:
        加入数据的动态获取


"""
if __name__ == "__main__":

    #优化实现
    if len(sys.argv) != 3:
        rospy.logerr("请正确提交参数")
        sys.exit(1)


    # 2.初始化 ROS 节点
    rospy.init_node("addInts_Client_p")
    # 3.创建请求对象
    client = rospy.ServiceProxy("addints",addints)
    # 请求前,等待服务已经就绪
    # 方式1:
    # rospy.wait_for_service("addInts")
    # 方式2
    client.wait_for_service()
    # 4.发送请求,接收并处理响应
    # 方式1
    # resp = client(3,4)
    # 方式2
    # resp = client(AddIntsRequest(1,5))
    # 方式3
    req = addintsRequest()
    # req.num1 = 100
    # req.num2 = 200 

    #优化
    req.num1 = int(sys.argv[1])
    req.num2 = int(sys.argv[2]) 

    resp = client.call(req)
    rospy.loginfo("响应结果:%d",resp.sum)

之后记得给文件执行权限
在这里插入图片描述

4.4 配置 CMakeLists.txt

catkin_install_python(PROGRAMS
  scripts/AddInts_Server_p.py 
  scripts/AddInts_Client_p.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/134144.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

将Android进行到底之内容提供者(ContentProvider)

文章目录前言一、ContentProvider是什么&#xff1f;二、使用示例1.为应用创建内容提供者2.使用内容提供者2.1 内容URI2.2 Uri参数解析2.2 使用内容URI操作数据3.ContentProvider妙用4 内容URI对应的MIME类型5.ContentProvider重点注意6 演示demo源码总结前言 随着现在的应用越…

java通过JDBC连接mysql8.0数据库,并对数据库进行操作

目录 一、JDBC简介 二、添加依赖 三、JDBC操作数据库的步骤 四、JDBC操作数据库——增删改查 (一)新增数据 (二)删除数据 (三)修改数据 (四)查询数据 (五)多表连接查询 一、JDBC简介 Java数据库连接&#xff0c;&#xff08;Java Database Connectivity&#xff0c;简…

C进阶:字符串相关函数及其模拟实现

目录 &#x1f431;&#x1f638;一.strlen &#x1f54a;️1.功能 &#x1f43f;️2.模拟实现 &#x1f42c;&#x1f40b;二.strcpy &#x1f432;1.功能 &#x1f916;2.注意事项 &#x1f47b;3.模拟实现 &#x1f431;&#x1f42f;三.strcat &#x1f984;1.功能…

i.MX8MP平台开发分享(IOMUX篇)- Linux注册PAD

专栏目录&#xff1a;专栏目录传送门 平台内核i.MX8MP5.15.71文章目录1. pinfunc.h2.pinctrl-imx8mp.c3.PAD信息注册这一篇开始我们深入Linux中的pinctl框架。1. pinfunc.h pinfunc.h中定义了所有的引脚&#xff0c;命名方式是MX8MP_IOMUXC___&#xff0c;例如下面的MX8MP_IO…

【JDBC】----------ServletContext和过滤器

分享第二十四篇励志语句 幸福是什么&#xff1f;幸福不一定是腰缠万贯、高官显禄、呼风唤雨。平凡人自有平凡人的幸福&#xff0c;只要你懂得怎样生活&#xff0c;只要你不放弃对美好生活的追求&#xff0c;你就不会被幸福抛弃。 一&#xff1a;ServletContext&#xff08;重要…

js对象篇

面向对象 对象 如果对象的属性键名不符合JS标识符命名规范&#xff0c;则这个键名必须用引号包裹 访问属性有2种方法&#xff0c;有点语法和方括号填写法&#xff0c;特别地&#xff0c;如果属性名不符合JS命名规范或者使用变量存储属性名&#xff0c;则必须使用方括号访问 属…

【王道操作系统】2.3.6 进程同步与互斥经典问题(生产者消费者问题、吸烟者问题、读者写者问题、哲学家进餐问题)

进程同步与互斥经典问题(生产者消费者问题、吸烟者问题、读者写者问题、哲学家进餐问题) 文章目录进程同步与互斥经典问题(生产者消费者问题、吸烟者问题、读者写者问题、哲学家进餐问题)1.生产者-消费者问题1.1 问题描述1.2 问题分析1.3 如何实现1.4 实现互斥的P操作一定在实现…

深化全面To C战略魏牌发布与用户共创大六座SUV蓝山

对魏牌而言&#xff0c;与用户共创不是吸引眼球的营销噱头&#xff0c;而是“直面用户需求&#xff0c;真实倾听用户意见”的有效途径。 2022年12月30日&#xff0c;第二十届广州国际汽车展览会&#xff08;以下简称“广州车展”&#xff09;正式启幕。魏牌以“品位蓝山 有咖有…

神经网络必备基础知识:卷积、池化、全连接(通道数问题、kernel与filter的概念)

文章目录卷积操作实际操作filter与kernel1x1的卷积层可视化的例子池化全连接卷积操作 这个不难理解。我们知道图像在计算机中是由一个个的像素组成的&#xff0c;可以用矩阵表示。 假设一个5x5的输入图像&#xff0c;我们定义一个3x3的矩阵&#xff08;其中的数值是随机生成的…

excel图表美化:设置标记样式让拆线图精巧有趣

折线图作为我们平时数据视图化非常常规的表现方式&#xff0c;想必大家已经司空见惯了。折线图很简单&#xff0c;每个人都会做&#xff0c;但是不同的人做出来的折线图却千差万别。大多数人的折线图都是直接插入默认折线图样式生成的&#xff0c;这样的折线图先不说有没有用心…

五、IDEA中创建Web项目

文章目录5.1 创建Web项目5.1.1 创建项目5.1.2 编写Servlet类5.2 手动部署项目5.3 自动部署项目5.3.1 IDEA集成Tomcat5.3.2 IDEA部署JavaWeb项目5.4 war包部署5.4.1 导出war包5.1 创建Web项目 5.1.1 创建项目 1、打开IDEA&#xff0c;单击“New Project”或者通过File–>ne…

Perl语言入门

一、简介 Perl语言是拉里.沃尔&#xff08;Larry Wall&#xff09;在1987年开发的一种编程语言&#xff0c;借鉴了C、sed、awk、shell脚本语言以及其他语言的特性&#xff0c;专门用于文本处理。 它可以在各种平台上运行&#xff0c;例如Windows&#xff0c;Mac OS和各种UNIX…

bean生命周期

1.Aware和InitializingBean接口 Aware 接口用于注入一些与容器相关信息&#xff0c;例如 BeanNameAware: 注入bean的名字BeanFactorAware&#xff1a; 注入beanFactor容器ApplicationContextAware&#xff1a; 注入applicationContext容器EmbeddedValueResolverAware: ${} 代码…

爬虫进阶一(基础一)

文章目录简介cookie爬取雪球热帖代理模拟登陆防盗链异步爬虫协程asyncioM3U8HLS爬取seleniumbilibili无头浏览器规避检测MySQLMongoDBRedis简介 这个系列分四部分 基础进阶Scrapy 框架逆向分析实战运用 先补充一些爬虫需要的基础知识和技能预热&#xff0c;爬取个简历模板网站…

浅谈Git

Git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理,也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。 版本控制 什么是版本控制&#xff1f; 版本控制是一种在开发的过程中用于管理我们对文…

如何评价唐卫国公李靖的战功、军事才能、政治才能?

link 一鞭直渡清河洛Research and no development已关注470 人赞同了该回答个人以为&#xff0c;在军事上&#xff0c;李靖是当之无愧的唐朝第一名将&#xff0c;他用兵如神&#xff0c;精于谋略&#xff0c;无论是在实际的军事指挥上&#xff0c;还是军事理论上&#xff0c;他…

Vue3 中computed计算属性的使用

目录前言&#xff1a;什么是计算属性选项式中的计算属性组合式中的计算属性计算属性和方法的区别&#xff1a;计算属性/计算方法注意事项&#xff1a;总结前言&#xff1a; 目标&#xff1a;现在vue3的使用越来越普遍了&#xff0c;vue3这方面的学习我们要赶上&#xff0c;今天…

银行家算法 源码+实验报告(用了自取)

XIAN TECHNOLOGICAL UNIVERSITY 课程设计报告 实验课程名称 操作系统—银行家算法 专 业&#xff1a;计算机科学与技术 班 级&#xff1a; 姓 名&#xff1a; 学 号&#xff1a; 实验学时&#xff1a; …

小程序03/ uni-app自定义全局组件 、 uni-app项目引入 Uview-ui 框架教程方法 和 Uview框架介绍

一. uni-app自定义全局组件 1.创建组件 注意: 在components文件夹下创建组件 、文件夹名要与文件名保持一致 2.使用组件 注意: 在pages文件夹下任意vue文件、 template标签内使用该组件即可 二.uni-app项目引入Uview-ui框架教程方法 和 Uview框架介绍 (1) Uview介绍: Uvi…

【自学Java】Java运算符

Java运算符 Java运算符 Java 程序是由许多语句组成的&#xff0c;而语句的基本操作单位是表达式与运算符。运算符就是数学中的运算符号&#xff0c;如 、-、*、 / 等等。 Java 中提供了许多的运算符&#xff0c;这些运算除了可以处理一般的数学运算外&#xff0c;还可以处理…