ROS服务(Service)通信:通信模型、Hello World与拓展

news2025/2/23 17:23:54

服务通讯是基于请求响应模式的,是一种应答机制。

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

一、服务通讯模型

服务是一种双向通讯方式,它通过请求和应答的方式传递消息,该模型涉及到三个角色:

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

Master 负责保管 ServerClient 的注册信息,并匹配服务名称相同的 ServerClient ,帮助 他们建立连接,连接建立后,Client 可以发送请求信息, Server 收到请求后返回响应信息。

在这里插入图片描述

服务模型通讯流程:

  • 0)advertise:服务端注册

    服务端(Server)向管理者(Master)注册信息,包括RPC地址和Service名字。Master会将服务端的注册信息加入到注册表中。

  • 1)客户端注册

    客户端(Client)向管理者(Master)注册信息,包括Service名字。Master会将客户端(Client)的注册信息加入到注册表中。

  • 2)Master匹配信息:牵线搭桥

    管理者(Master)通过查询注册表发现有匹配的服务端(Server)和客户端(Client),则通过RPC向客户端(Client)发送服务端(Server)的 TCP/UDP 地址信息。

  • 3)客户端发送请求信息

    客户端根据服务端的 TCP/UDP 地址与服务端建立网络连接,并发送请求信息。

  • 4)服务端响应请求

    服务端收到请求数据后,通过处理产生响应数据,通过 TCP/UDP 返回给客户端。

Note:

  1. 上述实现流程中,前三步使用 RPC 协议,最后两步使用 TCP/UDP 协议,默认TCP。
  2. 客户端请求时,必须保证服务端已经启动。
  3. 服务名相同的客户端可以有多个,服务端只能有1个。
  4. 与话题通信不同,服务通信过程中,ROS Master必须处于启动状态。

二、Service Hello World

万物始于Hello World,同样,使用Hello World介绍Service的简单使用。

使用Service传输数据时,需要注意以下几点:

  • Service名称
  • 消息格式(srv)
  • 服务端实现
  • 客户端实现

接下来实现一个简单的 Service 服务通信,客户端请求启动机器人,服务端启动机器人的各个模块,然后返回执行结果。

2.1 创建并初始化功能包

首先创建 service_hello_world 包,命令如下:

catkin_creat_pkg service_hello_world std_srvs roscpp rospy

创建后,文件结构如下:

在这里插入图片描述

2.2 确定Service名称及消息格式

Service名称:/hello_world_service

消息格式:std_srvs::SetBool

消息文件路径:/opt/ros/noetic/share/std_srvs/srv/SetBool.srv

消息文件内容:

bool data # e.g. for hardware enabling / disabling
---
bool success   # indicate successful run of triggered service
string message # informational, e.g. for error messages

2.3 实现服务端与客户端(C++版)

在创建的 service_hello_world 包路径下有一个 src 目录,在这里存储C++源码,我们创建 service_hello_world_server.cpp 以实现服务端,编辑内容如下:

#include <ros/ros.h>
#include <std_srvs/SetBool.h>

bool dealRobotSwitch(std_srvs::SetBool::Request &req, std_srvs::SetBool::Response &resp)
{
    bool flag = req.data;
    ROS_INFO("服务器收到 [%s] 机器人的指令.", flag ? "启动" : "关闭");

    // 逻辑处理
    if (flag)
    {
        ROS_INFO("正在启动机器人各模块...");
        ros::Duration(2).sleep();
        // 使用时间模拟随机成功与失败
        if (ros::Time::now().toNSec() % 2 == 0)
        {
            resp.success = true;
            resp.message = "Hello World.";
            ROS_INFO("机器人各模块启动成功.\n");
        }
        else
        {
            resp.success = false;
            resp.message = "再睡一会";
            ROS_INFO("机器人各模块启动失败.\n");
        }
    }
    else
    {
        ROS_INFO("正在关闭机器人各模块...");
        ros::Duration(2).sleep();
        // 模拟成功与失败
        if (ros::Time::now().toNSec() % 2 == 0)
        {
            resp.success = true;
            resp.message = "Good Night.";
            ROS_INFO("机器人各模块关闭成功.\n");
        }
        else
        {
            resp.success = false;
            resp.message = "我还能卷";
            ROS_INFO("机器人各模块关闭失败.\n");
        }
    }

    return true;
}

