ROS-ROS通信机制-服务通信

文章目录

  • 一、服务通信基本知识
  • 二、自定义srv
  • 三、C++实现
  • 四、Python实现

一、服务通信基本知识

服务通信也是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.服务端和客户端都可以存在多个。

理解图:
在这里插入图片描述

二、自定义srv

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

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

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

1.定义srv文件
服务通信中,数据分成两部分,请求与响应,在 srv 文件中请求和响应使用—分割,具体实现如下:
功能包下新建 srv 目录,添加 xxx.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
add_service_files(
  FILES
  AddInts.srv
)
generate_messages(
  DEPENDENCIES
  std_msgs
)

注意: 官网没有在 catkin_package 中配置 message_runtime,经测试配置也可以
3.编译
编译后的中间文件查看:
C++ 需要调用的中间文件(…/工作空间/devel/include/包名/xxx.h)
在这里插入图片描述
Python 需要调用的中间文件(…/工作空间/devel/lib/python3/dist-packages/包名/srv)
在这里插入图片描述
后续调用相关 srv 时,是从这些中间文件调用的

三、C++实现

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

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

  • 服务端
  • 客户端
  • 数据

流程:

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

0.vscode配置
需要像之前自定义 msg 实现一样配置c_cpp_properies.json 文件,如果以前已经配置且没有变更工作空间,可以忽略,如果需要配置,配置方式与之前相同:

{
    "configurations": [
        {
            "browse": {
                "databaseFilename": "",
                "limitSymbolsToIncludedHeaders": true
            },
            "includePath": [
                "/opt/ros/noetic/include/**",
                "/usr/include/**",
                "/xxx/yyy工作空间/devel/include/**" //配置 head 文件的路径 
            ],
            "name": "ROS",
            "intelliSenseMode": "gcc-x64",
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "c++17"
        }
    ],
    "version": 4
}

1.服务端

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

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

*/
#include "ros/ros.h"
#include "demo03_server_client/AddInts.h"

// bool 返回值由于标志是否处理成功
bool doReq(demo03_server_client::AddInts::Request& req,
          demo03_server_client::AddInts::Response& resp){
    int num1 = req.num1;
    int num2 = req.num2;

    ROS_INFO("服务器接收到的请求数据为:num1 = %d, num2 = %d",num1, num2);

    //逻辑处理
    if (num1 < 0 || num2 < 0)
    {
        ROS_ERROR("提交的数据异常:数据不可以为负数");
        return false;
    }

    //如果没有异常,那么相加并将结果赋值给 resp
    resp.sum = num1 + num2;
    return true;


}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ROS 节点
    ros::init(argc,argv,"AddInts_Server");
    // 3.创建 ROS 句柄
    ros::NodeHandle nh;
    // 4.创建 服务 对象
    ros::ServiceServer server = nh.advertiseService("AddInts",doReq);
    ROS_INFO("服务已经启动....");
    //     5.回调函数处理请求并产生响应
    //     6.由于请求有多个,需要调用 ros::spin()
    ros::spin();
    return 0;
}

2.客户端

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

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

*/
// 1.包含头文件
#include "ros/ros.h"
#include "demo03_server_client/AddInts.h"

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;
    }


    // 2.初始化 ROS 节点
    ros::init(argc,argv,"AddInts_Client");
    // 3.创建 ROS 句柄
    ros::NodeHandle nh;
    // 4.创建 客户端 对象
    ros::ServiceClient client = nh.serviceClient<demo03_server_client::AddInts>("AddInts");
    //等待服务启动成功
    //方式1
    ros::service::waitForService("AddInts");
    //方式2
    // client.waitForExistence();
    // 5.组织请求数据
    demo03_server_client::AddInts ai;
    ai.request.num1 = atoi(argv[1]);
    ai.request.num2 = atoi(argv[2]);
    // 6.发送请求,返回 bool 值,标记是否成功
    bool flag = client.call(ai);
    // 7.处理响应
    if (flag)
    {
        ROS_INFO("请求正常处理,响应结果:%d",ai.response.sum);
    }
    else
    {
        ROS_ERROR("请求处理失败....");
        return 1;
    }

    return 0;
}

3.配置 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}
)

4.执行
流程:

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

结果:

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

注意:

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

优化:

  • 在客户端发送请求前添加:client.waitForExistence();
    在这里插入图片描述
    在这里插入图片描述

  • 或:ros::service::waitForService(“AddInts”);
    在这里插入图片描述
    在这里插入图片描述

这是一个阻塞式函数,只有服务启动成功后才会继续执行
此处可以使用 launch 文件优化,但是需要注意 args 传参特点


1.不知道为啥报错
在这里插入图片描述
但由于这个错误会影响C++程序编写,只能把该相对路径改为绝对路径
在这里插入图片描述
发现问题了!!!!!,include path 不知道什么时候被我多打了一个s。
2.在使用rosservice测试的时候未能自动补齐两个数。
解决:先source一下,然后在输入addInts后空一格,然后tab键同时按两次即可补齐
在这里插入图片描述
3.main函数中argc代表传入的参数的总数量,在本例中,第一个参数指程序名,第二个参数是12,第三个参数是34,所以argc是3。argv中保存的为参数具体值。通过atoi将字符串类型的参数转换为整型。
在这里插入图片描述

