逐步实现TCP服务端Step03-9:MessageBody的使用及改进

假设我们设计了一组向server请求账号信息的消息,格式如下:

消息首部不展开说明。client向server发送request消息,告诉server待查询账号的ID,以及能证明client已登陆的token,该token被设定为4字节二进制值,而非字符串。server收到request后,会向client发送response消息,消息中携带了账号ID,账号等级和积分信息。

两个消息对应的RequestAccountInfo和ResponseAccountInfo类:

对于消息接受方来说,不知道下一刻会收到什么消息。程序需要分辨出是什么消息,然后创建相应的对象去处理。对于消息的判定,可基于消息首部中的“消息ID”和“消息类型”字段。

实际使用时,可能会出现这样的代码:
上面的多个switch-case,目的就是要创建对应于消息的一个对象。若要处理大量消息的话,此处的代码将会非常臃肿。如果存在一个函数,可根据传入的消息ID和消息类型,完成对象的创建,对使用者来说将会非常方便。这个函数其实就是一个“工厂”,产出具体的消息对象。

如果要实现这样一个函数(假设名为CreateMessageBody),直接把上面的那套switch-case放到函数体里就行了。但这样做,CreateMessageBody函数就变得非常臃肿。应设计某种机制,基于消息ID和消息类型,快速定位到某个地址,该地址处存在一个函数,该函数用于完成对象的创建。

图中是一个二维数组,以消息ID作为索引号,在第一维中检索,得到一个二维数组,然后以该消息的类型ID在这个二维数组中检索,最后定位出Create函数。这是查表的过程,二维数组就是Table 。Create函数分散到各个消息体类中,让类的定义者负责去实现,显然,它必须要先于对象存在,它应属于类而非对象,即static类型。
注意上面红色框出的部分,每一个消息体类的定义中都需要定义这5个方法,重点是在每个类中的代码都一模一样。这种多处存在的重复代码应该用宏来代替。由于Encode和Decode方法本身很简单,直接将它们在类体中实现。

 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
#define PREPARE_ENCODE\
    if (!buf) return -1;\
    if (!PrepareEncode(buf, buf_size)) return -1;

#define PREPARE_DECODE\
    if (!buf || buf_size <= 0) return -1;\
    if (!PrepareDecode((unsigned char*)buf, buf_size)) return -1;

#define FINISH_ENCODE\
    return FinishEncode() == true ? 0 : -1;

#define FINISH_DECODE\
    return FinishDecode() == true ? 0 : -1;

#define COMMON_METHOD\
    virtual int Encode(unsigned char* buf, int& buf_size)\
    {\
        PREPARE_ENCODE;\
        StartEncode();\
        FINISH_ENCODE;\
    }\
    virtual int Decode(const unsigned char* buf, const int buf_size)\
    {\
        PREPARE_DECODE;\
        StartDecode();\
        FINISH_DECODE;\
    }\
    void StartEncode();\
    void StartDecode();\
    static MessageBody* Create();

有了上面的宏,再定义类就方便了,以RequestAccountInfo为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#define ACCOUNT_INFO_MSGID      (101)

class RequestAccountInfo : public MessageBody {
public:
    RequestAccountInfo();
    ~RequestAccountInfo();

    COMMON_METHOD;

public:
    int accid_; 
    int token_;   
};

再实现Create,StartEncode和StartDecode方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
RequestAccountInfo::RequestAccountInfo() 
    : accid_(0), token_(0)
{
}

RequestAccountInfo::~RequestAccountInfo()
{
}

void RequestAccountInfo::StartDecode() {
    *this >> accid_ >> token_;
}

void RequestAccountInfo::StartEncode() {
    *this << accid_ << token_;
}

MessageBody* RequestAccountInfo::Create() {
    return new RequestAccountInfo();
}

最后,在使用CreateMessageBody函数创建RequestAccountInfo对象之前,需要对RequestAccountInfo的Create函数进行注册(将Create函数的地址存到二维数组中):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main(int argc, char *argv[])
{
    RegisterMsgBodyCreateFunc(ACCOUNT_INFO_MSGID, 
        REQUEST, RequestAccountInfo::Create);

    MessageBody* request_account_info
        = CreateMessageBody(ACCOUNT_INFO_MSGID, REQUEST);

    return 0;
}

代码详见:

<==  index  ==>