ROS 的通信机制之话题订阅模型
文章目录
1.基础概念
- 话题(Topic):话题表示的是一个定义了类型的消息流。例如,摄像机产生的数据可能会被发送到一个名为“image”的话题上,其消息类型是Image。
- 发布与订阅:
- 发布(Publish):节点可以将数据发布到特定的话题上。在发布数据之前,节点需要先声明话题名和消息类型,并与roscore(ROS的核心)建立连接。roscore会共享其订阅者与发布者的列表,然后节点就可以将数据发布到该话题上了。
- 订阅(Subscribe):想要从话题上接收数据的节点,需要向roscore发出请求以订阅该话题。订阅后,该话题上所有的消息都会被转发到这个请求的节点上,从而建立了发布者与订阅者之间的直接连接。
- 消息类型:同一个话题上的所有消息必须是同一类型的。ROS提供了多种基础的消息类型,如std_msgs(标准的数据类型)、geometry_msgs(几何数据型)和sensor_msgs(传感器数据型)。但在实际应用中,很多时候还需要设计特定的消息类型。
2.通信流程
- 发布者创建并发布特定主题上的数据。
- 订阅者向roscore请求订阅该主题。
- roscore将主题上的数据转发给订阅者。
3.本质原理
话题订阅模型在ROS中的实现本质上依赖于底层的通信机制和网络编程。
3.1节点注册和发现
- ROS节点在启动时,会向ROS Master(roscore的一部分)注册自己,并声明它们将要发布或订阅的话题。
- ROS Master维护一个全局的话题和节点注册表,它知道哪些节点正在运行,以及这些节点正在发布或订阅哪些话题。
3.2话题匹配
- 当一个节点想要订阅某个话题时,它会向ROS Master发送一个请求,询问有哪些节点正在发布该话题。
- ROS Master将发布该话题的节点信息返回给订阅者节点。
- 订阅者节点随后直接与发布者节点建立连接,开始接收数据。
3.3通信协议
- ROS使用TCPROS或UDPROS等协议作为底层的通信协议。TCPROS提供了可靠的数据传输,而UDPROS则提供了更高效的通信,但牺牲了一定的可靠性。
- 发布者节点将消息序列化后,通过选定的通信协议发送给订阅者节点。
- 订阅者节点接收到消息后,将其反序列化,并转换成对应的ROS消息类型,以供本地处理。
- 话题订阅模型支持异步通信,即发布者和订阅者不需要同步运行。发布者可以在任何时候发布消息,而订阅者则在自己的节奏下接收和处理这些消息。
3.4 消息序列化与反序列化
ROS消息在传输之前需要进行序列化,即将消息对象转换为字节流,以便在网络中传输。接收端接收到字节流后,需要进行反序列化,即将字节流转换回原始的ROS消息对象
4. 简单通信举例
4.1 简单话题消息发布
示例代码CPP:
#include "ros/ros.h"
#include "std_msgs/String.h" //普通文本类型的消息
#include <sstream>
int main(int argc, char *argv[]){
//设置编码
setlocale(LC_ALL,"");
ros::init(argc,argv,"talker");
//3.实例化 ROS 句柄
ros::NodeHandle nh;//该类封装了 ROS 中的一些常用功能
//参数2: 队列中最大保存的消息数,超出此阀值时,先进的先销毁(时间早的先销毁)
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);
std_msgs::String msg;
//数据类型为 String
std::string msg_front = "Hello 你好!"; //消息前缀
int count = 10;
ros::Rate r(1); // 设置频率为1Hz ,1s 一次
while (ros::ok())
{
std::stringstream ss;
ss << msg_front << count;
msg.data = ss.str();
pub.publish(msg);
ros::spinOnce(); // 处理ROS回调
ROS_INFO("发送的消息:%s",msg.data.c_str()); //打印发送的消息
count++;
r.sleep(); // 根据设定的频率暂停,以确保不会执行得太快
}
return 0;
}
4.2简单话题消息订阅
示例代码CPP:
#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;
ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,doMsg);
// 6.设置循环调用回调函数
ros::spin();//循环读取接收的数据,并调用回调函数处理
}
4.3CmakeLists.txt编写
add_executable(publisher
src/publisher.cpp
)
add_executable(sub
src/sub.cpp
)
target_link_libraries(publisher
${catkin_LIBRARIES}
)
target_link_libraries(sub
${catkin_LIBRARIES}
)
如果编译通过,但是rosrun 提示找不到节点,请检查是否有catkin_package()
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
)
catkin_package()