1 ros::spin()
代码示例
Person.msg
string name uint8 age uint8 sex uint8 unknown = 0 uint8 male = 1 uint8 female = 2
person_publisher.cpp
#include <ros/ros.h> #include "learning_topic/Person.h" int main(int argc, char **argv) { // ROS节点初始化 ros::init(argc, argv, "person_publisher"); // 创建节点句柄 ros::NodeHandle n; // 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10 ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info", 10); // 设置循环的频率 ros::Rate loop_rate(1); int count = 0; while (ros::ok()) { // 初始化learning_topic::Person类型的消息 learning_topic::Person person_msg; person_msg.name = "Tom"; person_msg.age = 18; person_msg.sex = learning_topic::Person::male; // 发布消息 person_info_pub.publish(person_msg); ROS_INFO("Publish Person Info: name:%s age:%d sex:%d", person_msg.name.c_str(), person_msg.age, person_msg.sex); // 按照循环频率延时 loop_rate.sleep(); } return 0; }
person_subscriber.cpp
#include <ros/ros.h> #include "learning_topic/Person.h" // 接收到订阅的消息后,会进入消息回调函数 void personInfoCallback(const learning_topic::Person::ConstPtr& msg) { // 将接收到的消息打印出来 ROS_INFO("Subcribe Person Info: name:%s age:%d sex:%d", msg->name.c_str(), msg->age, msg->sex); } int main(int argc, char **argv) { // 初始化ROS节点 ros::init(argc, argv, "person_subscriber"); // 创建节点句柄 ros::NodeHandle n; // 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback); // 循环等待回调函数 ros::spin(); return 0; }
程序执行流程分析:
- 消息发布器在一个while里面一直循环发送“person_msg”到话题(topic)/person_info上。
- 消息订阅器一旦知道/person_info上面有person_msg,就会将这person_msg作为参数传入callback函数中,但是此时还没有执行callback函数,而是把callback函数放到了一个回调函数队列中。
- 所以当发布器不断发送person_msg到/person_info上面时,就会有相应的callback函数进入队列中,它们函数名一样,只是实参不一样。
那什么时候处理回调函数队列中的回调函数了?这就是 ros::spin() 需要做的工作了。
- 对于spin函数,一旦进入spin函数,它就不会返回了,也不继续往后执行了,相当于它在自己的函数里面死循环了(直到ctrl+c 或者程序终止的时候才退出)。
- 主要的工作,就是不断的检查回调函数队列里面是否有callback函数存在,如果有的话,它就会马上去执行callback函数。如果没有的话,它就会阻塞,不会占用CPU。
2 ros::spinOnce()
消息publish跟上面一样,这里用ros::spinOnce改写subscriber程序
person_subscriber.cpp
#include <ros/ros.h> #include "learning_topic/Person.h" // 接收到订阅的消息后,会进入消息回调函数 void personInfoCallback(const learning_topic::Person::ConstPtr& msg) { // 将接收到的消息打印出来 ROS_INFO("Subcribe Person Info: name:%s age:%d sex:%d", msg->name.c_str(), msg->age, msg->sex); } int main(int argc, char **argv) { // 初始化ROS节点 ros::init(argc, argv, "person_subscriber"); // 创建节点句柄 ros::NodeHandle n; // 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback); #if 0 // 循环等待回调函数 ros::spin(); #endif ros::Rate loop_rate(5); while (ros::ok()) { ros::spinOnce(); loop_rate.sleep(); //配合执行频率,sleep一段时间,然后进入下一个循环。 } return 0; }
回调函数的队列的创建和上面的分析是一样的,这里讲一讲ros::spinOnce() 的处理流程:
- 当spinOnce函数被调用时,spinOnce就会调用回调函数队列中第一个callback函数,此时callback函数被执行。
- spinOnce函数执行一次后,接着执行下面的语句。不像spin函数,进入到自己的内部循环,不往下执行。
- 等到下次spinOnce函数又被调用时,回调函数队列中第二个callback函数就会被调用,以此类推。
那我们如何控制执行的速度了,也就是执行的频率。从上面的实例中,我们看到了:
ros::Rate loop_rate(5); 设置执行频率
loop_rate.sleep(); 配合执行频率,sleep一段时间,然后进入下一个循环。
通过这样的设置,我们就可以设计自己的监听频率了,而不用完全被动的接收topic了。
3 区别
上面其实已经分析到了一个主要区别
- ros::spin()被动的接收topic,或者说纯粹的接收topic.
- ros::spinOnce()可以根据自己的需求设置接收频率。更加主动灵活。
ros::Rate loop_rate(10); while(ros::ok()) { // can add some function ros::spinOnce(); loop_rate.sleep(); }
如果退化成下面这样的化,其实和ros::spin()是一样的。 ```c while(ros::ok()) { ros::spinOnce(); }
从上面的对比中,其实可以看出,ros::spin()和ros::spinOnce() 还有一个重要的区别就是:
ros::spinOnce() 可以配合其它函数,一起放在while循环中处理。也就是说,当程序中除了响应回调函数还有其他重复性工作的时候,那就在循环中做那些工作,然后调用ros::spinOnce()。