2.7 消息处理器接口、实现及其注册
消息分发框架应该支持用户定义一个或多个消息处理器类,因为用户的业务消息可能需要分类处理,比如把逻辑相关的消息进行分组,每组消息定义一个消息处理器。对于框架来说,需要一个消息处理器的公共接口和一个消息处理器列表,因为这种情况最适合使用C++的多态机制了。
最容易想到的方法是在MessageProcessor接口中声明一个pure virtual函数Process():
class MessageProcessor { public: virtual BOOL Process(HalfStructuredMessageSmartPtr psmsg) = 0; }; |
各个具体的消息处理器从MessageProcessor派生并实现Process()函数,然后实例化并加入消息分发器的处理器列表中。当消息队列中有消息到来时,分发线程逐个取出消息并依次调用消息处理器列表中的每个消息处理器的Process()函数,直到某个处理器的Process()函数返回TRUE,分发线程即完成当前消息的分发。而对于任何消息处理器,如果它接受并处理当前消息,则令Process()返回TRUE,否则返回FALSE以表示不是自己关心的消息。
但是,这种方法的一个致命缺陷是:框架层完全不负责消息的识别,而是把这个工作抛给了消息处理器的实现者即框架的用户或应用层业务逻辑的开发人员。下面我们将着重解决这个问题。
2.7.1 引进消息映射表
我们已经在前面“业务消息格式框架”一节中要求每个业务消息必须有一个唯一的消息ID,所以完全可以设计一个消息映射表机制,并由框架层来负责消息的精确匹配和投递。显然,消息映射表定义为各个消息处理器的static成员是合理的,并且消息分发器能通过调用接口MessageProcessor的一个virtual函数得到它。最简单的消息映射表是由若干个二元组<消息ID,消息处理函数指针>组成的数组:
struct _MSGMAP_TABLE_ITEM_ { #ifdef _USE_XML_MESSAGE_FORMAT_ const char * m_msgId; #elif defined(_USE_BINARY_MESSAGE_FORMAT_) _enum_MessageID m_msgId; #endif
PMFN_MessageProcessor m_pmfn; // 消息处理函数指针 }; |
PMFN_MessageProcessor为MessageProcessor的成员函数指针类型。修改后的MessageProcessor定义如下:
class MessageProcessor { private: static const _MSGMAP_TABLE_ITEM_ s_messageMap[]; public: virtual const _MSGMAP_TABLE_ITEM_* GetMessageMapTable() const = 0; }; |
显然,MessageProcessor的派生类应该定义自己的消息映射表,并实现纯虚函数GetMessageMapTable()以返回自己的消息映射表。而MessageProcessor的消息映射表应置为空:
const _MSGMAP_TABLE_ITEM_ MessageProcessor::s_messageMap[] = { _UNKNOWN_MESSAGE_ID_, (PMFN_MessageProcessor)0 } |
如果让业务消息处理器的实现者在每个派生类中都把上述代码写一遍,显然是很愚蠢的,我们完全可以用一组宏来代劳:
#define DECLARE_MESSAGE_MAP_TABLE() / private: / static const _MSGMAP_TABLE_ITEM_ s_messageMap[]; / public: / virtual const _MSGMAP_TABLE_ITEM_* GetMessageMapTable() const;
#define BEGIN_MESSAGE_MAP_TABLE(processor_class_name) / const _MSGMAP_TABLE_ITEM_* / processor_class_name::GetMessageMapTable() const / { return &(processor_class_name::s_messageMap[0]); } / const _MSGMAP_TABLE_ITEM_ processor_class_name::s_messageMap[] = / {
#define END_MESSAGE_MAP_TABLE() / { _UNKNOWN_MESSAGE_ID_, (PMFN_MessageProcessor)0 } / }; |
DECALRE_MESSAGE_MAP_TABLE()用来在类定义中声明自己包含一个消息映射表及相应的虚函数,而BEGIN_MESSAGE_MAP_TABLE()和END_MESSAGE_MAP_TABLE()则用来在实现文件中初始化对应的消息映射表。显然,我们还需要一个宏,以便向消息映射表中填充二元组<消息ID,消息处理函数指针>:
#define ON_SERVICE_MESSAGE(message_id, member_func_name) / { message_id, (PMFN_MessageProcessor)&member_func_name }, |
现在MessageProcessor可以如下定义:
class MessageProcessor { DECLARE_MESSAGE_MAP_TABLE() public: virtual ~MessageProcessor() = 0; }; BEGIN_MESSAGE_MAP_TABLE(MessageProcessor) END_MESSAGE_MAP_TABLE() |
其任一派生类可如下定义:
class ExampleProcessor : public MessageProcessor { DECLARE_MESSAGE_MAP_TABLE() private: void OnMessage_X(HalfStructuredMessageWrapper); // 处理消息X void OnMessage_A(HalfStructuredMessageWrapper); // 处理消息A }; BEGIN_MESSAGE_MAP_TABLE(ExampleProcessor) ON_SERVICE_MESSAGE(SERVICE_MESSAGE_X, OnMessage_X) ON_SERVICE_MESSAGE(SERVICE_MESSAGE_A, OnMessage_A) END_MESSAGE_MAP_TABLE() |
可以想象,成员函数指针类型PMFN_MessageProcessor的定义应为:
typedef void (MessageProcessor::*PMFN_MessageProcessor) / (HalfStructuredMessageWrapper theWrapper); |
需要注意的是,应用层消息处理器的消息处理函数并不需要声明为virtual函数,而且基接口MessageProcessor也不会声明任何消息处理函数。当然,这并不是说消息处理函数不能为virtual函数,而是因为virtual函数会带来额外开销,即每增加一个虚函数,对应类的静态虚函数表就会增大若干字节(4或者8字节)。如果你愿意,可以把某些处理函数声明为virtual函数,框架仍可以正常工作,但是不建议那样做。
在此基础上,我们就可以实现函数MessageDispatcher::_Dispatch()。首先画出它的序列图如下:
图2-8 MessageDispatcher::_Dispatch()序列图
其实现代码如下:
void MessageDispatcher::_Dispatch(HalfStructuredMessageWrapper wrapper) { #ifdef _CENTRAL_SERVER_ MessageHeaderSmartPtr pMsgHeader = wrapper.pHalfMsg_->m_msgHeader; if ((pMsgHeader->GetMessageType() == MESSAGE_TYPE_LOCAL) || ( (pMsgHeader->GetMessageType() != MESSAGE_TYPE_BROADCAST) && (pMsgHeader->GetMessageType() != MESSAGE_TYPE_ACK) && (pMsgHeader->GetDestination()==CLIENT_ID_FOR_CENTRAL_SERVER) )) { m_theDemultiplexer->AcceptMessage(wrapper); // 不转发 } else { m_theDemultiplexer->ForwardMessage(wrapper); // 转发 } #else BOOL isAccepted = FALSE; MessageID curMsgId = / wrapper.pHalfMsg_->m_msgHeader->GetMessageID();
// 遍历处消息理器列表,查找注册了该消息ID的消息处理器 for (MessageProcessorList::iterator first = m_processorList.begin(); first != m_processorList.end(); ++first) { MessageProcessor *pMessageProcessor = *first; if (pMessageProcessor == NULL) break;
// 取得消息映射表并查找对应的映射项目 const _MSGMAP_TABLE_ITEM_* pTable = / pMessageProcessor->GetMessageMapTable(); const _MSGMAP_TABLE_ITEM_* lpEntry = NULL; while (pTable->m_pmfn != (PMFN_MessageProcessor)0) { if (pTable->m_msgId == curMsgId) { lpEntry = pTable; break; } else { ++pTable; } }// end while
if (lpEntry == NULL) // not found! continue; // 调用消息处理器函数处理消息 (pMessageProcessor->*(lpEntry->m_pmfn))(wrapper); isAccepted = TRUE; break; }// end for
if (!isAccepted) _Default(); #endif } |
对于中央服务器端,该函数的实现符合图2-7的设计,而MessageDemultiplexer::AcceptMessage()的实现与客户端消息分发过程的实现一致。可以看出,是否启用中央服务器的消息转发机制完全取决于客户端发出的消息的类型以及包含的目标客户端ID。所以如果你的系统并不需要中央服务器兼作为消息转发中心,那么发出的消息的目的客户端ID应该总是设置为CLIENT_ID_FOR_CENTRAL_SERVER(即0)即可,而不需要修改应用框架的代码。
这和MFC的消息映射表是什么关系? |
你会发现这里的消息映射表机制和消息分发函数很像MFC的消息映射表机制和分发函数,实际上它就是参照MFC设计的,只不过根据需要进行了相应的修改和简化,后面还将进一步改进它。可以看出,消息映射表的特点也是很明显的,它将远程网络消息或本地消息唯一地映射到了一个本地对象的成员函数,这就相当于这个本地对象给远程或本地客户端暴露了一个公共接口,客户端调用该公共接口的方法等价于向服务器消息处理器对象发送了一条消息。这就是分布式面向对象系统架构的思想基础。关于这种思想的进一步阐述参见本章2.17节。 |
消息分发依赖于消息处理器列表以及各个消息处理器包含的消息映射表,消息映射表的构造方法已经很清楚了,那么消息处理器列表如何生成呢?
2.7.2 消息处理器的注册
为了简化应用层开发人员的工作,当从接口MessageProcessor派生并实现每个具体的消息处理器后,最好能够自动创建处理器对象并注册到消息分发器对象的消息处理器列表中,而开发人员不需要知道它们是如何创建的。而且,一般说来程序编译完成后不能动态地增加或删除一个业务消息,也不能动态地增加或删除一个消息处理器。所以,将每个消息处理器定义为static全局对象,并且利用这个对象实例化时调用其构造函数的时机来完成注册,应该是可行的。通过这种方法,这些全局对象的名称也对应用层开发人员屏蔽了。目标很清楚了,下面就来动手实现。
首先定义两个用来注册消息处理器类的宏,这是最简单有效的办法:
#define REGISTER_MESSAGE_PROCESSOR(class_name) / typedef class_name class_name; #define IMPLEMENT_MESSAGE_PROCESSOR(class_name) / static class_name gs_##class_name; |
定义REGISTER_MESSAGE_PROCESSOR(class_name)宏的目的完全是为了完整性,即需要与IMPLEMENT_MESSAGE_PROCESSOR(class_name)配对使用。REGISTER_MESSAGE_PROCESSOR()在头文件中使用,而IMPLEMENT_MESSAGE_PROCESSOR()在实现文件中使用。
其次,需要给接口MessageProcessor增加默认构造函数实现。因为框架不可能去操纵具体的消息处理器派生类的构造函数,但是它们的构造函数最终都会调用基类即MessageProcessor的构造函数,所以在这里进行具体的注册是最好的选择:
MessageProcessor::MessageProcessor() { // 在这里自动注册每个具体的消息处理器对象 #ifdef _CENTRAL_SERVER_ MessageDemultiplexer *theDemultiplexer = / MessageDemultiplexerFactory::get_instance(); theDemultiplexer->RegisterProcessor(this); #else MessageDispatcher* theDispatcher = / MessageDispatcherFactory::get_instance(); theDispatcher->_RegisterProcessor(this); #endif } |
最后,需要为MessageDispatcher类增加这个注册消息处理器对象的方法:
#if !defined(_CENTRAL_SERVER_) void MessageDispatcher::_RegisterProcessor(MessageProcessor *proc) { m_processorList.push_back(pproc); } #endif |
MessageDemultiplexer::RegisterProcessor()方法的实现相同。
显然,消息处理器列表中包含的应该是各个消息处理器对象的原始指针:
typedef STD::list<MessageProcessor*> MessageProcessorList; |
一切看上去都很自然。但是稍等!这里好像有个小问题哦:
在MessageProcessor的构造函数中,我们调用了MessageDispatcherFactory::get_instance()来获得MessageDispatcher单例对象,这将触发MessageDispatcher的构造函数被调用起来,而在那里我们就已经启动了消息分发线程(回过头去看看MessageDispatcher::MessageDispatcher()的实现!)。再看看消息分发线程对象的_Run()函数实现,那里即将访问整个消息处理器列表并开始分发消息,此时是否所有消息处理器对象都已经注册完毕了呢?
假设用户定义并注册了多个消息处理器类(都从MessageProcessor继承),因此就会创建多个消息处理器对象(static全局对象)。虽然这些对象的实例化顺序不可预期,但是总有一个顺序。这里重要的是第一个实例化的对象,因为只有它在初始化时才会触发MessageDispatcher的构造函数被调用,进而调用this->start()把消息分发线程启动起来,后面实例化的对象只是访问MessageDispatcher单例对象并注册而已。现在回到MessageDispatcher::_Run()函数:它并没有立刻去分发消息,而是先查看消息队列中有没有消息,如果没有就进入等待状态。而所有消息处理器对象实例化都发生在main()函数之前,因此只要确保在主线程进入main()函数之前不会向消息分发线程的消息队列中放入消息,就可以保证在分发线程真正分发消息之前所有消息处理器都已经注册完毕。那么,能不能做到这一点呢?
让我们来看看哪些地方会向消息分发线程的消息队列中放入消息,参考图2-2或图2-7。
首先,NetworkObserverImpl对象只有在网络连接建立、断开以及有网络消息到来时才会向半结构化消息队列中写入消息,这里的关键是“连接建立”事件,因为收到网络消息和连接断开都发生在它的后面。而建立连接几乎可以肯定是在main()函数启动后,否则就得用全局对象来触发,用户会这样做吗?值得怀疑!
其次,Timer1SecondThread对象也会向消息队列中写入消息,不过它一般被用在需要计时和超时处理的地方,比如客户端发出请求消息并等待应答消息到来,同时规定一个超时时间,如果在这个规定的时间到达时仍然没有收到应答消息,就跳转到异常处理模块,等等。所以,一般说来也不会在main()函数之前启动Timer1SecondThread。
最后,用户自己需要向消息队列中写入其他消息的情况是无法预料的,比如他想把本地的一些事件映射为半结构化消息并按照统一模式来处理,但是这种事件发生在main()函数之前的可能性也不大。
嘘!似乎虚惊一场,危险性不大!J。不过用户的编码风格和行为是不可预料的,而且我们也不能假设用户会遵守上述规则,所以需要改进,而且也应该有改进的方法,我们将在2.11节中再具体讲述。
现在,MessageProcessor的任一派生类可如下定义和实现:
// ExampleProcessor.h class ExampleProcessor : public MessageProcessor { DECLARE_MESSAGE_MAP_TABLE() private: void OnMessage_X(HalfStructuredMessageWrapper); // 处理消息X void OnMessage_A(HalfStructuredMessageWrapper); // 处理消息A }; REGISTER_MESSAGE_PROCESSOR(ExampleProcessor)
// ExampleProcessor.cpp IMPLEMENT_MESSAGE_PROCESSOR(ExampleProcessor) BEGIN_MESSAGE_MAP_TABLE(ExampleProcessor) ON_SERVICE_MESSAGE(SERVICE_MESSAGE_X, OnMessage_X) ON_SERVICE_MESSAGE(SERVICE_MESSAGE_A, OnMessage_A) END_MESSAGE_MAP_TABLE() …… |
就是这么简单!现在该说一下业务消息的封装了。