"你不用对每个过客负责,
也不用对每个路人说教。"
2019-05-06
ROS中订阅(Subscribe)最新消息以及对消息队列的浅谈
机器人应用中难免会遇到运算起来很费时间的操作,比如图像的特征提取、点云的匹配等等。有时候,不可避免地,我们需要在ROS的Subscriber的Callback回调函数中进行这些费时的操作。Subscriber所订阅的消息的发布频率可能是很高的,而这些操作的运算速度肯定达不到消息发布的速度。所以,如果我们要是没有取舍的对于每个消息都调用一次回调函数,那么势必会导致计算越来越不实时,很有可能当下在处理的还是几十秒以前的数据。所以,我们希望每次回调函数都处理当前时刻最新的一个消息,这就是我们的目标。
要达到这个目标有三点,第一点是要设置Publisher的queue_size等于1;第二点是要设置Subscriber的queue_size(消息队列大小)等于1;第三点非常重要,要设置Subscriber的buff_size(缓冲区大小)足够大,大于一个消息的大小。像这样:
# ROS Python
pcdpub = rospy.Publisher("lidardata", PointCloud, queue_size=1)
rospy.Subscriber("lidardata", PointCloud, self.pcd_resolve_callback,queue_size=1,buff_size=52428800)
解释:
Subscriber和Publisher的消息队列起什么作用,队列的大小有什么影响?
简单描述一下,Publisher的消息队列是为了缓存发布节点发布的消息,一旦队列中消息的数量超过了queue_size,那么最先进入队列的(最老的)消息被舍弃。Subscriber的消息队列是为了缓存节点接收到的信息,一旦自己处理的速度过慢,接收到的消息数量超过了queue_size,那么最先进入队列的(最老的)消息会被舍弃。所以,我们想只处理最新的消息,实际上只需要把两个queue_size都设置成1,那么系统不会缓存数据,自然处理的就是最新的消息。
Subscriber有消息队列缓存消息了,为什么Publisher还要有消息队列?
在我看来,Publisher的消息队列是一定要有的,因为ROS中发布节点往外发送消息是基于Topic发送,而不是直接向Subscriber订阅者发送,所以必须要有一个消息队列来存放发布的消息,以供订阅者来获取。而且这个消息队列的好处是在网络差、带宽小、延时高的时候,保证数据不容易丢失。
既然Publisher有消息队列了,为什么Subscriber还要有消息队列?
这个问题比较难一点。我的理解是,由于ROS毕竟是分布式系统,Publisher和Subscriber不一定在同一台主机上,因此消息需要通过网络来交换。但是网络的性能时好时坏,如果Subscriber没有消息队列,那么每次运行Callback函数前都要先通过网络取回消息,然后才能处理。当网络很差时,就会让系统堵塞。而有消息队列的话,Subscriber就可以一边处理队列中的消息,一边通过网络缓存新的消息,而不用每次处理消息前都要临时去读一个回来。这样就增加了系统的可靠性。
为什么要设置缓冲区的大小?
这个缓冲区的大小是指消息队列使用的缓冲区物理内存空间大小。如果这个空间小于一个消息所需要的空间,比如消息是一副图片或者一帧点云,数据量超过了缓冲区的大小。这个时候为了保证通信不发生错误,就会触发网络通信的保护机制,TCP的Buffer会为你缓存消息。这种机制就会导致每一帧消息都被完整的缓存下来,没有消息被丢弃,感觉上就像queue_size被设置成了无穷大。详细说明请参考:
Ros subscriber not up to date
rospy subscriber queue_size acting strange when buff_size is too small
消息队列的运行机制
这里只能说是我的理解了,因为没有看过源代码。
首先,发布节点把消息发布,消息进入Publisher的消息队列,同时通知订阅了该话题消息的Subscriber来取消息。
其次,Subscriber来Publisher的消息队列里取消息,但取走的也是最老的消息,因为毕竟这是先入先出的队列。这也是为什么Publisher的消息队列的大小也要设置为1。
最后,被取走的消息存放入了Subscriber的消息队列中,等待被Callback执行。如果Callback执行很慢,消息越堆越多,最老的消息会逐渐被顶替。
当然,这里究竟是Subscriber来取消息,还是Publisher直接把消息推给Subscriber,我只是猜测,反正这里交换的消息肯定不是最新的消息,而是队列里最老的消息。