个人Protobuf消息设计原则


网络通讯涉及到消息的定义,不论是使用二进制模式、xml、json等格式。消息均可以大致的分为 命令消息、请求消息、应答消息和指示消息4大消息类型。通常状况下每一个消息还还有包含一个序列号和一个可以惟一区分类型类型的消息编号,编号可使用字符串、整数或者枚举等。json

1. 使用 protobuf 的enum定于消息的编号,也就是消息的类型。

我会为每一个系统都定义一个MSG枚举。包含系统用到的全部消息的枚举编号网络


enum MSG
{
  Login_Request  = 0x00001001;
  Login_Response = 0x00001002;

  XXX_Request  = 0x00001003;
  XXX_Request  = 0x00001004;

  XXX_Command = 0x00002001;

  XXX_Indication = 0x00003001;
}

2. 会为每一个具备消息体的消息定义一个对应的protobuf message。例如Login_Request会有一个对应LoginRequest消息。



message LoginRequest
{
  required bytes username = 1;
  required string password = 2;
}

3. 会为每一个消息大类定义一个消息,例如命令消息所有包含在message Command中,请求消息所有包含在Request消息中,应答消息所有包含在Response消息中,指示消息所有包含在Indication消息中。


也就是我会有下面4个protobuf message:函数


message Command
{// 包含全部的 XXXCommand 消息
}
message Request
{// 包含全部的 XXXRequest消息
}
message Response
{// 包含全部的Response消息
}
message Indication
{// 包含全部的Indication消息。
}

4. 对于应答消息,并不是老是成功的,所以在应答消息中还会包含另外2个字段。一个用于描述应答是否成功,一个用于描述失败时的字符串信息。 对于有多个应答的消息来讲,可能会包含是否为最后一个应答消息的标识。应答的序号(相似与网络数据包被分包之后,协议要合并时,须要知道分片在包中的具体位置)。所以Response看起来想这样:



message Response 
{
  required bool result = 1;
  optional bytes error_description = 2;
  required bool last_block = 3;
  required fixed32 block_index = 4;
  .....//其余的字段为 XXXResponse..
}

5. 最后我会定义一个大消息,把Command、Request、Response、Indication所有封装在一块儿,让后在通讯的时候都动大消息开始编解码。大消息看起来想下面这样。。



message Message
{
   required MSG type = 1;
   required fixed32 sequence = 2;
   
   optional Request request = 3;
   optional Response response = 4;
   optional Command  command = 5;
   optional Indication indication = 6;
}

6. 发送数据和接收数据。


用于UDP的时候比较简单,由于每一个数据包就是一个独立的Message消息,能够直接解码,或者编码后直接发送。ui

可是若是是使用于TCP的时候,因为涉及到粘包、拆包等处理,并且Message消息里面也没有包含长度相关的字段(很差处理),所以把Message编码后的消息嵌入另一个二进制消息中。this

使用4字节消息长度+Message(二进制数据)+(2字节CRC校验(可选))google

其中4字节的内容,只包含Message的长度,不包含自身和CRC的长度。若是须要也能够包含,当要记得通讯双方必须一致。编码

6. 消息处理(C++)

  编解码后,根据Message.type字段,能够知道要处理的消息,进行分发。不过通常状况下我不喜欢if、switch。因此我比较倾向于使用虚函数来处理。所以通常状况下我会定义一下的处理方法。spa

 

#pragma once

#include <Message.pb.h>
#include <memory>
#include <map>

#include "Client.h"

using std::shared_ptr;

class BaseHandler
{
public:
    BaseHandler(pbmsg::MSG type):type_(type){
        Register (this);
    }
    virtual ~BaseHandler(){}

    pbmsg::MSG GetType() const { return type_; }
	//具体处理方法,由派生类实现.
    virtual void Process( const shared_ptr<pbmsg::Message> & msg, const shared_ptr<Client> & client) = 0;

	//注册消息处理方法
    static void Register( BaseHandler *);
	//执行指定的消息,查询处理方法,调用Process。
    static void Execute( const shared_ptr<pbmsg::Message> & msg, const shared_ptr<Client> & client);
private:
    pbmsg::MSG type_;
    
   
private:
    static std::map<pbmsg::MSG , BaseHandler *> handers;
};
// 每一个消息都实现Process的一个特化版本...
template< pbmsg::MSG Type>
class MessageHandler : public BaseHandler
{
public:
    MessageHandler(void):BaseHandler(Type){}
    ~MessageHandler(void){}

    void Process( const shared_ptr<pbmsg::Message> & msg, const shared_ptr<Client> & client);
private:
    static MessageHandler thisHandler;
   
};

///放在.cpp\.cxx文件中.

void BaseHandler::Register( BaseHandler * h )
{
    handers[h->GetType ()] = h;
}


void BaseHandler::Execute( const shared_ptr<pbmsg::Message> & msg , ...其它参数)
{
    auto it = handers.find(msg->type());
    if( it != handers.end ())
    {
        it->second->Process(msg,client);
    }else{
        LOG(ERROR) <<"消息 "<<msg->type()<<" 没有对应的处理方法.\n";;
    }
}
//对每一个MSG 枚举的消息值,都会特化一个Process方法。
template<>
void MessageHandler<pbmsg::Login_Request>::Process( const shared_ptr<pbmsg::Message> & msg , ...其它参数){}
//而且在全局空间建立对象,系统启动时,自动建立。若是须要在堆空间中分配,另行封装方法,并调用下面的代码,让编译器实例化类。
MessageHandler<pbmsg::Login_Request> MessageHandler<pbmsg::Login_Request>::thisHandler;
// 最后消息处理:很是的easy:shared_ptr<pbmsg::Message> recvMessage( new pbmsg::Message());
bool parserOk = recvMessage->ParseFromArray((msg.rd_ptr ()+4), msg.size ()-4);
if( parserOk ){

    BaseHandler::Execute (recvMessage, ...其它参数);
  
 }

7. wireshark抓包

   protobuf是二进制的消息,wireshark抓包是没法直接分析的。不过google上面已经有了插件。 不过插件只支持UDP.本人在google上面的protobuf-wireshark的基础上修改了支持TCP的抓包解析,前提是顶层Message只有一个,并且封装在4个字节的长度后面。插件下载地址http://download.csdn.net/detail/chenxiaohong3905/5271945(wireshark 1.8.6版本). CSDN没分数的能够call me,留下你的邮箱。.net

相关文章
相关标签/搜索