网络游戏中通信消息的组织

     不同进程间通信都需要一些标识来定义本次通讯的意义及数据格式,而这些标识常常被称为消息,接下来讨论如何组织这些标识及与之对应的数据格式。
     传统的是的使用常量(枚举或宏)来定义消息编号,使用结构体来定义各消息的数据格式。在解析时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中去看。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值