Protocol Buffer使用簡介

我們項目中使用protocol buffer來進行服務(wù)器和客戶端的消息交互,服務(wù)器使用C++,所以本文主要描述protocol buffer C++方面的使用,其他語言方面的使用參見google的官方文檔.

1.概覽

1.1 什么是protocol buffer

protocol buffer是google的一個開源項目,它是用于結(jié)構(gòu)化數(shù)據(jù)串行化的靈活呆瞻、高效、自動的方法,例如XML辈毯,不過它比xml更小、更快、也更簡單按摘。你可以定義自己的數(shù)據(jù)結(jié)構(gòu),然后使用代碼生成器生成的代碼來讀寫這個數(shù)據(jù)結(jié)構(gòu)纫谅。你甚至可以在無需重新部署程序的情況下更新數(shù)據(jù)結(jié)構(gòu)炫贤。

2.使用

2.1定義一個消息類型

message SearchRequest 
{
  required string query = 1;
  optional int32 page_number = 2;// Which page number do we want?
  optional int32 result_per_page = 3;// Number of results to return per page.
}

該消息定義了三個字段,兩個int32類型和一個string類型的字段,每個字段由字段限制,字段類型,字段名和Tag四部分組成.對于C++,每一個.proto文件經(jīng)過編譯之后都會對應(yīng)的生成一個.h和一個.cc文件.

字段限制

字段限制共有3類:
required:必須賦值的字段
optional:可有可無的字段
repeated:可重復(fù)字段(變長字段),類似于數(shù)組
由于一些歷史原因,repeated字段并沒有想象中那么高效,新版本中允許使用特殊的選項來獲得更高效的編碼:

repeated int32 samples = 4 [packed=true];

Tags

消息中的每一個字段都有一個獨一無二的數(shù)值類型的Tag.1到15使用一個字節(jié)編碼,16到2047使用2個字節(jié)編碼,所以應(yīng)該將Tags 1到15留給頻繁使用的字段.
可以指定的最小的Tag為1, 最大為2^{29}-1或536,870,911.但是不能使用19000到19999之間的值,這些值是預(yù)留給protocol buffer的.

注釋

使用C/C++的//語法來添加字段注釋.

2.2 值類型

proto的值類型與具體語言中值類型的對應(yīng)關(guān)系.

2.3 可選字段與缺省值

在消息解析時,如果發(fā)現(xiàn)消息中沒有包含可選字段,此時會將消息解析對象中相對應(yīng)的字段設(shè)置為默認值,可以通過下面的語法為optional字段設(shè)置默認值:

optional int32 result_per_page = 3 [default = 10];

如果沒有指定默認值,則會使用系統(tǒng)默認值,對于string默認值為空字符串,對于bool默認值為false,對于數(shù)值類型默認值為0,對于enum默認值為定義中的第一個元素.

2.4 枚舉

