咱們項目中使用protocol buffer來進行服務器和客戶端的消息交互,服務器使用C++,因此本文主要描述protocol buffer C++方面的使用,其餘語言方面的使用參見google的官方文檔.ios
protocol buffer是google的一個開源項目,它是用於結構化數據串行化的靈活、高效、自動的方法,例如XML,不過它比xml更小、更快、也更簡單。你能夠定義本身的數據結構,而後使用代碼生成器生成的代碼來讀寫這個數據結構。你甚至能夠在無需從新部署程序的狀況下更新數據結構。c++
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];
消息中的每個字段都有一個獨一無二的數值類型的Tag.1到15使用一個字節編碼,16到2047使用2個字節編碼,因此應該將Tags 1到15留給頻繁使用的字段.
能夠指定的最小的Tag爲$$1$$,最大爲$$2^{29}-1$$或$$536,870,911$$.可是不能使用$$19000$$到$$19999$$之間的值,這些值是預留給protocol buffer的.數據結構
使用C/C++的//
語法來添加字段註釋.函數
proto的值類型與具體語言中值類型的對應關係.優化
在消息解析時,若是發現消息中沒有包含可選字段,此時會將消息解析對象中相對應的字段設置爲默認值,能夠經過下面的語法爲optional
字段設置默認值:ui
optional int32 result_per_page = 3 [default = 10];
若是沒有指定默認值,則會使用系統默認值,對於string
默認值爲空字符串,對於bool
默認值爲false,對於數值類型
默認值爲0,對於enum
默認值爲定義中的第一個元素.this
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
可使用一個消息的定義做爲另外一個消息的字段類型.
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";
在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 小節提到的方法來實現.
在更新一個數據類型時更多的是須要考慮與舊版本的兼容性問題:
optional
和repeated
字段限制,儘量的減小required
的使用.int32
, uint32
, int64
, uint64
, 和bool
是相互兼容的,這意味着能夠將其中一種類型任意改編爲另一種類型而不會產生任何問題sint32
和 sint64
是相互兼容的string
和 bytes
是相互兼容的fixed32
兼容 sfixed32
, fixed64
兼容 sfixed64
.optional
兼容repeated
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()
等接口.
SPEED
, CODE_SIZE
, 或 LITE_RUNTIME
. 不一樣的選項會如下述方式影響C++, Java代碼的生成.T
option optimize_for = CODE_SIZE;
對於以下消息定義:
// 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編譯器會爲每一個消息生成一個類,每一個類包含基本函數,消息實現,嵌套類型,訪問器等部分.
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);
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;
// 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編譯器會對每個字段生成一些get
和set
方法,這些方法的名稱採用標識符全部小寫加上相應的前綴或後綴組成.生成一個值爲Tags的k標識符FieldNum
常量,
除了生成上述類型的方法外, 編譯器還會生成一些用於消息類型處理的私有方法. 每個.proto
文件在編譯的時候都會自動包含message.h文件,這個文件聲明瞭不少序列化和反序列化,調試, 複製合併等相關的方法.
在咱們平時的使用中,一般一個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; }