ROS通信机制(一)

ROS通信机制

1.1 理论模型

话题通信是ROS中基于发布-订阅模式的一种异步通信机制,其实现模型涉及三个核心角色:

  • ROS Master(管理者) :作为节点注册与匹配的中心协调者,负责维护话题的注册信息,并协助发布者与订阅者建立连接。
  • Talker(发布者) :周期性发布特定话题消息的节点。
  • Listener(订阅者) :订阅并接收特定话题消息的节点。

在该模型中,ROS Master 负责存储 Talker 和 Listener 注册的话题信息,并根据话题名称进行匹配,帮助双方建立通信链路。一旦连接成功建立,Talker 发布的消息将直接发送给对应的 Listener。

话题通信的完整建立流程包括以下几个步骤:

  1. Talker 注册

    Talker 启动后,通过 RPC 协议向 ROS Master 注册自身信息,包括其发布的话题名称。ROS Master 将此信息记录在注册表中。

  2. Listener 注册

    Listener 启动后,同样通过 RPC 协议向 ROS Master 注册其需订阅的话题名称。ROS Master 更新注册表,记录该订阅请求。

  3. ROS Master 进行信息匹配

    ROS Master 根据注册表中的话题名称,匹配相应的 Talker 和 Listener,并通过 RPC 将 Talker 的地址信息发送给 Listener。

  4. Listener 发送连接请求

    Listener 依据收到的 Talker 地址信息,通过 RPC 向其发起连接请求,内容包括订阅的话题名称、消息类型及通信协议(如 TCP 或 UDP)。

  5. Talker 确认连接

    Talker 接收到请求后,通过 RPC 回复 Listener,确认连接并发送自身的 TCP 地址信息。

  6. 建立 TCP 连接

    Listener 根据 Talker 返回的地址信息,使用 TCP 协议与其建立网络连接。

  7. 消息传输

    连接建立后,Talker 开始通过 TCP 协议向 Listener 持续发布消息。

1.2 话题通信基本操作A(C++)

在虚拟机中新建一个文件夹demotest_ws作为工作空间,输入code .指令用vscode打开

右键src文件夹创建工程包

输入包名plumbing_pub_sub,回车,再输入roscpp rospy std_msga这三个依赖,回车

定义msg文件

功能包下新建 msg 目录,添加文件 Person.msg

go 复制代码
string name
uint16 age
float64 height
编辑配置文件

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

xml 复制代码
  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>
  <!-- 
  exce_depend 以前对应的是 run_depend 现在非法
  -->

CMakeLists.txt编辑 msg 相关配置

scss 复制代码
find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
# 需要加入 message_generation,必须有 std_msgs
## 配置 msg 源文件
add_message_files(
  FILES
  Person.msg
)
# 生成消息时依赖于 std_msgs
generate_messages(
  DEPENDENCIES
  std_msgs
)
#执行时依赖
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES demo02_talker_listener
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

在该工程包的src文件夹下创建两个cpp文件

一个是发布方,另一个是订阅方

vscode配置

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

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
}
发布方
c 复制代码
/*
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         1.发送方
         2.接收方
         3.数据(此处为普通文本)

         PS: 二者需要设置相同的话题


    消息发布方:
        循环发布信息:HelloWorld 后缀数字编号

    实现流程:
        1.包含头文件 
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 ROS 句柄
        4.实例化 发布者 对象
        5.组织被发布的数据,并编写逻辑发布数据

*/
// 1.包含头文件 
#include "ros/ros.h"
#include "std_msgs/String.h" //普通文本类型的消息
#include <sstream>

int main(int argc, char  *argv[])
{   
    //设置编码
    setlocale(LC_ALL,"");

    //2.初始化 ROS 节点:命名(唯一)
    // 参数1和参数2 后期为节点传值会使用
    // 参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
    ros::init(argc,argv,"talker");
    //3.实例化 ROS 句柄
    ros::NodeHandle nh;//该类封装了 ROS 中的一些常用功能

    //4.实例化 发布者 对象
    //泛型: 发布的消息类型
    //参数1: 要发布到的话题
    //参数2: 队列中最大保存的消息数,超出此阀值时,先进的先销毁(时间早的先销毁)
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);

    //5.组织被发布的数据,并编写逻辑发布数据
    //数据(动态组织)
    std_msgs::String msg;
    // msg.data = "你好啊!!!";
    std::string msg_front = "Hello 你好!"; //消息前缀
    int count = 0; //消息计数器

    //逻辑(一秒10次)
    ros::Rate r(1);

    //节点不死
    while (ros::ok())
    {
        //使用 stringstream 拼接字符串与编号
        std::stringstream ss;
        ss << msg_front << count;
        msg.data = ss.str();
        //发布消息
        pub.publish(msg);
        //加入调试,打印发送的消息
        ROS_INFO("发送的消息:%s",msg.data.c_str());

        //根据前面制定的发送贫频率自动休眠 休眠时间 = 1/频率;
        r.sleep();
        count++;//循环结束前,让 count 自增
        //暂无应用
        ros::spinOnce();
    }


    return 0;
}

订阅方

arduino 复制代码
/*
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         1.发送方
         2.接收方
         3.数据(此处为普通文本)


    消息订阅方:
        订阅话题并打印接收到的消息

    实现流程:
        1.包含头文件 
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 ROS 句柄
        4.实例化 订阅者 对象
        5.处理订阅的消息(回调函数)
        6.设置循环调用回调函数

*/
// 1.包含头文件 
#include "ros/ros.h"
#include "std_msgs/String.h"