message SearchRequest 
{
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3 [default = 10];
  enum Corpus 
  {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  optional Corpus corpus = 4 [default = UNIVERSAL];
}

由于枚舉值采用varint編碼,所以為了提高效率,不建議枚舉值取負數(shù).這些枚舉值可以在其他消息定義中重復(fù)使用.

2.5 使用其他消息類型

可以使用一個消息的定義作為另一個消息的字段類型.

message Result 
{
  required string url = 1;
  optional string title = 2;
  repeated string snippets = 3;
}

message SearchResponse 
{
  repeated Result result = 1;
}

可以使用import語法來包含另外一個.proto文件.

import "myproject/other_protos.proto";

2.6 嵌套類型

在protocol中可以定義如下的嵌套類型

message SearchResponse 
{
  message Result 
  {
    required string url = 1;
    optional string title = 2;
    repeated string snippets = 3;
  }
  repeated Result result = 1;
}

如果在另外一個消息中需要使用Result定義,則可以通過Parent.Type來使用.

message SomeOtherMessage 
{
  optional SearchResponse.Result result = 1;
}

protocol支持更深層次的嵌套和分組嵌套,但是為了結(jié)構(gòu)清晰起見,不建議使用過深層次的嵌套,建議通過 2.5 小節(jié)提到的方法來實現(xiàn).

2.7 更新一個數(shù)據(jù)類型

在更新一個數(shù)據(jù)類型時更多的是需要考慮與舊版本的兼容性問題:

  1. 不要改變?nèi)魏我汛嬖谧侄蔚腡ag值,如果改變Tag值可能會導(dǎo)致數(shù)值類型不匹配,具體原因參加protocol編碼
  2. 建議使用optionalrepeated字段限制,盡可能的減少required的使用.
  3. 不需要的字段可以刪除,刪除字段的Tag不應(yīng)該在新的消息定義中使用.
  4. 不需要的字段可以轉(zhuǎn)換為擴展,反之亦然只要類型和數(shù)值依然保留
  5. int32, uint32, int64, uint64, 和bool是相互兼容的,這意味著可以將其中一種類型任意改編為另外一種類型而不會產(chǎn)生任何問題
  6. sint32sint64是相互兼容的
  7. stringbytes是相互兼容的
  8. fixed32 兼容 sfixed32, fixed64 兼容 sfixed64.
  9. optional 兼容repeated

2.8 擴展

extend特性來讓你聲明一些Tags值來供第三方擴展使用.

message Foo 
{
  // ...
  extensions 100 to 199;
}

假如你在你的proto文件中定義了上述消息,之后別人在他的.proto文件中import你的.proto文件,就可以使用你指定的Tag范圍的值.

extend Foo 
{
  optional int32 bar = 126;
}

在訪問extend中定義的字段和,使用的接口和一般定義的有點不一樣,例如set方法:

    Foo foo;
    foo.SetExtension(bar, 15);

類似的有HasExtension(), ClearExtension(), GetExtension(), MutableExtension(), and AddExtension()等接口.

2.9 選項

  • optimize_for (file option): 可以設(shè)置的值有SPEED, CODE_SIZE, 或 LITE_RUNTIME. 不同的選項會以下述方式影響C++, Java代碼的生成.T
    • SPEED (default): protocol buffer編譯器將會生成序列化,語法分析和其他高效操作消息類型的方式.這也是最高的優(yōu)化選項.確定是生成的代碼比較大.
    • CODE_SIZE: protocol buffer編譯器將會生成最小的類,確定是比SPEED運行要慢
    • LITE_RUNTIME: protocol buffer編譯器將會生成只依賴"lite" runtime library (libprotobuf-lite instead of libprotobuf)的類. lite運行時庫比整個庫更小但是刪除了例如descriptors 和 reflection等特性. 這個選項通常用于手機平臺的優(yōu)化.
option optimize_for = CODE_SIZE;

3.常用API介紹

對于如下消息定義:

// test.proto
message PBStudent 
{    
    optional uint32 StudentID   = 1;
    optional string Name        = 2;
    optional uint32 Score       = 3;
}    
     
message PBMathScore
{    
    optional uint32 ClassID     = 1;  
    repeated PBStudent ScoreInf   = 2;
}

protocol buffer編譯器會為每個消息生成一個類,每個類包含基本函數(shù),消息實現(xiàn),嵌套類型,訪問器等部分.

3.1 基本函數(shù)

public:
 PBStudent();
 virtual ~PBStudent();
 
 PBStudent(const PBStudent& from);
 
 inline PBStudent& operator=(const PBStudent& from) {
   CopyFrom(from);
   return *this;
 }
 
 inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
   return _unknown_fields_;
 }
                                                                            
 inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() {
   return &_unknown_fields_;
 }
 
 static const ::google::protobuf::Descriptor* descriptor();
 static const PBStudent& default_instance();
 
 void Swap(PBStudent* other);

