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;
}
相关文章
相关标签/搜索