在这里插入图片描述

4.Alt+方向键:可快速移动整行代码

四、Python实现

0.vscode配置
需要像之前自定义 msg 实现一样配置settings.json 文件,如果以前已经配置且没有变更工作空间,可以忽略,如果需要配置,配置方式与之前相同:

{
    "python.autoComplete.extraPaths": [
        "/opt/ros/noetic/lib/python3/dist-packages",
    ]
}

1.服务端

#! /usr/bin/env python
"""
    需求: 
        编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
        客户端再解析

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

"""
# 1.导包
import rospy
from demo03_server_client.srv import AddInts,AddIntsRequest,AddIntsResponse
# 回调函数的参数是请求对象,返回值是响应对象
def doReq(req):
    # 解析提交的数据
    sum = req.num1 + req.num2
    rospy.loginfo("提交的数据:num1 = %d, num2 = %d, sum = %d",req.num1, req.num2, sum)

    # 创建响应对象,赋值并返回
    # resp = AddIntsResponse()
    # resp.sum = sum
    resp = AddIntsResponse(sum)
    return resp


if __name__ == "__main__":
    # 2.初始化 ROS 节点
    rospy.init_node("addints_server_p")
    # 3.创建服务对象
    server = rospy.Service("AddInts",AddInts,doReq)
    # 4.回调函数处理请求并产生响应
    # 5.spin 函数
    rospy.spin()

2.客户端

#! /usr/bin/env python

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

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

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