3.2 消息實現(xiàn)

PBStudent* New() const;
void CopyFrom(const ::google::protobuf::Message& from);
void MergeFrom(const ::google::protobuf::Message& from);
void CopyFrom(const PBStudent& from);
void MergeFrom(const PBStudent& from);
void Clear();
bool IsInitialized() const;                                                                          
 
int ByteSize() const;
bool MergePartialFromCodedStream(
    ::google::protobuf::io::CodedInputStream* input);
void SerializeWithCachedSizes(
    ::google::protobuf::io::CodedOutputStream* output) const;
::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
int GetCachedSize() const { return _cached_size_; }
private:
void SharedCtor();
void SharedDtor();
void SetCachedSize(int size) const;

3.3 嵌套類型

3.4 訪問器

// optional uint32 StudentID = 1;
inline bool has_studentid() const;
inline void clear_studentid();
static const int kStudentIDFieldNumber = 1;
inline ::google::protobuf::uint32 studentid() const;
inline void set_studentid(::google::protobuf::uint32 value);
 
// optional string Name = 2;
inline bool has_name() const;                                
inline void clear_name();
static const int kNameFieldNumber = 2;
inline const ::std::string& name() const;
inline void set_name(const ::std::string& value);
inline void set_name(const char* value);
inline void set_name(const char* value, size_t size);
inline ::std::string* mutable_name();
inline ::std::string* release_name();
inline void set_allocated_name(::std::string* name);

// optional uint32 Score = 3;                            
inline bool has_score() const;
inline void clear_score();
static const int kScoreFieldNumber = 3;
inline ::google::protobuf::uint32 score() const;
inline void set_score(::google::protobuf::uint32 value);

protocol buffer編譯器會對每一個字段生成一些getset方法,這些方法的名稱采用標(biāo)識符所有小寫加上相應(yīng)的前綴或后綴組成.生成一個值為Tags的k標(biāo)識符FieldNum常量,

3.5 其他函數(shù)

除了生成上述類型的方法外, 編譯器還會生成一些用于消息類型處理的私有方法. 每一個.proto文件在編譯的時候都會自動包含message.h文件,這個文件聲明了很多序列化和反序列化,調(diào)試, 復(fù)制合并等相關(guān)的方法.

3.6 使用例子

在我們平時的使用中,通常一個message對應(yīng)一個類,在對應(yīng)的類中定義一個set和create方法來生成和解析PB信息.針對上述消息定義如下類:

// test.h
class CStudent
{
public:
    unsigned    mStudentID;
    unsigned    mScore;
    string      mName;
    
    CStudent()
    {
        Init();
    }
    
    inline void Init()
    {
        mStudentID = 0;
        mScore = 0;
        mName = "";
    }
}

class CMathScore
{
private:
    unsigned    mClassID;
    CStudent    mScoreInf[100];
public:
    CMathSCore()
    {
        Init();
    }
    ~CMathScore() {};
    
    void Init();
    void SetFromPB(const PBMathScore* pPB);
    void CreatePB(PBMathScore* pPB);
    
    // Get & Set mClassID
    ...
    // Get & set mScoreInf
    ...
    // some other function
    ...
}

對應(yīng)的cpp文件中實現(xiàn)對PB的操作

// test.cpp
void CMathScore::Init()
{
    mClassID = 0;
    memset(mScoreInf, 0, sizeof(mScoreInf));
}

void CMathScore::SetFromPB(const PBMathScore* pPB)
{
    if ( NULL == pPB ) return;
    
    mClassID = pPB->classid();
    for(unsigned i = 0; i < (unsigned)pPB->scoreinf_size() && i < 100; ++i)
    {
        PBStudent* pStu = pPB->mutable_scoreinf(i);
        mScoreInf[i].mStudentID = pStu->studentid();
        mScoreInf[i].mScore     = pStu->score();
        mScoreInf[i].mName      = pStu->name();
    }
}

