传统的是的使用常量(枚举或宏)来定义消息编号,使用结构体来定义各消息的数据格式。在解析时switch-case常量来使用相应的结构体解析数据并进行处理,这种原生的方式会带来各种问题:使switch-case处理函数会变得相当长导而致难以维护;消息编号和其数据格式结构体映射混乱;不利于新增消息的分流等等。而后又演变出分段、消息结构体继承等思想以缓解这种情况。今天我探寻的是延续这些思想并进行扩展,使消息的定义、使用更方便。
在解决"大"问题的时候,使用分治法往往都能收到很不错的效果,面对数以千记的消息,果断地将它们分段,分段上再应用点小策略:从逻辑上来看各分段形成一颗树形结构,该树的每个节点要么是具体的消息,要么是一个分段。每个节点都有一个值和类型(核心思想),其中节点值在兄弟间唯一的,节点类型是继承自父节点的类型。仅有分段才能具有子节点。如图:
_root_
/ \
protocola _segmentA_
/ | \
_segmentAA_ _segmentBB_ protocolaa
... ...
[root为消息的根节点,segment为消息段,protocol为消息编号]
消息值和消息类型都在其父节点的域内,如访问消息protocola为_root_::protocola。这样做主要是防止命名冲突,如组队系统中有同意邀请消息,而副本系统也有同意邀请消息。
Ok,话了这么多,直接上码:
/
typedef unsigned char PROT_TYPE; // 消息编号类型
// 根消息段。
namespace msg_ROOT_sg
{
struct ROOT_dt { PROT_TYPE No() { return _no; } protected: PROT_TYPE _no; };
typedef ROOT_dt MyDT;
}
// 开始定义一个段
// belong所属段名 segment当前段名
#define BEGIN_DEFINE_SEGMENT(belong , segment) \
namespace msg_##segment##_sg \
{ \
typedef msg_##belong##_sg::segment##_dt MyDT;
// 结束定义一个段
#define END_DEFINE_SEGMENT(segment) }
// 在段的定义中声明一个段
// segment段名 value段的值(该值必须是父亲直接孩子中唯一的)
#define DECLEAR_SEGMENT(segment , value) \
static PROT_TYPE const segment = value ; \
struct segment##_dt: public MyDT \
{ \
segment##_dt() { MyDT::_no = segment; } \
PROT_TYPE No () { return _no ; } \
protected: PROT_TYPE _no; \
};
// 在段的定义中声明一个消息
// protocol消息名 value消息的值(该值必须是父亲直接孩子中唯一的)
#define DECLEAR_PROTOCOL(protocol , value) static PROT_TYPE const protocol = value;
// 段的定义外开始定义一个消息
// belong所属段 protocol消息名
#define BEGIN_DEFINE_PROTOCOL(belong , protocol) \
namespace msg_##belong##_sg \
{ \
struct protocol##_dt : public MyDT \
{ \
protocol##_dt() { MyDT::_no = protocol; }
// 段的定义外结束定义一个消息
// protocol消息名
#define END_DEFINE_PROTOCOL(protocol ) }; }
// 在段的定义中定义一个消息
// protocol消息名 value消息的值(该值必须是父亲直接孩子中唯一的)
#define DEFINE_PROTOCOL(protocol , value) \
static PROT_TYPE const protocol = value ; \
struct protocol##_dt : public MyDT \
{ protocol##_dt() { MyDT::_no = protocol; } };
BEGIN_DEFINE_SEGMENT(ROOT, ROOT) // 根节点的所属段依旧是根。而且段名必须是ROOT
DECLEAR_SEGMENT(CHAT, 1) // 声明聊天段
DEFINE_PROTOCOL(GC_HEART_DETECT, 100) // 定义GC_HEART_DETECT消息,该消息不附带额外的数据。
END_DEFINE_SEGMENT(ROOT)
// 定义聊天消息段
BEGIN_DEFINE_SEGMENT(ROOT, CHAT)
DECLEAR_PROTOCOL(CS_GMCHAT, 10) // 声明GM聊天消息
END_DEFINE_SEGMENT(CHAT)
// 定义GM聊天消息
BEGIN_DEFINE_PROTOCOL(CHAT, CS_GMCHAT)
char szContext[128]; // 内容
unsigned char byType; // 类型
END_DEFINE_PROTOCOL(CS_GMCHAT)
/
#include <iostream>
#include<windows.h>
void Recv(msg_ROOT_sg::ROOT_dt *pData); // 模拟全局消息处理函数
void MsgProc_Chat(msg_ROOT_sg::CHAT_dt *pData); // 处理聊天系统的消息函数(模拟具体的消息段)
int _tmain(int argc, _TCHAR* argv[])
{
msg_CHAT_sg::CS_GMCHAT_dt data;
strcpy(data.szContext, "this a test");
data.byType = 100;
void *pData = &data; // 模拟网络传输后
Recv((msg_ROOT_sg::ROOT_dt *)pData);
msg_ROOT_sg::GC_HEART_DETECT_dt detect;
pData = &detect; // 模拟网络传输后
Recv((msg_ROOT_sg::ROOT_dt *)pData);
system("pause");
return 0;
}
void Recv(msg_ROOT_sg::ROOT_dt *pData)
{
switch (pData->No())
{
case msg_ROOT_sg::CHAT:
MsgProc_Chat((msg_ROOT_sg::CHAT_dt*)pData);
break;
case msg_ROOT_sg::GC_HEART_DETECT:
std::cout << "Msg: GC_HEART_DETECT" << std::endl;
break;
default:break;
}
}
// 处理聊天系统的消息函数
void MsgProc_Chat(msg_ROOT_sg::CHAT_dt *pData)
{
switch (pData->No())
{
case msg_CHAT_sg::CS_GMCHAT:
{
msg_CHAT_sg::CS_GMCHAT_dt *pChat = (msg_CHAT_sg::CS_GMCHAT_dt*)pData;
std::cout << "Msg:CS_GMCHAT ";
std::cout << "Chat:" << pChat->szContext << " Type:" << pChat->byType << std::endl;
}
break;
}
}
PS: 几个项目一步一步总结出来的,现也运用在项目中~但未必完美,望不吝赐教!
排版貌似乱了,我是直接从偶的demo中拷贝出来的,懒得排了....感兴趣自己拷到.h/.cpp中去看。