Protobuf使用手册

Posted langqi250

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Protobuf使用手册相关的知识,希望对你有一定的参考价值。

Protobuf使用手册

第1章 定义.proto 文件

首先我们需要编写一个 proto 文件,定义我们程序中需要处理的结构化数据,在 protobuf 的术语中,结构化数据被称为 Message。proto 文件非常类似 java 或者 C 语言的数据定义,可以使用C或C++风格的注释。下面是一个proto文件的例子。

package tutorial;

 

option java_package = "com.example.tutorial";

option java_outer_classname = "AddressBookProtos";

 

message Person {

required string name = 1;

required int32 id = 2;        // Unique ID number for this person.

optional string email = 3;

 

enum PhoneType {

  MOBILE = 0;

  HOME = 1;

  WORK = 2;

}

 

message PhoneNumber {

  required string number = 1;

  optional PhoneType type = 2 [default = HOME];

}

 

repeated PhoneNumber phone = 4;

}

 

// Our address book file is just one of these.

message AddressBook {

repeated Person person = 1;

}

一个proto文件主要包含package定义、message定义和属性定义三个部分,还有一些可选项。

1.1 定义package

Package在c++中对应namespace。

对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package。

1.2 定义message

Message在C++中对应class。Message中定义的全部属性在class中全部为private的。

Message的嵌套使用可以嵌套定义,也可以采用先定义再使用的方式。

Message的定义末尾可以采用java方式在不加“;”,也可以采用C++定义方式在末尾加上“;”,这两种方式都兼容,建议采用java定义方式。

