Protocol Buffer使用簡介
我們項目中使用protocol buffer來進行服務器和客戶端的消息交互,服務器使用C++,所以本文主要描述protocol buffer C++方面的使用,其他語言方面的使用參見google的官方文檔.
1.概覽
1.1 什么是protocol buffer
protocol buffer是google的一個開源項目,它是用於結構化數據串行化的靈活、高效、自動的方法,例如XML,不過它比xml更小、更快、也更簡單。你可以定義自己的數據結構,然后使用代碼生成器生成的代碼來讀寫這個數據結構。你甚至可以在無需重新部署程序的情況下更新數據結構。
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
文件.
字段限制
字段限制共有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
字段設置默認值:
optional int32 result_per_page = 3 [default = 10];
如果沒有指定默認值,則會使用系統默認值,對於string
默認值為空字符串,對於bool
默認值為false,對於數值類型
默認值為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編碼,所以為了提高效率,不建議枚舉值取負數.這些枚舉值可以在其他消息定義中重復使用.
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 更新一個數據類型
在更新一個數據類型時更多的是需要考慮與舊版本的兼容性問題:
- 不要改變任何已存在字段的Tag值,如果改變Tag值可能會導致數值類型不匹配,具體原因參加protocol編碼
- 建議使用
optional
和repeated
字段限制,盡可能的減少required
的使用. - 不需要的字段可以刪除,刪除字段的Tag不應該在新的消息定義中使用.
- 不需要的字段可以轉換為擴展,反之亦然只要類型和數值依然保留
int32
,uint32
,int64
,uint64
, 和bool
是相互兼容的,這意味着可以將其中一種類型任意改編為另外一種類型而不會產生任何問題sint32
和sint64
是相互兼容的string
和bytes
是相互兼容的fixed32
兼容sfixed32
,fixed64
兼容sfixed64
.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編譯器會對每一個字段生成一些get
和set
方法,這些方法的名稱采用標識符所有小寫加上相應的前綴或后綴組成.生成一個值為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; }
自己編了一個股票監控軟件,有如下功能,有興趣的朋友可以下載;
(1) 個股監測。監測個股實時變化,可以監測個股大單交易、急速拉升和下降、主力入場和出場、股票最高點和最低點提醒。檢測到最高點、最低點、主力進場點、主力退場點、急速拉升點、急速下跌點,給出語音或者聲音提醒,不用再時刻看着大盤了,給你更多自由的時間;
(2) 大盤監測。監測大盤的走勢,采用上證、深證、創業三大指數的綜合指數作為大盤走勢。並實時監測大盤的最高點和最低點、中間的轉折點。
(3) 股票推薦。還能根據歷史數據長期或短期走勢進行分析,對股市3千多個股票進行分析對比,選出漲勢良好的股票,按照增長速度從大到小排序,推薦給你漲勢良好的股票;
下載地址:
1.0.3版本(修復大盤指數崩潰缺陷)下載地址:
鏈接:https://pan.baidu.com/s/1BJcTp-kdniM7VE9K5Kd3vg 提取碼:003h
更新鏈接:
https://www.cnblogs.com/bclshuai/p/10621613.html