我們項目中使用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ù)類型時更多的是需要考慮與舊版本的兼容性問題:
- 不要改變?nèi)魏我汛嬖谧侄蔚腡ag值,如果改變Tag值可能會導(dǎo)致數(shù)值類型不匹配,具體原因參加protocol編碼
- 建議使用
optional
和repeated
字段限制,盡可能的減少required
的使用. - 不需要的字段可以刪除,刪除字段的Tag不應(yīng)該在新的消息定義中使用.
- 不需要的字段可以轉(zhuǎn)換為擴展,反之亦然只要類型和數(shù)值依然保留
-
int32
,uint32
,int64
,uint64
, 和bool
是相互兼容的,這意味著可以將其中一種類型任意改編為另外一種類型而不會產(chǎn)生任何問題 -
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): 可以設(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編譯器會對每一個字段生成一些get
和set
方法,這些方法的名稱采用標(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;
}