LoopJump's Blog

Cwinux源码解析2

2014-01-07

8. CwxCommander命令模式等相关类

前面在CwxThread线程类和CwxThreadPool/CwxThreadPoolEx线程池类中,描述了线程的两种执行方式。回顾如下:

线程依据构造函数传递的参数不同,有两种工作方式。第一种是执行已指定的某个函数,这种方式比较简单,与pthread的用法基本一致。第二种是通过Commander这种机制来完成具体的功能。这种方式比较复杂。

在Commander这种方式下,每个消息都有自己的类型, 每种类型的消息都有一个预设的操作对象来处理这个消息。Commander可以被视为类型到操作对象的映射关系管理者。当线程按Commander这种方式工作时,线程不断地从消息队列中取出消息,然后交给Commander分发处理。

Cwinux采用了Command设计模式,Commander等类就是具体的实现。

Command设计模式适用于如下的情形:

  1. 抽象出待执行的动作以参数化某对象。
  2. 在不同时刻指定、排列和执行请求。
  3. 支持取消操作。
  4. 支持修改日志。

Cwinux中,CwxCommander符合第一种情形。

对于第一种情形,例如,在Java GUI编程中,我们可以使用库中自带工具箱中的Menu类来添加一个菜单。该Menu类的开发者并不知道某个Menu的MenuItem对应的动作时什么,但可以指定一个Command类,这个Command类具有一个函数execute。Menu的使用者从Command派生一个子类ConcreteCommand,在ConcreteCommand的execute函数中实现具体的工作。这种例子中,Command模式是面向对象版本的回调函数机制。

Command的结构:

Command_patter

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及相关类逻辑结构如图示。

Commander_newsize

结合线程池的实现,将上述内容概述如下:

用户(使用Cwinux的应用开发者)在应用初始化时建立线程池,并创建Commander。然后实现CwxCmdOp的子类XxxHandle用于处理消息。用户将XxxHandle注册到Commander中。用户从网络上接收到的消息包,并投入到线程池中的消息队列中。线程池会从消息队列中不断地读取消息并通过Commander方式执行dispatch函数将消息分发到具体的操作函数来处理该消息。

扫描二维码,分享此文章