protocol buffer 使用簡介

咱們項目中使用protocol buffer來進行服務器和客戶端的消息交互,服務器使用C++,因此本文主要描述protocol buffer C++方面的使用,其餘語言方面的使用參見google的官方文檔.ios

1.概覽

1.1 什麼是protocol buffer

protocol buffer是google的一個開源項目,它是用於結構化數據串行化的靈活、高效、自動的方法,例如XML,不過它比xml更小、更快、也更簡單。你能夠定義本身的數據結構,而後使用代碼生成器生成的代碼來讀寫這個數據結構。你甚至能夠在無需從新部署程序的狀況下更新數據結構。c++

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文件通過編譯以後都會對應的生成一個.h和一個.cc文件.api

字段限制

字段限制共有3類:
required:必須賦值的字段
optional:無關緊要的字段
repeated:可重複字段(變長字段),相似於數值
因爲一些歷史緣由,repeated字段並無想象中那麼高效,新版本中容許使用特殊的選項來得到更高效的編碼:服務器

repeated int32 samples = 4 [packed=true];

Tags

消息中的每個字段都有一個獨一無二的數值類型的Tag.1到15使用一個字節編碼,16到2047使用2個字節編碼,因此應該將Tags 1到15留給頻繁使用的字段.
能夠指定的最小的Tag爲$$1$$,最大爲$$2^{29}-1$$或$$536,870,911$$.可是不能使用$$19000$$到$$19999$$之間的值,這些值是預留給protocol buffer的.數據結構

註釋

使用C/C++的//語法來添加字段註釋.函數

2.2 值類型

proto的值類型與具體語言中值類型的對應關係.優化

2.3 可選字段與缺省值

在消息解析時,若是發現消息中沒有包含可選字段,此時會將消息解析對象中相對應的字段設置爲默認值,能夠經過下面的語法爲optional字段設置默認值:ui

optional int32 result_per_page = 3 [default = 10];

若是沒有指定默認值,則會使用系統默認值,對於string默認值爲空字符串,對於bool默認值爲false,對於數值類型默認值爲0,對於enum默認值爲定義中的第一個元素.this

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編碼,因此爲了提升效率,不建議枚舉值取負數.這些枚舉值能夠在其餘消息定義中重複使用.google

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支持更深層次的嵌套和分組嵌套,可是爲告終構清晰起見,不建議使用過深層次的嵌套,建議經過 2.5 小節提到的方法來實現.

2.7 更新一個數據類型

在更新一個數據類型時更多的是須要考慮與舊版本的兼容性問題:

  1. 不要改變任何已存在字段的Tag值,若是改變Tag值可能會致使數值類型不匹配,具體緣由參加protocol編碼
  2. 建議使用optionalrepeated字段限制,儘量的減小required的使用.
  3. 不須要的字段能夠刪除,刪除字段的Tag不該該在新的消息定義中使用.
  4. 不須要的字段能夠轉換爲擴展,反之亦然只要類型和數值依然保留
  5. int32, uint32, int64, uint64, 和bool是相互兼容的,這意味着能夠將其中一種類型任意改編爲另一種類型而不會產生任何問題
  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): 能夠設置的值有SPEED, CODE_SIZE, 或 LITE_RUNTIME. 不一樣的選項會如下述方式影響C++, Java代碼的生成.T
    • SPEED (default): protocol buffer編譯器將會生成序列化,語法分析和其餘高效操做消息類型的方式.這也是最高的優化選項.肯定是生成的代碼比較大.
    • CODE_SIZE: protocol buffer編譯器將會生成最小的類,肯定是比SPEED運行要慢
    • LITE_RUNTIME: protocol buffer編譯器將會生成只依賴"lite" runtime library (libprotobuf-lite instead of libprotobuf)的類. lite運行時庫比整個庫更小可是刪除了例如descriptors 和 reflection等特性. 這個選項一般用於手機平臺的優化.
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編譯器會爲每一個消息生成一個類,每一個類包含基本函數,消息實現,嵌套類型,訪問器等部分.

3.1 基本函數

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 消息實現

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方法,這些方法的名稱採用標識符全部小寫加上相應的前綴或後綴組成.生成一個值爲Tags的k標識符FieldNum常量,

3.5 其餘函數

除了生成上述類型的方法外, 編譯器還會生成一些用於消息類型處理的私有方法. 每個.proto文件在編譯的時候都會自動包含message.h文件,這個文件聲明瞭不少序列化和反序列化,調試, 複製合併等相關的方法.

3.6 使用例子

在咱們平時的使用中,一般一個message對應一個類,在對應的類中定義一個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
    ...
}

對應的cpp文件中實現對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;
}
相關文章
相關標籤/搜索