int main(int argc, char **argv)
{
    setlocale(LC_ALL, "");
    ros::init(argc, argv, "service_hello_world_server");
    ros::NodeHandle nh;
    ros::ServiceServer server = nh.advertiseService("/robotSwitch", dealRobotSwitch);
    ROS_INFO("robotSwitch 服务已启动...");
    ros::spin();
    return 0;
}

创建 service_hello_world_client.cpp 以实现客户端,编辑内容如下:

#include <ros/ros.h>
#include <std_srvs/SetBool.h>

int main(int argc, char **argv)
{
    setlocale(LC_ALL, "");
    ros::init(argc, argv, "service_hello_world_client");
    ros::NodeHandle nh;
    ros::ServiceClient client = nh.serviceClient<std_srvs::SetBool>("/robotSwitch");

    std_srvs::SetBool srv;
    if (strcmp(argv[1], "on") == 0)
    {
        srv.request.data = true;
    }
    else if (strcmp(argv[1], "off") == 0)
    {
        srv.request.data = false;
    }
    else
    {
        ROS_WARN("仅支持on和off");

        return 1;
    }

    // 等待服务启动
    // ros::service::waitForService("/robotSwitch");
    // client.waitForExistence();
    if (client.call(srv))
    {
        if (srv.response.success)
        {
            ROS_INFO("操作成功, %s", srv.response.message.c_str());
        }
        else
        {
            ROS_ERROR("操作失败, %s", srv.response.message.c_str());
        }
    }
    else
    {
        ROS_ERROR("操作失败, 未知错误!");
    }

    return 0;
}

修改 CMakeLists.txt ,只需添加如下内容:

add_executable(${PROJECT_NAME}_client src/service_hello_world_client.cpp)
add_executable(${PROJECT_NAME}_server src/service_hello_world_server.cpp)

target_link_libraries(${PROJECT_NAME}_client
  ${catkin_LIBRARIES}
)

target_link_libraries(${PROJECT_NAME}_server
  ${catkin_LIBRARIES}
)

编译运行

进入工作空间执行 catkin_make 命令编译工程,编译成功后,使用如下命令依次启动服务端和客户端。

1. 启动ros master
roscore
2. 启动服务端
rosrun service_hello_world service_hello_world_server
3. 启动客户端
rosrun service_hello_world service_hello_world_client

结果如下:

在这里插入图片描述

目前为止,Service Hello World 已经成功了。

2.4 实现服务端与客户端(Python版)

在创建的 service_hello_world 包路径下 src 目录的同级,创建一个 scripts 目录,在这里存储脚本(如python脚本),我们创建 service_hello_world_server.py 以实现服务端,编辑内容如下:

import rospy
from std_srvs.srv import SetBool, SetBoolResponse


def dealRobotSwitch(req):
    flag = req.data
    rospy.loginfo("服务器收到 [%s] 机器人的指令.", "启动" if flag else "关闭")
    if flag:
        rospy.loginfo("正在启动机器人各模块...")
        if rospy.Time.now().to_nsec() % 2 == 0:
            rospy.loginfo("机器人各模块启动成功.\n")
            return SetBoolResponse(True, "Hello World.")
        else:
            rospy.logerr("机器人各模块启动失败.\n")
            return SetBoolResponse(False, "再睡一会")
    else:
        rospy.loginfo("正在关闭机器人各模块...")
        if rospy.Time.now().to_nsec() % 2 == 0:
            rospy.loginfo("机器人各模块关闭成功.\n")
            return SetBoolResponse(True, "Good Night.")
        else:
            rospy.logerr("机器人各模块关闭失败.\n")
            return SetBoolResponse(False, "我还能卷")


if __name__ == "__main__":
    rospy.init_node("service_hello_world_server")
    server = rospy.Service("/robotSwitch", SetBool, dealRobotSwitch)
    rospy.loginfo("robotSwitch 服务已启动...")
    rospy.spin()

创建 service_hello_world_client.py 以实现客户端,编辑内容如下:

import sys
import rospy
from std_srvs.srv import SetBool, SetBoolRequest


