ROS通信机制
1.1 理论模型
话题通信是ROS中基于发布-订阅模式的一种异步通信机制,其实现模型涉及三个核心角色:
- ROS Master(管理者) :作为节点注册与匹配的中心协调者,负责维护话题的注册信息,并协助发布者与订阅者建立连接。
- Talker(发布者) :周期性发布特定话题消息的节点。
- Listener(订阅者) :订阅并接收特定话题消息的节点。
在该模型中,ROS Master 负责存储 Talker 和 Listener 注册的话题信息,并根据话题名称进行匹配,帮助双方建立通信链路。一旦连接成功建立,Talker 发布的消息将直接发送给对应的 Listener。

话题通信的完整建立流程包括以下几个步骤:
-
Talker 注册
Talker 启动后,通过 RPC 协议向 ROS Master 注册自身信息,包括其发布的话题名称。ROS Master 将此信息记录在注册表中。
-
Listener 注册
Listener 启动后,同样通过 RPC 协议向 ROS Master 注册其需订阅的话题名称。ROS Master 更新注册表,记录该订阅请求。
-
ROS Master 进行信息匹配
ROS Master 根据注册表中的话题名称,匹配相应的 Talker 和 Listener,并通过 RPC 将 Talker 的地址信息发送给 Listener。
-
Listener 发送连接请求
Listener 依据收到的 Talker 地址信息,通过 RPC 向其发起连接请求,内容包括订阅的话题名称、消息类型及通信协议(如 TCP 或 UDP)。
-
Talker 确认连接
Talker 接收到请求后,通过 RPC 回复 Listener,确认连接并发送自身的 TCP 地址信息。
-
建立 TCP 连接
Listener 根据 Talker 返回的地址信息,使用 TCP 协议与其建立网络连接。
-
消息传输
连接建立后,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 处理请求后返回相应的响应信息。

服务通信的完整建立流程包括以下步骤:
- Server 注册
Server 启动后,通过 RPC 协议向 ROS Master 注册自身信息,包括其提供的服务名称。ROS Master 将此信息记录在注册表中。 - Client 注册
Client 启动后,同样通过 RPC 协议向 ROS Master 注册其需要请求的服务名称。ROS Master 更新注册表,记录该客户端请求信息。 - ROS Master 进行信息匹配
ROS Master 根据注册表中的服务名称,匹配相应的 Server 和 Client,并通过 RPC 将 Server 的 TCP 地址信息发送给 Client。 - Client 发送请求
Client 根据接收到的 Server 地址信息,使用 TCP 协议与 Server 建立网络连接,并发送请求数据。 - 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
