個人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

相關文章
相關標籤/搜索