if __name__ == "__main__":
    rospy.init_node("service_hello_world_client")

    if len(sys.argv) != 2:
        rospy.logerr("参数个数有误")
        sys.exit(1)

    flag = False
    if sys.argv[1] == "on":
        flag = True
    elif sys.argv[1] == "off":
        pass
    else:
        rospy.logwarn("仅支持on和off")
        sys.exit(1)

    rospy.loginfo("客户端请求 [%s] 机器人.", "启动" if flag else "关闭")
    client = rospy.ServiceProxy("/robotSwitch", SetBool)
    client.wait_for_service()
    req = SetBoolRequest()
    req.data = flag
    res = client.call(req)

    if res.success:
        rospy.loginfo("操作成功,%s", res.message)
    else:
        rospy.logerr("操作失败,%s", res.message)

修改 CMakeLists.txt ,只需添加如下内容:

catkin_install_python(PROGRAMS
  scripts/service_hello_world_server.py
  scripts/service_hello_world_client.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

编译运行

进入工作空间执行 catkin_make 命令编译工程,编译成功后,使用如下命令依次启动服务端和客户端。

1. 启动ros master(如果已启动,无需再启动)
roscore
2. 启动服务端
rosrun service_hello_world service_hello_world_server.py
3. 启动客户端
rosrun service_hello_world service_hello_world_client.py

结果如下:

在这里插入图片描述

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

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

相关文章

74基于matlab的PSO-ELM的多输入,单输出结果预测,输出训练集和测试机预测结果及误差。

基于matlab的PSO-ELM的多输入&#xff0c;单输出结果预测&#xff0c;输出训练集和测试机预测结果及误差&#xff0c;适应度值。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 74matlabPSO-ELM多输入单输出 (xiaohongshu.com)

Redis实战篇(1)

实战篇Redis 短信登录 这一块我们会使用redis共享session来实现 商户查询缓存 通过本章节&#xff0c;我们会理解缓存击穿&#xff0c;缓存穿透&#xff0c;缓存雪崩等问题&#xff0c;让小伙伴的对于这些概念的理解不仅仅是停留在概念上&#xff0c;更是能在代码中看到对应…

AMEYA360:罗姆旗下蓝碧石面向电动汽车开发出AVAS专用的语音合成LSI

罗姆集团旗下的蓝碧石科技株式会社(以下简称“蓝碧石科技”)面向电动汽车(xEV)开发出AVAS(车辆接近报警系统)专用的语音合成LSI“ML22120xx”(ML22120TB、ML22120GP)。 在推动实现碳中和(无碳)社会的进程中&#xff0c;混合动力汽车和纯电动汽车(EV)的数量不断增加。由于这些车…

Flutter笔记: 在Flutter应用中使用SQLite数据库

Flutter笔记 在Flutter应用中使用SQLite数据库&#xff08;基于sqflite&#xff09; 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/q…

人工智能引领环境保护的新浪潮:技术应用及其影响

在全球范围内&#xff0c;环境保护已经成为一个迫切的话题。随着人工智能技术的发展&#xff0c;它开始在环境保护领域扮演越来越重要的角色。AI不仅能够帮助更有效地监测环境变化&#xff0c;还能提出解决方案来应对环境问题。 污染监测与控制&#xff1a; AI系统可以分析来自…

【C语言】深入解开指针(三)

&#x1f308;write in front :&#x1f50d;个人主页 &#xff1a; 啊森要自信的主页 真正相信奇迹的家伙&#xff0c;本身和奇迹一样了不起啊&#xff01; 欢迎大家关注&#x1f50d;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;>希望看完我的文章对你有小小的帮助&#x…

企业云盘:企业文件数据存储与共享的全面解决方案

企业云盘为企业的文件数据的存储与管理提供了一种安全、高效又便捷的解决方案。在企业文件数据存储管理上&#xff0c;企业云盘有什么优势吗&#xff1f;本文将重点分析企业云盘的优点和好处&#xff01; 一、安全性 对于企业文件数据管理工具&#xff0c;安全是首位。与个人…

【源码系列】短剧系统开发国际版短剧系统软件平台介绍

系统介绍 短剧是一种快节奏、紧凑、有趣的戏剧形式&#xff0c;通过短时间的精彩表演&#xff0c;向观众传递故事的情感和思考。它以其独特的形式和魅力&#xff0c;吸引着观众的关注&#xff0c;成为了当代戏剧娱乐中不可或缺的一部分。短剧每一集都是一个小故事&#xff0c;…

【Java 进阶篇】深入浅出:JQuery 事件绑定的奇妙世界

在前端的世界里&#xff0c;事件是不可或缺的一部分。用户的点击、输入、滚动等行为都触发着各种事件&#xff0c;而如何在代码中捕捉并处理这些事件是每位前端开发者必须掌握的技能之一。本文将带你深入浅出&#xff0c;探索 JQuery 中的事件绑定&#xff0c;为你揭开这个奇妙…

IIC总线概述和通信时序代码详细图文解析

IIC总线 1 IIC总线概述 I2C总线两线制包括&#xff1a;串行数据SDA&#xff08;Serial Data&#xff09;、串行时钟SCL&#xff08;Serial Clock&#xff09;。总线必须由主机&#xff08;通常为微控制器&#xff09;控制&#xff0c;主机产生串行时钟&#xff08;SCL&#x…

【漏洞复现】用友移动管理系统文件上传

漏洞描述 用友移动系统管理旧版本uploadApk接口存在任意文件上传&#xff0c;攻击者可在无需登录的情况下上传恶意文件&#xff0c;执行任意命令 免责声明 技术文章仅供参考&#xff0c;任何个人和组织使用网络应当遵守宪法法律&#xff0c;遵守公共秩序&#xff0c;尊重社…

基于模拟退火算法优化概率神经网络PNN的分类预测 - 附代码

基于模拟退火算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于模拟退火算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于模拟退火优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

redis运维(十一) python操作redis

一 python操作redis ① 安装pyredis redis常见错误 说明&#xff1a;由于redis服务器是5.0.8的,为了避免出现问题,默认最高版本的即可 --> 适配 ② 操作流程 核心&#xff1a;获取redis数据库连接对象 ③ Python 字符串前面加u,r,b的含义 原因&#xff1a; 字符串在…

使用 SMI 指标增强股票分析:amCharts JS Crack

使用 SMI 指标增强股票分析 2023 年 11 月 16 日 amCharts 5&#xff1a;股票图表 v5.5.3 增加了对随机动量指数指标的支持&#xff0c;帮助用户做出更明智的交易决策。 amCharts 5&#xff1a;股票图表提供了用于显示基于时间的数据的分析工具&#xff0c;无论是金融、股票还是…

【MySQL】聚合函数:汇总、分组数据

文章目录 学习目标MAX()、MIN()、AVG()、SUM()、COUNT()COUNT(*) 得到所有记录条目DISTINCT去重练习1&#xff08;使用UNION &#xff0c; SUM&#xff0c; BETEEN AND&#xff09;GROUP BY子句练习2&#xff08;使用sum&#xff0c;group by&#xff0c; join on&#xff0c; …

NET8 ORM 使用AOT SqlSugar

.NET AOT8 基本上能够免强使用了, SqlSugar ORM也支持了CRUD 能在AOT下运行了 Nuget安装 SqlSugarCore 具体代码 StaticConfig.EnableAot true;//启用AOT 程序启动执行一次就好了//用SqlSugarClient每次都new,不要用单例模式 var db new SqlSugarClient(new ConnectionC…

【Nuxt】Nuxt3 动态导入图片 src

nuxt3 不再支持 require 动态导入资源&#xff0c;因此需要我们将图片放到 public 目录下&#xff0c;这样我们就可以动态导入了 比如下面 &#x1f447;&#xff1a; 感谢 Nuxt3遇见的坑&#xff08;四&#xff09;&#xff1a;图片动态渲染之后打包路径问题以及打包css样式…

MySQL5.7安装详细过程--window系统

1、Window安装MySQL5.7详细过程 1.1、下载MySQL5.7安装包 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 1.2、将文件解压到盘符中 你可以解压到你想解压的位置&#xff0c;放在C或其他盘符都可以。 我放在了C盘中&#xff1a;C:\Program Files\my…

视频一键转码:批量转换MP4视频的技巧

随着数字媒体设备的普及&#xff0c;视频文件在生活中扮演着越来越重要的角色。而在处理视频文件时&#xff0c;有时需要将其转换为不同的格式以适应不同的需求。其中&#xff0c;MP4格式因其通用性和高质量而备受青睐。本文详解云炫AI智剪如何一键转码的技巧&#xff0c;帮助批…

Java内存区域速览

文章目录 JVM的组成加载字节码流程 运行时数据区-总览1. 程序计数器2. 虚拟机栈栈帧栈的运行原理 3. 本地方法栈4. 堆内存(Java Heap虚拟机对堆 的划分1. 年轻代&#xff08;Young Generation&#xff09;&#xff1a;2. 老年代&#xff08;Old Generation&#xff09;&#xf…