8. CwxCommander命令模式等相关类
前面在CwxThread线程类和CwxThreadPool/CwxThreadPoolEx线程池类中,描述了线程的两种执行方式。回顾如下:
线程依据构造函数传递的参数不同,有两种工作方式。第一种是执行已指定的某个函数,这种方式比较简单,与pthread的用法基本一致。第二种是通过Commander这种机制来完成具体的功能。这种方式比较复杂。
在Commander这种方式下,每个消息都有自己的类型, 每种类型的消息都有一个预设的操作对象来处理这个消息。Commander可以被视为类型到操作对象的映射关系管理者。当线程按Commander这种方式工作时,线程不断地从消息队列中取出消息,然后交给Commander分发处理。
Cwinux采用了Command设计模式,Commander等类就是具体的实现。
Command设计模式适用于如下的情形:
- 抽象出待执行的动作以参数化某对象。
- 在不同时刻指定、排列和执行请求。
- 支持取消操作。
- 支持修改日志。
Cwinux中,CwxCommander符合第一种情形。
对于第一种情形,例如,在Java GUI编程中,我们可以使用库中自带工具箱中的Menu类来添加一个菜单。该Menu类的开发者并不知道某个Menu的MenuItem对应的动作时什么,但可以指定一个Command类,这个Command类具有一个函数execute。Menu的使用者从Command派生一个子类ConcreteCommand,在ConcreteCommand的execute函数中实现具体的工作。这种例子中,Command模式是面向对象版本的回调函数机制。
Command的结构:
CwxCommander,CwxCmdOp等类就是Cwinux中Command模式的实现。
在cmn/CwxCommander.h文件中定义了两个类 CwxCmdOp和CwxCommander。
CwxCmdOp代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class CWX_API CwxCmdOp { public: CwxCmdOp() { } virtual ~CwxCmdOp() { } public: virtual int onConnCreated(CwxMsgBlock*& msg, CwxTss* pThrEnv) {} virtual int onConnClosed(CwxMsgBlock*& msg, CwxTss* pThrEnv) {}
/** @brief 收到通信数据包事件的处理函数。 @param [in] msg 收到通信数据包的事件对象。 @param [in] pThrEnv 线程的TSS对象。 @return -1:处理失败,0:不处理此事件,1:处理此事件。 */ virtual int onRecvMsg(CwxMsgBlock*& msg, CwxTss* pThrEnv) { CWX_UNUSED_ARG(msg); CWX_UNUSED_ARG(pThrEnv); return 0; }
virtual int onEndSendMsg(CwxMsgBlock*& msg, CwxTss* pThrEnv) {} virtual int onFailSendMsg(CwxMsgBlock*& msg, CwxTss* pThrEnv) {} virtual int onTimeoutCheck(CwxMsgBlock*& msg, CwxTss* pThrEnv) {} virtual int onEvent4Handle(CwxMsgBlock*& msg, CwxTss* pThrEnv) {} virtual int onUserEvent(CwxMsgBlock*& msg, CwxTss* pThrEnv) {} };
|
CwxCmdOp有若干个处理函数,当前这些处理函数不做任何有用的工作。这是Command模式中的Command类,这些onXXX函数就是Command类的execute函数(这里有多个不同的execute函数)。
具体使用时,Cwinux的使用者从CwxCmdOp继承一个具体的子类,例如CwxEchoEventHandler,然后实现所关心的execute函数(例如onRecvMsg函数)。
下面是CwxCommander类的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| /** @class CwxCommander @brief Command类,基于事件的SVR-ID,实现事件与其处理Handle的映射。 */ class CWX_API CwxCommander { ///消息映射函数类型定义 typedef int (*fnEventApi)(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv); ///SVR-ID与其处理Handle的映射Hash typedef hash_map<CWX_UINT32, CwxCmdOp*, CwxNumHash<CWX_UINT32> > CwxEventCommandHash; public: ///构造函数 CwxCommander():m_msgHash(1024) { m_arrEvent[CwxEventInfo::DUMMY] = NULL; m_arrEvent[CwxEventInfo::CONN_CREATED] = &CwxCommander::onConnCreated; m_arrEvent[CwxEventInfo::CONN_CLOSED] = &CwxCommander::onConnClosed; m_arrEvent[CwxEventInfo::RECV_MSG] = &CwxCommander::onRecvMsg; m_arrEvent[CwxEventInfo::END_SEND_MSG] = &CwxCommander::onEndSendMsg; m_arrEvent[CwxEventInfo::FAIL_SEND_MSG] = &CwxCommander::onFailSendMsg; m_arrEvent[CwxEventInfo::TIMEOUT_CHECK] = &CwxCommander::onTimeoutCheck; m_arrEvent[CwxEventInfo::EVENT_4_HANDLE] = &CwxCommander::onEvent4Handle; m_arrEvent[CwxEventInfo::SYS_EVENT_NUM] = &CwxCommander::onUserEvent; } ///析构函数 ~CwxCommander() { m_msgHash.clear(); } public: ///注册SVR-ID为uiSvrID的事件的处理函数。返回值,0:success, -1: 此SVR-ID已经存在 int regHandle(CWX_UINT32 uiSvrID, CwxCmdOp* pHandle);
/** @brief 将消息分发给其处理Handle @param [in] msg 要分发的事件 @param [in] pThrEnv 线程的TSS对象。 @param [in] iRet Handle对消息的处理结果,-1:处理失败,0:指定的Handle不处理此事件,1:处理成功。 @return true:将消息分发给了指定的处理Handle;false:没有handle处理此消息 */ bool dispatch(CwxMsgBlock*& msg, CwxTss* pThrEnv, int& iRet);
///清空Command注册的SVR-ID void reset() { m_msgHash.clear(); } private: /** @brief 将收到通信数据包事件分发给事件处理Handle。 @param [in] pEventOp 事件的处理Handle。 @param [in] msg 连接建立的事件对象。 @param [in] pThrEnv 线程的TSS对象。 @return -1:Handle处理失败,0:Handle不处理此事件,1:Handle成功处理此事件。 */ static int onRecvMsg(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv);
static int onConnCreated(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv); static int onConnClosed(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv); static int onEndSendMsg(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv); static int onFailSendMsg(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv); static int onTimeoutCheck(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv); static int onEvent4Handle(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv); static int onUserEvent(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv); private: ///获取SVR-ID的处理Handle CwxCmdOp* getEventOp(CWX_UINT32 uiSvrID); private: fnEventApi m_arrEvent[CwxEventInfo::SYS_EVENT_NUM + 1];///事件类型与处理API的映射 CwxEventCommandHash m_msgHash;///<事件SVR-ID与事件处理Hanlde的映射 };
|
首先,CwxCommander类的功能:基于事件的SVR-ID(消息包中带有这个属性),实现事件与其处理Handle的映射。
数据结构:既然是实现一个映射关系,自然是通过map数据结构。这里是使用的hash_map。代码中有如下代码片段:
1 2
| // SVR-ID与其处理Handle的映射Hash typedef hash_map<CWX_UINT32, CwxCmdOp*, CwxNumHash<CWX_UINT32> > CwxEventCommandHash;
|
这段代码定义了一种类型,该类型类型名叫CwxEventCommandHash,它本质上是一个hash_map类型,hash_map的前两个模板是常用的Key的类型和Value的类型,第三个模板是用来指定一个哈希函数,hash_map在维护内部数据结构的时候使用这个哈希函数。注意到,CwxEventCommandHash的Key类型是CWX_UINT32,这是SVR-ID的类型。SVR-ID是每个消息包都有的一个属性,CwxCommander使用这个属性进行分发。CwxEventCommandHash的Value类型是CwxCmdOp*类型。通过这种方式,就将SVR-ID到CwxCmdOp映射关系记录下来了。
另外, typedef int (*fnEventApi)(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss pThrEnv);* 定义了一个函数指针类型(消息映射函数类型定义)。该函数指针指向的函数的参数是CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv三个参数,返回值是int型变量。
同时,代码fnEventApi m_arrEvent[CwxEventInfo::SYS_EVENT_NUM + 1];///事件类型与处理API的映射 定义了一个函数指针数组。该数组依据事件类型对应到不同的函数指针。事件类型也是每个消息包的一个属性。Cwinux中有若干中事件类型:
1 2 3 4 5 6 7 8 9 10 11
| enum { DUMMY = 0,///<dummy事件,此为无效事件类型 CONN_CREATED = 1,///<连接建立事件 CONN_CLOSED = 2,///<连接关闭事件 RECV_MSG = 3,///<收到通信数据事件 END_SEND_MSG = 4,///<通信数据包发送完毕事件 FAIL_SEND_MSG = 5,///<通信数据包发送失败事件 TIMEOUT_CHECK = 6,///<超时检查事件 EVENT_4_HANDLE = 7,///<IO上注册的事件发生的事件 SYS_EVENT_NUM = 8,///<系统事件的数量 };
|
这些事件类型会对应到CwxCommander的几个静态函数onRecvMsg,onConnCreated等(该对应关系在CwxCommander构造函数中设置)。每一个类型都对应于Command模式中的Command类的一个execute函数,这也是为何Cwinux中有多个execute函数。
好了,先暂停回顾一下。
每个消息都有SVR-ID和事件类型两个属性。
CwxCommander保存了SVR-ID到具体的CwxCmdOp的映射关系,同时每一个CwxCmdOp都有对应于不同事件类型的操作函数。
所以,每一个消息,交给CwxCommander后,CwxCommander能够依据消息的这两个属性找到一个具体的操作函数。
接着CwxCommander代码继续。
CwxCommander保存了SVR-ID到具体的CwxCmdOp的映射关系。在使用时,首先要先注册这种关系。regHandle就是实现这个功能。
1 2 3 4 5 6 7 8 9
| ///注册SVR-ID为uiSvrID的事件的处理函数。返回值,0:success, -1: 此SVR-ID已经存在 inline int CwxCommander::regHandle(CWX_UINT32 uiSvrID, CwxCmdOp* pHandle) { CWX_ASSERT(pHandle != NULL); if (this->getEventOp(uiSvrID)) return -1; this->m_msgHash[uiSvrID] = pHandle; return 0; }
|
其中,getEventOp是检查是否已经注册过了。代码如下:
1 2 3 4 5 6 7 8 9
| inline CwxCmdOp* CwxCommander::getEventOp(CWX_UINT32 uiSvrID) { CwxEventCommandHash::iterator iter = this->m_msgHash.find(uiSvrID); if (iter != this->m_msgHash.end()) { return iter->second; } return NULL; }
|
CwxCommander的重头戏在dispatch函数。dispatch函数将消息分发给消息属性对应的(消息属性决定)Handle(CwxCmdOp的子类)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| /** @brief 将消息分发给其处理Handle @param [in] msg 要分发的事件 @param [in] pThrEnv 线程的TSS对象。 @param [in] iRet Handle对消息的处理结果,-1:处理失败,0:指定的Handle不处理此事件,1:处理成功。 @return true:将消息分发给了指定的处理Handle;false:没有handle处理此消息 */ inline bool CwxCommander::dispatch(CwxMsgBlock*& msg, CwxTss* pThrEnv, int& iRet) { CwxCmdOp* pHandle = getEventOp(msg->event().getSvrId()); if (!pHandle) return false; if (msg->event().getEvent() < CwxEventInfo::SYS_EVENT_NUM) { if (m_arrEvent[msg->event().getEvent()]) { iRet = (*m_arrEvent[msg->event().getEvent()])(pHandle, msg, pThrEnv); } else { iRet = 0; } } else { iRet = (*m_arrEvent[CwxEventInfo::SYS_EVENT_NUM])(pHandle, msg, pThrEnv); } return true; }
// CwxCommander的几个操作的函数的实现。 int CwxCommander::onConnCreated(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv) { return pEventOp->onConnCreated(msg, pThrEnv); }
int CwxCommander::onConnClosed(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv) { return pEventOp->onConnClosed(msg, pThrEnv); }
int CwxCommander::onRecvMsg(CwxCmdOp* pEventOp, CwxMsgBlock*& msg, CwxTss* pThrEnv) { return pEventOp->onRecvMsg(msg, pThrEnv); }
|
dispatch函数中,首先根据消息的SVR_ID属性查找对应的CwxCmdOp类型的Handle,然后根据消息的事件类型属性查找对应的操作函数,最后调用该操作函数(CwxCommander的几个静态函数)。CwxCommander的这几个静态函数实际执行的是Handle对应的操作函数。
综上,CwxCommander及相关类逻辑结构如图示。
结合线程池的实现,将上述内容概述如下:
用户(使用Cwinux的应用开发者)在应用初始化时建立线程池,并创建Commander。然后实现CwxCmdOp的子类XxxHandle用于处理消息。用户将XxxHandle注册到Commander中。用户从网络上接收到的消息包,并投入到线程池中的消息队列中。线程池会从消息队列中不断地读取消息并通过Commander方式执行dispatch函数将消息分发到具体的操作函数来处理该消息。