咱们项目中使用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; }