void CMathScore::CreatePB(PBMathScore* pPB)
{
    if ( NULL == pPB ) return;
    
    pPB->set_classid(mClassID);
    for(unsigned i = 0; i < 100; ++i)
    {
        PBStudent* pStu = pPB->add_scoreinf();
        pStu->set_studentid(mScoreInf[i].mStudentID)
        pStu->set_score(mScoreInf[i].mScore);
        pStu->set_name(mScoreInf[i].mName);     
    }
}

PB文件的讀寫

// use.cpp
#include<test.h>

#defind     MAX_BUFFER      1024 * 1024
int write()
{
    CMathScore  mMath;
    PBMathScore mPBMath;
    // use set functions to init member variable
    
    fstream fstm("./math.dat", ios::out | ios::binary);
    if ( fstm.is_open() == false )
    {
        return -1;
    }   
    char* tpBuffer = (char*)malloc(MAX_BUFFER);
    if ( NULL == tpBuffer )
    {
        return -2;
    }
    
    mMath.CreatePB(&mPBMath);
    if ( mPBMath.SerializeToArray(tpBuffer, mPBMath.ByteSize()) == false )
    {
        return -3;
    }
    fstm.write(tpBuffer, mPBMath.ByteSize());
    free(tpBuffer);
    fstm.close();
    
    return 0;
}

int read()
{
    CMathScore  mMath;
    PBMathScore mPBMath;
    
    fstream fstm.open("./math.dat", ios::out | ios::binary);
    if ( fstm.is_open() == false )
    {
        return -1;
    }   
    char* tpBuffer = (char*)malloc(MAX_BUFFER);
    if ( NULL == tpBuffer )
    {
        return -2;
    }
    char*   tpIdx = tpBuffer;
    int     tLen;
    while ( !fstm.eof() && tLen < MAX_BUFFER )
    {
        fstm.read(tpIdx, 1);
        tpIdx += 1;
        tLen++;
    }
    if ( mPBMath.ParseFromArray(tpBuffer, tLen - 1) == false )
    {
        return -3;
    }
    fstm.close();
    free(tpBuffer);
    tpIdx = NULL;

    mMath.SetFromPB(&mPBMath);
    // do some thing

    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市付秕,隨后出現(xiàn)的幾起案子兰珍,更是在濱河造成了極大的恐慌,老刑警劉巖询吴,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掠河,死亡現(xiàn)場離奇詭異亮元,居然都是意外死亡,警方通過查閱死者的電腦和手機唠摹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門爆捞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人勾拉,你說我怎么就攤上這事煮甥。” “怎么了藕赞?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵苛秕,是天一觀的道長。 經(jīng)常有香客問我找默,道長,這世上最難降的妖魔是什么吼驶? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任惩激,我火速辦了婚禮,結(jié)果婚禮上蟹演,老公的妹妹穿的比我還像新娘风钻。我一直安慰自己,他們只是感情好酒请,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布骡技。 她就那樣靜靜地躺著,像睡著了一般羞反。 火紅的嫁衣襯著肌膚如雪布朦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天昼窗,我揣著相機與錄音是趴,去河邊找鬼。 笑死澄惊,一個胖子當(dāng)著我的面吹牛唆途,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掸驱,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼肛搬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了毕贼?” 一聲冷哼從身側(cè)響起温赔,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鬼癣,沒想到半個月后让腹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體远剩,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年骇窍,在試婚紗的時候發(fā)現(xiàn)自己被綠了瓜晤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡腹纳,死狀恐怖痢掠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嘲恍,我是刑警寧澤足画,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站佃牛,受9級特大地震影響淹辞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜俘侠,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一象缀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧爷速,春花似錦央星、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至廉沮,卻和暖如春颓遏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背滞时。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工州泊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人漂洋。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓遥皂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親刽漂。 傳聞我的和親對象是個殘疾皇子演训,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容