前奏
本篇呢是对ROS的话题机制做个全面的总结,如果总结的地方有错误,在此特别希望走过的大佬发现问题并指出错误。
ROS的核心就是通信机制,它该种通信机制被叫做:分布式通信机制。ROS中所有的软件和工具都建立在这种分布式通信机制上,所以在开始真正像深入研究ROS之前必须理解该机制。ROS的这种具有分布式的机制有三种,分别是:话题通讯机制,服务通信机制以及参数管理器,本篇先来总结话题通信机制,因为它在ROS中使用非常频繁,而且其通信模型也很复杂,我们先来上图瞧瞧:
这张图已经涵盖了我们今天要总结的内容!在这张图我们可以看出,在ROS中有两个节点分别是Talker和Listener,他们启动顺序没有要求,他们想要建立联系,那么他们分别发布,订阅的话题必须一致!这就像,两个彼此互补认识而且并没有见面的陌生人想要联系的时候必须通过某种方式来进行交流,他们之间的交流前提是找到好一个很好的共同话题。
首先呢,我们先来了解一下什么是RPC,TCP/UDP,话题,消息,节点管理器?
答:
1.RPC:远程过程调用协议。RPC它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。具体参考https://baike.baidu.com/item/%E8%BF%9C%E7%A8%8B%E8%BF%87%E7%A8%8B%E8%B0%83%E7%94%A8%E5%8D%8F%E8%AE%AE/6893245?fr=aladdin
2.TCP/UDP:其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;而UDP则不为IP提供可靠性、流控或差错恢复功能。一般来说,TCP对应的是可靠性要求高的应用,而UDP对应的则是可靠性要求低、传输经济的应用。
具体参考:`https://baike.baidu.com/item/TCP/UDP%E5%8D%8F%E8%AE%AE/7719820`
3.消息:节点通过消息完成彼此的沟通。消息包含一个节点发送到其他节点的数据信息。ROS 中包含很多种标准类型的消息,同时你也可以基于标准消息开发自定义类型的消息。
4.话题:主题是由 ROS 网络对消息进行路由和消息管理的数据总线。每一条消息都要发布到相应的主题。当一个节点发送数据时,我们就说该节点正在向主题发布消息。节点可以通过订阅某个主题,接收来自其他节点的消息。一个节点可以订阅一个主题,而并不需要该节点同时发布该主题。这就保证了消息的发布者和订阅者之间相互解耦,完全无需知晓对方的存在。主题的名称必须是独一无二的,否则在同名主
题之间的消息路由就会发生错误。
ROS 通信机制的过程
1.Talker启动,通过1234这个端口使用RPC向节点管理器(ROS Master)注册发布者的信息,包含所要发布消息的话题名;随后ROS Master会将该节点的注册信息放入注册列表中储存起来,以等待接受此话题的接收者Listener。
2.Listener启动,通过使用TCP向节点管理器(ROS Master)注册接收者的信息,包括所需订阅的话题名。
3.ROS Master根据Listener所需的订阅话题在注册列表寻找与之匹配的话题,如果没有找到匹配的发布者,则等待发布者的加入,如果找到可以与之匹配的发布者信息,则可以通过RPC向Listener发送Talker的RPC地址信息。
4.Listener接收到Talker的RPC地址信息,就尝试通过RPC像Talker发送连接许可,以便于传输订阅的话题名,消息类型以及TCP/UDP。
5.Talker接受到Listener发来的连接请求后,继续通过RPC向Listener确认链接信息,其中就包括自己的TCP信息
6.Listener接收到确认的消息后,就使用TCP与Talker进行连接
7.等连接成功后,Talker才向Listener发送话题消息类型。
我们举个经典的hello world的例子,先贴代码,下一篇着重讲解代码!
talker.py
#!/usr/bin/env python
import rospy
from std_msgs.msg import String
def talker():
pub = rospy.Publisher('chatter', String, queue_size=100)
rospy.init_node('talker', anonymous=True)
rate = rospy.Rate(10)
while not rospy.is_shutdown():
hello_str = "this hello words is from python %s" % rospy.get_time()
rospy.loginfo(hello_str)
pub.publish(hello_str)
rate.sleep()
if __name__ == '__main__':
try:
talker()
except rospy.ROSInterruptException:
pass
listener.py:
#!/usr/bin/env python
import rospy
from std_msgs.msg import String
def callbake(data):
rospy.loginfo(rospy.get_caller_id() + 'i heard %s', data.data)
def listener():
rospy.init_node('listener', anonymous=True)
rospy.Subscriber('chatter', String, callbake)
rospy.spin()
if __name__ == '__main__':
listener()
C++代码:
talker.cpp
#include <sstream>
#include "ros/ros.h"
#include "std_msgs/String.h"
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "talker");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布名为chatter的topic,消息类型为std_msgs::String
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 10);
// 设置循环的频率
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok())
{
// 初始化std_msgs::String类型的消息
std_msgs::String msg;
std::stringstream ss;
ss << "this hello world is from C++" << count;
msg.data = ss.str();
// 发布消息
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);
// 循环等待回调函数
ros::spinOnce();
// 按照循环频率延时
loop_rate.sleep();
++count;
}
return 0;
}
lstener.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
// 接收到订阅的消息后,会进入消息回调函数
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "listener");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为chatter的topic,注册回调函数chatterCallback
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
// 循环等待回调函数
ros::spin();
return 0;
}