转载说明: 感谢简书原作者play_robot的分享!
著作权归原作者所有,如有侵权,请联系我删除,谢谢!
原文地址: https://www.jianshu.com/p/af9adf450dad
文章目录
- 1. MessageManager 工作原理
- 2. MessageManager 源码分析
- 3. spinOnce()源码分析
- 4. spin()源码分析
1. MessageManager 工作原理
MessageManager通过它的通信连接接收simple message。而后基于收到的message类型调用相应的回调函数,回调函数则执行相应的操作,以及根据需要作出消息应答。
MessageManager有两种工作模式: spin()
和spinOnce()
。
spin的执行是阻塞式的,而spinOnce是执行一次单独的操作。 因此,在spinOnce模式下,程序可以同时干其它事情,但是要确保执行spinOnce的频率足够高,这样不至于丢失通信数据。
2. MessageManager 源码分析
namespace industrial
{
namespace message_manager
{
class MessageManager
{
public:
MessageManager();
~MessageManager();
bool init(industrial::smpl_msg_connection::SmplMsgConnection* connection);
bool init(industrial::smpl_msg_connection::SmplMsgConnection* connection,
industrial::comms_fault_handler::CommsFaultHandler* fault_handler);
void spinOnce();
void spin();
bool add(industrial::message_handler::MessageHandler* handler, bool allow_replace = false);
unsigned int getNumHandlers()
{
return this->num_handlers_;
}
unsigned int getMaxNumHandlers()
{
return this->MAX_NUM_HANDLERS;
}
industrial::comms_fault_handler::CommsFaultHandler* getCommsFaultHandler()
{
return this->comms_hndlr_;
}
void setCommsFaultHandler(industrial::comms_fault_handler::CommsFaultHandler* handler)
{
this->comms_hndlr_ = handler;
}
private:
static const unsigned int MAX_NUM_HANDLERS = 64;
industrial::message_handler::MessageHandler* handlers_[MAX_NUM_HANDLERS];
industrial::smpl_msg_connection::SmplMsgConnection* connection_;
industrial::ping_handler::PingHandler ping_hndlr_;
industrial::simple_comms_fault_handler::SimpleCommsFaultHandler def_comms_hndlr_;
industrial::comms_fault_handler::CommsFaultHandler* comms_hndlr_;
unsigned int num_handlers_;
industrial::message_handler::MessageHandler* getHandler(int msg_type);
int getHandlerIdx(int msg_type);
industrial::simple_comms_fault_handler::SimpleCommsFaultHandler& getDefaultCommsFaultHandler()
{
return this->def_comms_hndlr_;
}
industrial::ping_handler::PingHandler& getPingHandler()
{
return this->ping_hndlr_;
}
;
void setConnection(industrial::smpl_msg_connection::SmplMsgConnection* connection)
{
this->connection_ = connection;
}
;
industrial::smpl_msg_connection::SmplMsgConnection* getConnection()
{
return this->connection_;
}
;
void setNumHandlers(unsigned int num_handlers)
{
this->num_handlers_ = num_handlers;
}
;
};
}
}
先来看一下它的私有成员:
类型 | 变量符号 | 含义 |
---|
int | MAX_NUM_HANDLERS | 消息处理器的最大数目 |
int | num_handlers_ | 消息处理器的实际数目 |
MessageHandler* | handlers_[MAX_NUM_HANDLERS] | 存放消息处理器的指针数组 |
SmplMsgConnection* | connection_ | 通信使用的连接 |
PingHandler | ping_hndlr_ | ping消息处理器 |
SimpleCommsFaultHandler | def_comms_hndlr_ | 默认的通信错误处理器 |
CommsFaultHandler* | comms_hndlr_ | 用户指定的通信错误处理器 |
在使用MessageManager时,外部需要先初始化好connection,然后传入init以初始化MessageManager使用的连接和错误处理器,此外还将初始化ping_hndlr_对象,并调用add将ping_hndlr_存放到handlers_数组中,保存的目的就在于当遇到ping message时,MessageManager就会调用PingHandler来处理该消息。
参考关于ros消息发布器和订阅器的教程, 消息发布器在一个while循环内一直循环发送“hello world”到话题(topic)chatter上。消息订阅器一旦知道chatter上面有data,就会将这data作为参数传入callback函数中,但是此时还没有执行callback函数,而是把callback函数放到了一个回调函数队列中。所以当发布器不断发送data到chatter上面时,就会有相应的callback函数进入队列中,它们函数名一样,只是实参不一样。
3. spinOnce()源码分析
下面分析spinOnce代码:
void MessageManager::spinOnce()
{
SimpleMessage msg;
MessageHandler* handler = NULL;
if(!this->getConnection()->isConnected())
{
this->getCommsFaultHandler()->connectionFailCB();
}
if (this->getConnection()->receiveMsg(msg))
{
LOG_COMM("Message received");
handler = this->getHandler(msg.getMessageType());
if (NULL != handler)
{
LOG_DEBUG("Executing handler callback for message type: %d", handler->getMsgType());
handler->callback(msg);
}
else
{
if (CommTypes::SERVICE_REQUEST == msg.getCommType())
{
simple_message::SimpleMessage fail;
fail.init(msg.getMessageType(), CommTypes::SERVICE_REPLY, ReplyTypes::FAILURE);
this->getConnection()->sendMsg(fail);
LOG_WARN("Unhandled message type encounters, sending failure reply");
}
LOG_ERROR("Message callback for message type: %d, not executed", msg.getMessageType());
}
}
else
{
LOG_ERROR("Failed to receive incoming message");
this->getCommsFaultHandler()->sendFailCB();
}
}
- 进入spinOnce后,首先会检查是否处于连接状态,如果连接断开则触发通信错误处理器的连接失败回调函数connectionFailCB。
- 接着将尝试接收一条SimpleMessage消息,如果接收到消息,则根据消息类型寻找能处理该消息的处理器handler,找到后则触发处理的回调函数callback对接收到的消息进行处理。
- 当spinOnce函数被调用时,spinOnce就会调用回调函数队列中第一个callback函数,此时callback函数才被执行,然后等到下次spinOnce函数又被调用时,回调函数队列中第二个callback函数就会被调用,以此类推。
- 所以,这会有一个问题。因为回调函数队列的长度是有限的,如果发布器发送数据的速度太快,spinOnce函数调用的频率太少,就会导致队列溢出,一些callback函数就会被挤掉,导致没被执行到。
4. spin()源码分析
对于spin方法,它是基于spinOnce实现的,进入该方法后,程序将进入内部的死循环,持续调用spinOnce:
void MessageManager::spin()
{
LOG_INFO("Entering message manager spin loop");
#ifdef ROS
while (ros::ok())
#else
while (true)
#endif
{
this->spinOnce();
if (!this->getConnection()->isConnected())
mySleep(5);
}
}
参考roswiki: Writing a Simple Publisher and Subscriber (C++),摘抄了一段关于spin()以及spinOnce()的描述:
ros::spin() enters a loop, calling message callbacks as fast as possible. Don’t worry though, if there’s nothing for it to do it won’t use much CPU.
ros::spin() will exit once ros::ok() returns false, which means ros::shutdown() has been called, either by the default Ctrl-C handler, the master telling us to shutdown, or it being called manually.
简述下来就是,当无事可做时,ros::spin()
只占据CPU很少的资源。只要回调函数队列里面有callback函数在,它就会马上去执行callback函数。如果没有的话,它就会阻塞,不会占用CPU。
当ros::ok()
返回false
时,ros::spin()
结束并退出,这就意味着ros::shutdown()
被调用,或者用户在终端执行了ctrl + c
命令,master节点告诉我们要shutdown。
MessageManager就分析到这里,后面再举例分析在更上层的代码中是如何使用它的。
注: 可以参考以下网站,对比学习
- industrial::message_manager::MessageManager
- docs.ros.org - simple_message
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)