"""
#1.导包
import rospy
from demo03_server_client.srv import *
import sys

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)

3.设置权限
终端下进入 scripts 执行:chmod +x *.py
4.配置 CMakeLists.txt

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

5.执行
流程:

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

结果:

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

1.在对发布方测试时,num1:40 冒号后面的空格一定不能删,不然会被认为是str类型
2.sys显示未定义,应在头文件import sys
在这里插入图片描述
3.优化
动态参数传入:
在这里插入图片描述
客户端先启动不会报异常,会挂起等待服务端启动
在这里插入图片描述

参考:
[1]Autolabor-ROS机器人入门课程《ROS理论与实践》季基础教程
[2]【Autolabor初级教程】ROS机器人入门
[3]胡春旭.ROS机器人开发实践[M].机械工业出版社,2018.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

通过断点调试解决node 运行js程序直接退出(没有任何报错提示)的问题

现象&#xff1a; node运行程序直接退出&#xff0c;但是从echo $?的返回值可以知道&#xff1a; 一定出现了错误&#xff0c;但是没有显示出来 解决办法&#xff1a; 1. 使用node --inspect-brk 启动程序 然后在浏览器访问chrome://inspect 然后点击inspect 进行单步调试 …

软件设计之桥接模式

实现茶水间&#xff1a;茶可以分红茶和绿茶&#xff0c;每种茶又可以分大杯和中杯&#xff0c;现在你是服务员需要计算茶水的价格。 package Bridge;public class BlackTea implements TeaKind{private float redTeaPrice 2.0f;Overridepublic float price() {return redTeaPr…

“快慢指针”思想在物理或者逻辑循环中的应用

1 基础概念 1.1 什么是物理循环和逻辑循环&#xff1f; 物理循环是指物理索引访问顺序上相邻&#xff0c;逻辑上也相邻&#xff0c;比如循环链表&#xff0c;逻辑循环则指物理的索引上不一定相邻 1.2 快慢指针本质上可以解决逻辑循环问题&#xff0c;而物理循环也属于逻辑循…

【java】toString() 导致的 StackOverflowError 异常

这是怎么导致的呢&#xff1f; A 类属性包含着 B 类的引用 B 类属性包含着 A 类的引用 代码大概就是这样的&#xff1a; Data public class User{public String name;public Dog dog; }Data public class Dog{public String name;public User user; }User user new User()…

面试前自测题,接口自动化测试过程中怎么处理接口依赖?

面试的时候经常会被问到在接口自动化测试过程中怎么处理接口依赖&#xff1f; 首先我们要搞清楚什么是接口依赖。 01、什么是接口依赖 接口依赖指的是&#xff0c;在接口测试的过程中一个接口的测试经常需要依赖另一个或多个接口成功请求后的返回数据。 那怎么处理呢&#…

机器学习---使用 EM 算法来进行高斯混合模型的聚类

1. 指定k个高斯分布參数 导包 import math import copy import numpy as np import matplotlib.pyplot as pltisdebug False 全局变量 isdebug可以用来控制是否打印调试信息。当 isdebug 为 True 时&#xff0c;代码中的一些调试信 息将被打印出来&#xff0c;方便进行调试…

Kubernetes(k8s)访问不了Pod服务

在k8s集群部署java web应用的服务时&#xff0c;浏览器访问不了pod服务或linux终端curl http://192.168.138.112:30000即curl http://ip地址:端口号失败&#xff0c;如下图&#xff1a; 在网上找了很久的答案&#xff0c;最后还是没解决&#xff0c;后来突然想起来一直是在k8…

7个简单技巧,让你从容应对压力面试!

01-什么是压力面试&#xff1f; 压力面试是指有意制造紧张&#xff0c;以了解求职者将如何面对工作压力的一种面试形式。 事实上&#xff0c;压力面试不是单独存在的一类面试&#xff0c;往往是穿插在面试过程中。 面试人通过提出不礼貌、冒犯的问题&#xff0c;或者用怀疑、…

JavaWeb(五)

一、JDBC概述 JDBC 就是使用Java语言操作关系型数据库的一套API 全称是Java DataBase Connectivity 表示Java数据库连接。 JDBC的本质是官方&#xff08;sun公司&#xff09;定义的一套操作所有关系型数据库的规则&#xff0c;即接口,各个数据库厂商去实现这套接口&#xff0…

11、信息打点——红队工具篇FofaQuakeSuize水泽Arl灯塔

网络空间测绘引擎 Fofa Quake shodan Zoomeye 主要搜关联资产、特征资产、资产信息&#xff08;在测绘引擎上直接搜IP&#xff0c;它会显示所有与该域名有关的信息。&#xff09; fofa和Quake测绘引擎集成化工具&#xff1a;Finger 自动化信息收集项目 ARL灯塔 Suize水泽 …

编程语言分类

如果要将编程语言分成两大类&#xff0c;可以考虑以下分类方式&#xff1a; 编译型语言&#xff08;Compiled Languages&#xff09;&#xff1a;这类语言在运行之前需要通过编译器将源代码转换为机器码或类似形式的可执行代码。编译型语言的特点包括&#xff1a; 需要显式的编…

二极管:TVS瞬态抑制二极管

一、什么是TVS二极管 TVS&#xff08;Transient Voltage Suppressors&#xff09;&#xff0c;即瞬态电压抑制器&#xff0c;又称雪崩击穿二极管。 TVS二极管的符号如下图所示 什么是雪崩击穿 雪崩击穿是有必要了解一下的&#xff0c;不然后面还有齐纳击穿&#xff0c;搞不…

GoWin FPGA--- startup2

clock Click Tools\IP Core Generator\rPLL, and open the configure file 原语 for Clock 双击选项&#xff0c;生产对应的代码&#xff0c;Copy到制定的地点。 右侧有对应的说明文件

【优选算法系列】【专题一双指针】第三节.611. 有效三角形的个数和LCR 179. 查找总价格为目标值的两个商品

文章目录 前言一、有效三角形的个数 1.1 题目描述 1.2 题目解析 1.2.1 算法原理 1.2.2 代码编写 1.2.3 题目总结二、查找总价格为目标值的两个商品 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 …

FastAPI与BaseModel

from typing import Optionalfrom fastapi import FastAPI from pydantic import BaseModel #当一个模型属性具有默认值时&#xff0c;它不是必需的。否则它是一个必需属性。将默认值设为 None 可使其成为可选属性 app FastAPI() class Item(BaseModel):name:str #没有初始值都…

动态类型语言与静态类型语言的对比与比较

编程语言可以根据类型系统和类型检查时机分为动态编程语言和静态编程语言两大类&#xff0c;它们在运行时的代码检查方式、变量类型的使用方式等方面有很大的区别。这一块你知道吗&#xff1f; 本文将为您详细讲解两种编程语言的优缺点&#xff0c;以及它们的应用场景。 动态编…

基于SpringBoot的校园互助网站

简介 本系统分为三个角色&#xff0c;分别是普通用户和管理员、以及超级管理员&#xff0c;主要的功能模块有注册、登录、物品代购、快递代取、话题管理、任务管理、反馈管理、投诉管理、订单管理等功能模块。 项目 数据库 首页 登录 新增反馈 发布话题 发布任务 接单 我要投诉…

Windows下使用AndroidStudio及CMake编译Android可执行程序或静态库动态库

Windows下使用AndroidStudio及CMake编译Android可执行程序或静态库动态库 文章目录 Windows下使用AndroidStudio及CMake编译Android可执行程序或静态库动态库一、前言二、编译环境三、示例C/CPP程序1、总体工程结构2、示例代码3、CMakeLists.txt&#xff08;重要&#xff09;4、…

深入理解URL、URI和URN在Web开发中的重要性

引言&#xff1a; 在Web开发中&#xff0c;我们经常听到URL、URI和URN这几个术语&#xff0c;它们是构建和理解互联网资源的基础。虽然它们看起来相似&#xff0c;但实际上代表着不同的概念。本文将深入研究URL、URI和URN的定义、用途以及在Web开发中的重要性。 一、什么是URI&…

Verilog基础:$time、$stime和$realtime系统函数的使用

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html $time、 $stime和$realtime这三个系统函数提供了返回当前仿真时间方法。注意&#xff0c;这里的仿真时间的最小分辨能力是由仿真时间精度决定的&#xff0c;简单来说&#xff0c;可以理解为…