void doMsg(const std_msgs::String::ConstPtr& msg_p){
    ROS_INFO("我听见:%s",msg_p->data.c_str());
    // ROS_INFO("我听见:%s",(*msg_p).data.c_str());
}
int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    //2.初始化 ROS 节点:命名(唯一)
    ros::init(argc,argv,"listener");
    //3.实例化 ROS 句柄
    ros::NodeHandle nh;

    //4.实例化 订阅者 对象
    ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,doMsg);
    //5.处理订阅的消息(回调函数)

    //     6.设置循环调用回调函数
    ros::spin();//循环读取接收的数据,并调用回调函数处理

    return 0;
}

接下来配置CMakeLists.txt

scss 复制代码
add_executable(person_talker src/person_talker.cpp)
add_executable(person_listener src/person_listener.cpp)



add_dependencies(person_talker ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(person_listener ${PROJECT_NAME}_generate_messages_cpp)


target_link_libraries(person_talker
  ${catkin_LIBRARIES}
)
target_link_libraries(person_listener
  ${catkin_LIBRARIES}
)

ctrl+shift+B编译,打开三个终端

终端一:roscore

终端二:source ./devel/setup.bash

rosrun plumbing_pub_sub demo03_pub_person

终端三:source ./devel/setup.bash

rosrun plumbing_pub_sub demo04_sub_person

1.3 服务通信

服务通信是ROS中基于请求-响应模式的同步通信机制,其实现模型相对于话题通信更为简单,主要涉及三个核心角色:

  • ROS Master(管理者) :作为服务注册与匹配的中心协调者,负责维护服务的注册信息。
  • Server(服务端) :提供特定服务的节点,接收请求并返回响应。
  • Client(客户端) :发起服务请求并等待响应结果的节点。

在该模型中,ROS Master 负责存储 Server 和 Client 注册的服务信息,并根据服务名称进行匹配,帮助双方建立通信链路。连接建立后,Client 向 Server 发送请求信息,Server 处理请求后返回相应的响应信息。

服务通信的完整建立流程包括以下步骤:

  1. Server 注册
    Server 启动后,通过 RPC 协议向 ROS Master 注册自身信息,包括其提供的服务名称。ROS Master 将此信息记录在注册表中。
  2. Client 注册
    Client 启动后,同样通过 RPC 协议向 ROS Master 注册其需要请求的服务名称。ROS Master 更新注册表,记录该客户端请求信息。
  3. ROS Master 进行信息匹配
    ROS Master 根据注册表中的服务名称,匹配相应的 Server 和 Client,并通过 RPC 将 Server 的 TCP 地址信息发送给 Client。
  4. Client 发送请求
    Client 根据接收到的 Server 地址信息,使用 TCP 协议与 Server 建立网络连接,并发送请求数据。
  5. Server 发送响应
    Server 接收并解析请求数据,执行相应的服务处理逻辑,最后将响应结果通过 TCP 连接返回给 Client。

1.4 服务通信自定义srv

在src目录下新建工程包plumbing_client_server,添加依赖roscpp rospy std_msgs

定义srv文件

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

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

yaml 复制代码
# 客户端请求时发送的两个数字
int32 num1
int32 num2
---
# 服务器响应发送的数据
int32 sum
编辑配置文件

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

xml 复制代码
  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>
  <!-- 
  exce_depend 以前对应的是 run_depend 现在非法
  -->

CMakeLists.txt编辑 srv 相关配置

scss 复制代码
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
)
vscode配置

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

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
}

服务端

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

    服务器实现:
        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;
}

客户端

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

    服务器实现:
        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;
}
配置 CMakeLists.txt
scss 复制代码
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}
)

ctrl+shift+B编译,打开三个终端

终端一:roscore

终端二:source ./devel/setup.bash

rosrun plumbing_client_server demo01_server

终端三:source ./devel/setup.bash

rosrun plumbing_client_server demo02_client

相关推荐
岁月宁静2 小时前
# Node.js+Vue3.5 实战:豆包快速 / 深度思考模型的流式调用方案
vue.js·人工智能·node.js
NiceAIGC2 小时前
万亿参数!阿里 Qwen3-Max 大模型正式发布!
人工智能
别惹CC2 小时前
Spring AI 进阶之路03:集成RAG构建高效知识库
java·人工智能·spring
AgeClub2 小时前
一周快讯 | 银发文娱旅游一周新鲜事
大数据·人工智能·twitter
AI人工智能+2 小时前
AI攻克阿拉伯文OCR:深度学习如何破译千年文字密码
人工智能·深度学习·ocr·阿拉伯文识别
当代新讯2 小时前
链农科技亮相龙岗万达广场:“京北助力·舞动岭南”文艺展演
人工智能·科技
狗头大军之江苏分军2 小时前
什么?前端干不了AI? MCP开发对接AI长期记忆功能!
人工智能
辞--忧3 小时前
PyTorch 数据处理与可视化全攻略
人工智能·pytorch
想不明白的过度思考者3 小时前
阿里Qwen-Image-Edit-2509重磅升级:多图编辑能力让AI图像创作迈入“自由组合”时代
人工智能