向.proto文件添加注释,可以使用C/C++/java风格的双斜杠(//) 语法格式。

1.3 定义属性

属性定义分为四部分:标注+类型+属性名+属性顺序号+[默认值],其示意如下所示。

标注

类型

属性名

属性顺序号

[默认值]

required

string

name

= 1

[default=””];

       其中属性名与C++和java语言类似,不再解释;下面分别对标注、类型和属性顺序号加以详细介绍。

其中包名和消息名以及其中变量名均采用java的命名规则——驼峰式命名法,驼峰式命名法规则见附件1。

1.3.1 标注

标注包括“required”、“optional”、“repeated”三种,其中

required表示该属性为必选属性,否则对应的message“未初始化”,debug模式下导致断言,release模式下解析失败;

optional表示该属性为可选属性,不指定,使用默认值(int或者char数据类型默认为0,string默认为空,bool默认为false,嵌套message默认为构造,枚举则为第一个)

repeated表示该属性为重复字段,可看作是动态数组,类似于C++中的vector。

如果为optional属性,发送端没有包含该属性,则接收端在解析式采用默认值。对于默认值,如果已设置默认值,则采用默认值,如果未设置,则类型特定的默认值为使用,例如string的默认值为””。

1.3.2 类型

Protobuf的属性基本包含了c++需要的所有基本属性类型。

protobuf属性

C++属性

java属性

备注

double

double

double

固定8个字节

float

float

float

固定4个字节

int32

int32

int32

使用变长编码,对于负数编码效率较低,如果经常使用负数,建议使用sint32

int64

int64

int64

使用变长编码,对于负数编码效率较低,如果经常使用负数,建议使用sint64

uint32

uint32

int

使用变长编码

uint64

uint64

long

使用变长编码

sint32

int32

int

采用zigzag压缩,对负数编码效率比int32高

sint64

int64

long

采用zigzag压缩,对负数编码效率比int64高

fixed32

uint32

int

总是4字节,如果数据>2^28,编码效率高于unit32

fixed64

uint64

long

总是8字节,如果数据>2^56,编码效率高于unit32

sfixed32

int32

int

总是4字节

sfixed64

int64

long

总是8字节

bool

bool

boolean

 

string

string

String

一个字符串必须是utf-8编码或者7-bit的ascii编码的文本

bytes

string

ByteString

可能包含任意顺序的字节数据

1.3.2.1 Union类型定义

Protobuf没有提供union类型,如果希望使用union类型,可以采用enum和optional属性定义的方式。

例如,如果已经定义了Foo、Bar、Baz等message,则可以采用如下定义。

message OneMessage {
  enum Type { FOO = 1; BAR = 2; BAZ = 3; }
 
  // Identifies which field is filled in.
  required Type type = 1;
 
  // One of the following will be filled in.
  optional Foo foo = 2;
  optional Bar bar = 3;
  optional Baz baz = 4;

}

 

1.3.3 属性顺序号

属性顺序号是protobuf为了提高数据的压缩和可选性等功能定义的,需要按照顺序进行定义,且不允许有重复。

1.4 其它可选项

Protocol Buffer允许我们在.proto文件中定义一些常用的选项,这样可以指示Protocol Buffer编译器帮助我们生成更为匹配的目标语言代码。Protocol Buffer内置的选项被分为以下三个级别:

  1. 文件级别,这样的选项将影响当前文件中定义的所有消息和枚举。
  2. 消息级别,这样的选项仅影响某个消息及其包含的所有字段。
  3. 字段级别,这样的选项仅仅响应与其相关的字段。

1.4.1 java_package可选项

java_package (file option): 是文件级别的选项,表明生成java类所在的包。如果在.proto文件中没有明确的声明java_package,就采用默认的包名。当然了,默认方式产生的 java包名并不是最好的方式,按照应用名称倒序方式进行排序的。如果不需要产生java代码,则该选项将不起任何作用。与此同时,生成的Java文件也将会自动存放到指定输出目录下的com/example/foo子目录中。如:

option java_package = "com.example.foo";

1.4.2 java_outer_classname可选项

java_outer_classname (file option): 是文件级别的选项,表明想要生成Java类的名称。如果在.proto文件中没有明确的java_outer_classname定义,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(foo_bar.proto生成的java类名为FooBar.java),如果不生成java代码,则该选项不起任何作用。如:

option java_outer_classname = "Ponycopter";

注:主要是因为Java中要求同一个.java文件中只能包含一个Java外部类或外部接口,而C++则不存在此限制。因此在.proto文件中定义的消息均为指定外部类的内部类,这样才能将这些消息生成到同一个Java文件中。在实际的使用中,为了避免总是输入该外部类限定符,可以将该外部类静态引入到当前Java文件中,如:import static com.company.project.LYPhoneMessage.*。

 

1.4.3 *_generic_services可选项

cc_generic_services, java_generic_services, py_generic_services (file options): 在C++、java、python中protocol buffer编译器是否应该基于服务定义产生抽象服务代码。由于历史遗留问题,该值默认是true。但是自2.3.0版本以来,它被认为通过提供代码生成 器插件来对RPC实现更可取,而不是依赖于“抽象”服务。

// This file relies on plugins to generate service code.

option cc_generic_services = false;

option java_generic_services = false;

option py_generic_services = false;

1.4.4 message_set_wire_format可选项

message_set_wire_format (message option):如果该值被设置为true,该消息将使用一种不同的二进制格式来与Google内部的MessageSet的老格式相兼容。对于Google外部的用户来说,该选项将不会被用到。如下所示:

message Foo {

  option message_set_wire_format = true;

  extensions 4 to max;

}

1.4.5 import可选项

Import可选项用于包含其它proto文件中定义的message或enum类型等。标准格式如下

import “phonetype.proto”;

使用时,import的文件必须与当前文件处于同一个文件夹下,protoc无法完成不处于同一个文件夹下的import选项。

1.4.6 optimize_for

optimize_for (fileoption): 是文件级别的选项,可以被设置为 SPEED, CODE_SIZE,or LITE_RUNTIME,缺省情况下是SPEED。这些值将通过如下的方式影响C++及java代码的生成:

SPEED (default): protocol buffer编译器将通过在消息类型上执行序列化、语法分析及其他通用的操作。这种代码是最优的。

CODE_SIZE: protocol buffer编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作。采用该方式产生的代码将比SPEED要少得多, 但是操作要相对慢些。当然实现的类及其对外的API与SPEED模式都是一样的。这种方式经常用在一些包含大量的.proto文件而且并不盲目追求速度的 应用中。

LITE_RUNTIME: protocol buffer编译器依赖于运行时核心类库来生成代码(即采用libprotobuf-lite 替代libprotobuf)。这种核心类库由于忽略了一 些描述符及反射,要比全类库小得多。这种模式经常在移动手机平台应用多一些。编译器采用该模式产生的方法实现与SPEED模式不相上下,产生的类通过实现 MessageLite接口,但它仅仅是Messager接口的一个子集。

option optimize_for = CODE_SIZE;

1.4.7 packed

packed (field option): 如果该选项在一个整型基本类型上被设置为真,则采用更紧凑的编码方式。当然使用该值并不会对数值造成任何损失。在2.3.0版本之前,解析器将会忽略那些 非期望的包装值。因此,它不可能在不破坏现有框架的兼容性上而改变压缩格式。在2.3.0之后,这种改变将是安全的,解析器能够接受上述两种格式,但是在 处理protobuf老版本程序时,还是要多留意一下。

repeated int32 samples = 4 [packed=true];

 

 

1.4.8 default

[default = default_value]: optional类型的字段,如果在序列化时没有被设置,或者是老版本的消息中根本不存在该字段,那么在反序列化该类型的消息是,optional的字段将被赋予类型相关的缺省值,如bool被设置为false,int32被设置为0。Protocol Buffer也支持自定义的缺省值,如:

  optional int32 result_per_page = 3 [default = 10]

1.5 大数据量使用建议

在使用过程中发现,对于大数据量的协议报文(循环超过10000条),如果repeated修饰的属性为对象类型(诸如message 、Bytes、string等称为“对象类型”,其余的诸如int32、int64、float等等称为“原始类型”)时,效率非常低,而且占用的进程内存也非常大,建议采用如下方式优化。

1.5.1 repeated message类型

在message 中对repeated 标识的 message类型的字段需要做大量ADD操作时,可以考虑尽量避免嵌套message或者减少嵌套的message个数。

实例如下所示:

 

 

 

 

 

->

 

按行存取

按列存取

1.5.2 repeated raw类型

在message中对repeated 标识的原始数据类型的字段需要做大量ADD操作(例如超过3千)时,可以考虑预分配数据空间,避免重复大量地分配空间。

实例如下所示:

 

 

 

->

 

未预分配空间

预分配空间

 

1.5.3 repeated Bytes类型

在protobuf中,Bytes基于C++ STL中的string实现,因为string内存管理的原因,程序空间往往较大。所以应用如果有很多repeated Bytes类型的字段的话,进程显示耗用大量内存,这与vector<string>的情况基本一致。

1.6 Protocol Buffer消息升级原则

      在实际的开发中会存在这样一种应用场景,既消息格式因为某些需求的变化而不得不进行必要的升级,但是有些使用原有消息格式的应用程序暂时又不能被立刻升级,这便要求我们在升级消息格式时要遵守一定的规则,从而可以保证基于新老消息格式的新老程序同时运行。规则如下:

      1. 不要修改已经存在字段的标签号。

      2. 任何新添加的字段必须是optional和repeated限定符,否则无法保证新老程序在互相传递消息时的消息兼容性。

      3. 在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用。

      4. int32、uint32、int64、uint64和bool等类型之间是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着如果想修改原有字段的类型时,为了保证兼容性,只能将其修改为与其原有类型兼容的类型,否则就将打破新老消息格式的兼容性。

      5. optional和repeated限定符也是相互兼容的。

第2章 编译 .proto 文件

可以通过定义好的.proto文件来生成Java、Python、C++代码,需要基于.proto文件运行protocol buffer编译器protoc。运行的命令如下所示:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto

MPORT_PATH声明了一个.proto文件所在的具体目录。如果忽略该值,则使用当前目录。如果有多个目录则可以 对--proto_path 写多次,它们将会顺序的被访问并执行导入。-I=IMPORT_PATH是它的简化形式。

当然也可以提供一个或多个输出路径:

--cpp_out 在目标目录DST_DIR中产生C++代码,可以在 http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference /cpp-generated.html中查看更多。

--java_out 在目标目录DST_DIR中产生Java代码,可以在 http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference /java-generated.html中查看更多。

--python_out 在目标目录 DST_DIR 中产生Python代码,可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers /docs/reference/python-generated.html中查看更多。

作为一种额外的使得,如果DST_DIR 是以.zip或.jar结尾的,编译器将输出结果打包成一个zip格式的归档文件。.jar将会输出一个 Java JAR声明必须的manifest文件。注:如果该输出归档文件已经存在,它将会被重写,编译器并没有做到足够的智能来为已经存在的归档文件添加新的文 件。

你必须提供一个或多个.proto文件作为输入。多个.proto文件能够一次全部声明。虽然这些文件是相对于当前目录来命名的,每个文件必须在一个IMPORT_PATH中,只有如此编译器才可以决定它的标准名称。

第3章 使用message

3.1 类成员变量的访问

在生成的.h文件中定义了类成员的访问方法。例如,对于Person类,定义了name、id、email、phone等成员的访问方法。

获取成员变量值直接采用使用成员变量名(全部为小写),设置成员变量值,使用在成员变量名前加set_的方法。

对于普通成员变量(required和optional)提供has_方法判断变量值是否被设置;提供clear_方法清除设置的变量值。

对于string类型,提供多种set_方法,其参数不同。同时,提供了一个mutable_方法,返回变量值的可修改指针。

对于repeated变量,提供了其它一些特殊的方法:

l   _size方法:返回repeated field’s

l   通过下脚标访问其中的数组成员组

l   通过下脚标返回其中的成员的mutable_的方法

l   _add方法:增加一个成员。

 

// name

inlinebool has_name()const;

inlinevoid clear_name();

inlineconst::std::string& name()const;

inlinevoid set_name(const::std::string& value);

inlinevoid set_name(constchar* value);

inline::std::string* mutable_name();

 

// id

inlinebool has_id()const;

inlinevoid clear_id();

inline int32_t id()const;

inlinevoid set_id(int32_t value);

 

// email

inlinebool has_email()const;

inlinevoid clear_email();

inlineconst::std::string& email()const;

inlinevoid set_email(const::std::string& value);

inlinevoid set_email(constchar* value);

inline::std::string* mutable_email();

 

// phone

inlineint phone_size()const;

inlinevoid clear_phone();

inlineconst::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phone() const;

inline::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phone();

inlineconst::tutorial::Person_PhoneNumber& phone(int index)const;

inline::tutorial::Person_PhoneNumber* mutable_phone(int index);

inline::tutorial::Person_PhoneNumber* add_phone();

3.2 标准message方法

生成的.h文件中的class都继承自::google::protobuf::Message类,Message类提供了一些方法可以检查或者操作整个message,包括

l   bool IsInitialized() const;检查是否所有required变量都已经初始化;      

l   string DebugString() const;返回message的可阅读的表示,主要用于调试程序;

l   void CopyFrom(const Person& from);使用一个message的值覆盖本message;

l   void Clear();清空message的所有成员变量值。

3.3 编码和解码函数

每个message类都提供了写入和读取message数据的方法,包括

l   bool SerializeToString(string* output) const;把message编码进output。 

l   bool ParseFromString(const string& data);从string解码到message

l   bool SerializeToArray(char* buf,int size) const;把message编码进数组buf.

l   bool ParseFromArray(const char* buf,int size);把buf解码到message。此解码方法效率较ParseFromString高很多,所以一般用这种方法解码。

l   bool SerializeToOstream(ostream* output) const;把message编码进ostream

l   bool ParseFromIstream(istream* input);从istream解码到message

备注:发送接收端所使用的加码解码方法不一定非得配对,即发送端用SerializeToString 接收端不一定非得用ParseFromString ,可以使用其他解码方法。

3.4 简单message生成的C++代码

这里先定义一个最简单的message,其中只是包含原始类型的字段。

option optimize_for = LITE_RUNTIME;

      message LogonReqMessage {

          required int64 acctID = 1;

          required string passwd = 2;

      }

由于我们在MyMessage文件中定义选项optimize_for的值为LITE_RUNTIME,因此由该.proto文件生成的所有C++类的父类均为::google::protobuf::MessageLite,而非::google::protobuf::Message。MessageLite类是Message的父类,在MessageLite中将缺少Protocol Buffer对反射的支持,而此类功能均在Message类中提供了具体的实现。使用LITE版本的Protocol Buffer。这样不仅可以得到更高编码效率,而且生成代码编译后所占用的资源也会更少,至于反射所能带来的灵活性和极易扩展性。下面我们来看一下由message LogonReqMessage生成的C++类的部分声明,以及常用方法的说明性注释。

class LogonReqMessage : public ::google::protobuf::MessageLite {

    public:

        LogonReqMessage();

        virtual ~LogonReqMessage();

 

        // implements Message ----------------------------------------------

        //下面的成员函数均实现自MessageLite中的虚函数。

        //创建一个新的LogonReqMessage对象,等同于clone

        LogonReqMessage* New() const;

        //用另外一个LogonReqMessage对象初始化当前对象,等同于赋值操作符重载(operator=

        void CopyFrom(const LogonReqMessage& from);

        //清空当前对象中的所有数据,既将所有成员变量置为未初始化状态。

        void Clear();

        //判断当前状态是否已经初始化。

        bool IsInitialized() const;

        //在给当前对象的所有变量赋值之后,获取该对象序列化后所需要的字节数。

        int ByteSize() const;

        //获取当前对象的类型名称。

        ::std::string GetTypeName() const;

 

        // required int64 acctID = 1;

        //下面的成员函数都是因message中定义的acctID字段而生成。

        //这个静态成员表示AcctID的标签值。命名规则是k + FieldName(驼峰规则) + FieldNumber

        static const int kAcctIDFieldNumber = 1;

        //如果acctID字段已经被设置返回true,否则false

        inline bool has_acctid() const;

        //执行该函数后has_acctid函数将返回false,而下面的acctid函数则返回acctID的缺省值。

        inline void clear_acctid();

        //返回acctid字段的当前值,如果没有设置则返回int64类型的缺省值。

        inline ::google::protobuf::int64 acctid() const;

        //acctid字段设置新值,调用该函数后has_acctid函数将返回true

        inline void set_acctid(::google::protobuf::int64 value);

   

        // required string passwd = 2;

        //下面的成员函数都是因message中定义的passwd字段而生成。这里生成的函数和上面acctid

        //生成的那组函数基本相似。因此这里只是列出差异部分。

        static const int kPasswdFieldNumber = 2;

        inline bool has_passwd() const;

        inline void clear_passwd();

        inline const ::std::string& passwd() const;

        inline void set_passwd(const ::std::string& value);

        //对于字符串类型字段设置const char*类型的变量值。

        inline void set_passwd(const char* value);

        inline void set_passwd(const char* value, size_t size);

        //可以通过返回值直接给passwd对象赋值。在调用该函数之后has_passwd将返回true

        inline ::std::string* mutable_passwd();

        //释放当前对象对passwd字段的所有权,同时返回passwd字段对象指针。调用此函数之后,passwd字段对象

        //的所有权将移交给调用者。此后再调用has_passwd函数时将返回false

        inline ::std::string* release_passwd();

    private:

        ... ...

    };

下面是读写LogonReqMessage对象的C++测试代码和说明性注释。

void testSimpleMessage()

    {

        printf("==================This is simple message.================\\n");

        //序列化LogonReqMessage对象到指定的内存区域。

        LogonReqMessage logonReq;

        logonReq.set_acctid(20);

        logonReq.set_passwd("Hello World");

        //提前获取对象序列化所占用的空间并进行一次性分配,从而避免多次分配

        //而造成的性能开销。通过该种方式,还可以将序列化后的数据进行加密。

        //之后再进行持久化,或是发送到远端。

        int length = logonReq.ByteSize();

        char* buf = new char[length];

        logonReq.SerializeToArray(buf,length);

        //从内存中读取并反序列化LogonReqMessage对象,同时将结果打印出来。

        LogonReqMessage logonReq2;

        logonReq2.ParseFromArray(buf,length);

        printf("acctID = %I64d, password = %s\\n",logonReq2.acctid(),logonReq2.passwd().c_str());

        delete [] buf;

    }

3.5 嵌套message生成的C++代码

enum UserStatus {

          OFFLINE = 0;

          ONLINE = 1;

      }

      enum LoginResult {

          LOGON_RESULT_SUCCESS = 0;

          LOGON_RESULT_NOTEXIST = 1;

          LOGON_RESULT_ERROR_PASSWD = 2;

          LOGON_RESULT_ALREADY_LOGON = 3;

          LOGON_RESULT_SERVER_ERROR = 4;

      }

      message UserInfo {

          required int64 acctID = 1;

          required string name = 2;

          required UserStatus status = 3;

      }

      message LogonRespMessage {

          required LoginResult logonResult = 1;

          required UserInfo userInfo = 2; //这里嵌套了UserInfo消息。

      }

对于上述消息生成的C++代码,UserInfo因为只是包含了原始类型字段,因此和上例中的LogonReqMessage没有太多的差别,这里也就不在重复列出了。由于LogonRespMessage消息中嵌套了UserInfo类型的字段,在这里我们将仅仅给出该消息生成的C++代码和关键性注释。

class LogonRespMessage : public ::google::protobuf::MessageLite {

    public:

        LogonRespMessage();

        virtual ~LogonRespMessage();

   

        // implements Message ----------------------------------------------

        ... ... //这部分函数和之前的例子一样。

       

        // required .LoginResult logonResult = 1;

        //下面的成员函数都是因message中定义的logonResult字段而生成。

        //这一点和前面的例子基本相同,只是类型换做了枚举类型LoginResult   

        static const int kLogonResultFieldNumber = 1;

        inline bool has_logonresult() const;

        inline void clear_logonresult();

        inline LoginResult logonresult() const;

        inline void set_logonresult(LoginResult value);

       

        // required .UserInfo userInfo = 2;

        //下面的成员函数都是因message中定义的UserInfo字段而生成。

        //这里只是列出和非消息类型字段差异的部分。

        static const int kUserInfoFieldNumber = 2;

        inline bool has_userinfo() const;

        inline void clear_userinfo();

        inline const ::UserInfo& userinfo() const;

        //可以看到该类并没有生成用于设置和修改userInfo字段set_userinfo函数,而是将该工作

        //交给了下面的mutable_userinfo函数。因此每当调用函数之后,Protocol Buffer都会认为

        //该字段的值已经被设置了,同时has_userinfo函数亦将返回true。在实际编码中,我们可以

        //通过该函数返回userInfo字段的内部指针,并基于该指针完成userInfo成员变量的初始化工作。

        inline ::UserInfo* mutable_userinfo();

        inline ::UserInfo* release_userinfo();

    private:

        ... ...

    };

下面是读写LogonRespMessage对象的C++测试代码和说明性注释。

void testNestedMessage()

    {

        printf("==================This is nested message.================\\n");

        LogonRespMessage logonResp;

        logonResp.set_logonresult(LOGON_RESULT_SUCCESS);

        //如上所述,通过mutable_userinfo函数返回userInfo字段的指针,之后再初始化该对象指针。

        UserInfo* userInfo = logonResp.mutable_userinfo();

        userInfo->set_acctid(200);

        userInfo->set_name("Tester");

        userInfo->set_status(OFFLINE);

        int length = logonResp.ByteSize();

        char* buf = new char[length];

        logonResp.SerializeToArray(buf,length);

   

        LogonRespMessage logonResp2;

        logonResp2.ParseFromArray(buf,length);

        printf("LogonResult = %d, UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\\n"

            ,logonResp2.logonresult(),logonResp2.userinfo().acctid(),logonResp2.userinfo().name().c_str(),logonResp2.userinfo().status());

        delete [] buf;

    }

3.6 repeated嵌套message生成的C++代码

message BuddyInfo {

          required UserInfo userInfo = 1;

          required int32 groupID = 2;

      }

      message RetrieveBuddiesResp {

          required int32 buddiesCnt = 1;

          repeated BuddyInfo buddiesInfo = 2;

      }

对于上述消息生成的代码,我们将只是针对RetrieveBuddiesResp消息所对应的C++代码进行详细说明,其余部分和前面小节的例子基本相同,可直接参照。而对于RetrieveBuddiesResp类中的代码,我们也仅仅是对buddiesInfo字段生成的代码进行更为详细的解释。

class RetrieveBuddiesResp : public ::google::protobuf::MessageLite {

    public:

        RetrieveBuddiesResp();

        virtual ~RetrieveBuddiesResp();

 

        ... ... //其余代码的功能性注释均可参照前面的例子。

           

        // repeated .BuddyInfo buddiesInfo = 2;

        static const int kBuddiesInfoFieldNumber = 2;

        //返回数组中成员的数量。

        inline int buddiesinfo_size() const;

        //清空数组中的所有已初始化成员,调用该函数后,buddiesinfo_size函数将返回0

        inline void clear_buddiesinfo();

        //返回数组中指定下标所包含元素的引用。

        inline const ::BuddyInfo& buddiesinfo(int index) const;

        //返回数组中指定下标所包含元素的指针,通过该方式可直接修改元素的值信息。

        inline ::BuddyInfo* mutable_buddiesinfo(int index);

        //像数组中添加一个新元素。返回值即为新增的元素,可直接对其进行初始化。

        inline ::BuddyInfo* add_buddiesinfo();

        //获取buddiesInfo字段所表示的容器,该函数返回的容器仅用于遍历并读取,不能直接修改。

        inline const ::google::protobuf::RepeatedPtrField< ::BuddyInfo >&

          buddiesinfo() const;

        //获取buddiesInfo字段所表示的容器指针,该函数返回的容器指针可用于遍历和直接修改。

        inline ::google::protobuf::RepeatedPtrField< ::BuddyInfo >*

          mutable_buddiesinfo();

    private:

        ... ...

    };

下面是读写RetrieveBuddiesResp对象的C++测试代码和说明性注释。

void testRepeatedMessage()

    {

        printf("==================This is repeated message.================\\n");

        RetrieveBuddiesResp retrieveResp;

        retrieveResp.set_buddiescnt(2);

        BuddyInfo* buddyInfo = retrieveResp.add_buddiesinfo();

        buddyInfo->set_groupid(20);

        UserInfo* userInfo = buddyInfo->mutable_userinfo();

        userInfo->set_acctid(200);

        userInfo->set_name("user1");

        userInfo->set_status(OFFLINE);

   

        buddyInfo = retrieveResp.add_buddiesinfo();

        buddyInfo->set_groupid(21);

        userInfo = buddyInfo->mutable_userinfo();

        userInfo->set_acctid(201);

        userInfo->set_name("user2");

        userInfo->set_status(ONLINE);

   

        int length = retrieveResp.ByteSize();

        char* buf = new char[length];

        retrieveResp.SerializeToArray(buf,length);

   

        RetrieveBuddiesResp retrieveResp2;

        retrieveResp2.ParseFromArray(buf,length);

        printf("BuddiesCount = %d\\n",retrieveResp2.buddiescnt());

        printf("Repeated Size = %d\\n",retrieveResp2.buddiesinfo_size());

        //这里仅提供了通过容器迭代器的方式遍历数组元素的测试代码。

        //事实上,通过buddiesinfo_sizebuddiesinfo函数亦可循环遍历。

        RepeatedPtrField<BuddyInfo>* buddiesInfo = retrieveResp2.mutable_buddiesinfo();

        RepeatedPtrField<BuddyInfo>::iterator it = buddiesInfo->begin();

        for (; it != buddiesInfo->end(); ++it) {

            printf("BuddyInfo->groupID = %d\\n", it->groupid());

            printf("UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\\n"

                , it->userinfo().acctid(), it->userinfo().name().c_str(),it->userinfo().status());

        }

        delete [] buf;

    }

3.7 简单message生成的Java代码

option java_package = "com.lsk.lyphone";

      option java_outer_classname = "LYPhoneMessage";

      option optimize_for = LITE_RUNTIME;

      message LogonReqMessage {

          required int64 acctID = 1;

          required string passwd = 2;

      }

当前.proto文件中该选项的值为LITE_RUNTIME,因此由该.proto文件生成的所有Java类的父类均为com.google.protobuf.GeneratedMessageLite,而非com.google.protobuf.GeneratedMessage,同时与之对应的Builder类则均继承自com.google.protobuf.MessageLiteOrBuilder,而非com.google.protobuf.MessageOrBuilder。使用LITE版本的Protocol Buffer。这样不仅可以得到更高编码效率,而且生成代码编译后所占用的资源也会更少,至于反射所能带来的灵活性和极易扩展性。下面我们来看一下由message LogonReqMessage生成的Java类的部分声明,以及常用方法的说明性注释。

在做各种case的对比性分析之前必须要事先声明的是,Protocol Buffer针对Java语言所生成的代码和C++相比存在一个非常重要的差别,即为每个消息均会生成一个Builder接口和一个与消息对应的实现类,该实现类又将同时实现生成的Builder接口和扩展Protocol Buffer内置的GeneratedMessageLite(或GeneratedMessage)类。这一点对于Protocol Buffer而言,是巧妙的使用了设计模式中的Builder模式。换言之,对于所有消息字段的修改操作均需要通过与其对应的Builder接口辅助完成。相信我们会通过对下面用例的学习可以得到更为清楚的认识。

//用于修改LogonReqMessage消息字段的辅助Builder接口。

    //该接口会为消息中的每个字段均提供gettersetter方法。

    public interface LogonReqMessageOrBuilder

        extends com.google.protobuf.MessageLiteOrBuilder {

   

        // required int64 acctID = 1;

        boolean hasAcctID();

        long getAcctID();

       

        // required string passwd = 2;

        boolean hasPasswd();

        String getPasswd();

    }

    //该类为final类,即不可以在被子类化了。这一点在Protocol Buffer的官方文档中给予了明确

    //的说明,因为子类化将会破坏序列化和反序列化的过程。

    public static final class LogonReqMessage extends

        com.google.protobuf.GeneratedMessageLite

        implements LogonReqMessageOrBuilder {

       

        // Use LogonReqMessage.newBuilder() to construct.

        // 由于所有构造函数均为私有方法,由此可见,我们不能直接new LogonReqMessage的对象

        // 实例,而是只能通过与其对应Builder来构造,或是直接通过反序列化的方式生成。

        private LogonReqMessage(Builder builder) {

            super(builder);

        }

        //该静态方法为该类Builder接口的工厂方法。返回的Builder实现类在完成各个字段的

        //初始化后,通过build()方法返回与其对应的消息实现类,即LogonReqMessage

        public static Builder newBuilder() { return Builder.create(); }

        //通过该类的对象获取与其对应的Builder类对象,一般用于通过Builder类完成消息字段的修改。

       

以上是关于Protobuf使用手册的主要内容,如果未能解决你的问题,请参考以下文章

protobuf 的 C++函数使用手册

protobuf 的 C++函数使用手册

前端开发必备!Emmet使用手册

sublime Text emmet插件使用手册

转载:Emmet使用手册

前端开发必备!Emmet使用手册(转自 http://www.w3cplus.com/tools/emmet-cheat-sheet.html)

(c)2006-2024 SYSTEM All Rights Reserved IT常识