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.================ ");

        //序列化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 ",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.================ ");

        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 "

            ,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.================ ");

        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 ",retrieveResp2.buddiescnt());

        printf("Repeated Size = %d ",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 ", it->groupid());

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

                , 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类完成消息字段的修改。

        public Builder toBuilder() { return newBuilder(this); }

 

        private LogonReqMessage(boolean noInit) {}

        //判断当前对象的所有字段是否都已经被初始化。

        public final boolean isInitialized() {

            ... ...

        }

        //获取已经被初始化后的对象序列化时所占用的字节空间。

        public int getSerializedSize() {

            ... ...

        }

        //从内存中饭序列化LogonReqMessage对象。

        //Protocol Buffer中还提供其他一些接口方法,用于从不同的数据源反序列化对象。

        public static com.lsk.lyphone.LYPhoneMessage.LogonReqMessage parseFrom(byte[] data)

            throws com.google.protobuf.InvalidProtocolBufferException {

            return newBuilder().mergeFrom(data).buildParsed();

        }

        //功能和上一个函数相同,只是输入源改为InputStream接口。

        public static com.lsk.lyphone.LYPhoneMessage.LogonReqMessage parseFrom(java.io.InputStream input)

            throws java.io.IOException {

            return newBuilder().mergeFrom(input).buildParsed();

        }

       

        // required int64 acctID = 1;

        // 下面的静态变量对应于该字段在.proto中定义的标签号。该变量的命名规则为:字段(全部大写) + _FIELD_NUMBER

        public static final int ACCTID_FIELD_NUMBER = 1;

        public boolean hasAcctID() {

            return ((bitField0_ & 0x00000001) == 0x00000001);

        }

        public long getAcctID() {

            return acctID_;

        }

 

        // required string passwd = 2;

        public static final int PASSWD_FIELD_NUMBER = 2;

        public boolean hasPasswd() {

            return ((bitField0_ & 0x00000002) == 0x00000002);

        }

        public String getPasswd() {

            ... ...

        }

        //每一个Message类都会包含一个静态内部类,即与之对应的Builder类。上面代码中所涉及的Builder类均为该内部类。

        public static final class Builder extends

            com.google.protobuf.GeneratedMessageLite.Builder<

            com.lsk.lyphone.LYPhoneMessage.LogonReqMessage, Builder>

            implements com.lsk.lyphone.LYPhoneMessage.LogonReqMessageOrBuilder {

            //清空当前对象中的所有设置。调用该函数之后,本例中的hasAcctIDhasPasswd都会返回false   

            public Builder clear() {

                super.clear();

                acctID_ = 0L;

                bitField0_ = (bitField0_ & ~0x00000001);

                passwd_ = "";

                bitField0_ = (bitField0_ & ~0x00000002);

                return this;

            }

            //克隆出一个Builder对象。

            public Builder clone() {

                return create().mergeFrom(buildPartial());

            }

            public com.lsk.lyphone.LYPhoneMessage.LogonReqMessage build() {

                com.lsk.lyphone.LYPhoneMessage.LogonReqMessage result = buildPartial();

                if (!result.isInitialized()) {

                    throw newUninitializedMessageException(result);

                }

                return result;

            }

            // Builder类中修改外部消息类的方法。

            // required int64 acctID = 1;

            public boolean hasAcctID() {

                return ((bitField0_ & 0x00000001) == 0x00000001);

            }

            public long getAcctID() {

                return acctID_;

            }

            //设置AcctID字段,该函数调用后hasAcctID函数将返回true

            //这里之所以让返回值为Builder对象,就是可以让调用者在一条代码中方便的连续修改多个字段,

            //如:myMessage.setAcctID(100).setPasswd("MyName");

            public Builder setAcctID(long value) {

                bitField0_ |= 0x00000001;

                acctID_ = value;

                return this;

            }

            //清空AcctID字段,该函数调用后hasAcctID函数返回false

            //这里之所以让返回值为Builder对象,就是可以让调用者在一条代码中方便的连续清空多个字段,

            //如:myMessage.clearAcctID().clearPasswd();

            public Builder clearAcctID() {

                bitField0_ = (bitField0_ & ~0x00000001);

                acctID_ = 0L;

                return this;

            }

     

            // required string passwd = 2;

            public boolean hasPasswd() {

                return ((bitField0_ & 0x00000002) == 0x00000002);

            }

            public String getPasswd() {

                ... ...       

            }

            public Builder setPasswd(String value) {

                ... ...

            }

            public Builder clearPasswd() {

                bitField0_ = (bitField0_ & ~0x00000002);

                passwd_ = getDefaultInstance().getPasswd();

                return this;

            }

            void setPasswd(com.google.protobuf.ByteString value) {

                bitField0_ |= 0x00000002;

                passwd_ = value;

            }

        }

    }

在上面生成的代码中并没有列出与序列化相关的函数,这部分代码基本都是在父类中实现的,我们将在下面的例子中给出一些最基本的用法,有兴趣的开发者可以直接看Protocol Buffer中的源码,这部分代码比较通俗易懂。

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

private static void testSimpleMessage() {

        System.out.println("==================This is simple message.================");

        //如前所述,不能直接构造该消息类对象,只能通过他的内部Builder类构造并完成所有字段的初始化。

        LogonReqMessage.Builder logonReqBuilder = LogonReqMessage.newBuilder();

        logonReqBuilder.setAcctID(20);

        logonReqBuilder.setPasswd("Hello World");

        //builder对象初始化完毕后,再通过build方法生成与之对应的消息类对象。

        LogonReqMessage logonReq = logonReqBuilder.build();

        int length = logonReq.getSerializedSize();

        System.out.println("The result length is " + length);

        //直接序列化到内存中,之后可对该内存进行二次加工后再写到本地文件或发送到远端,如加密。

        byte[] buf = logonReq.toByteArray();

 

        try {

            LogonReqMessage logonReq2 = LogonReqMessage.parseFrom(buf);

            System.out.println("acctID = " + logonReq2.getAcctID() + " password = " + logonReq2.getPasswd());

        } catch (InvalidProtocolBufferException e) {

            e.printStackTrace();

        }

        //需要说明的是,文件中的内容是由之前C++实例代码写入的,这里这样写主要是一种验证。

        System.out.println("Reading data from local file generated by C++");

        try {

            LogonReqMessage logonReq3 = LogonReqMessage.parseFrom(new FileInputStream("C:/Mine/LogonReq.dat"));

            System.out.println("acctID = " + logonReq3.getAcctID() + " password = " + logonReq3.getPasswd());

        } catch (FileNotFoundException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

3.8 嵌套message生成的Java代码

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消息。

      }

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

public static final class LogonRespMessage extends

        com.google.protobuf.GeneratedMessageLite

        implements LogonRespMessageOrBuilder {

       

        //Message类的通用性函数定义。

        ... ...

       

        // required .LoginResult logonResult = 1;

        public static final int LOGONRESULT_FIELD_NUMBER = 1;

        public boolean hasLogonResult() {

            return ((bitField0_ & 0x00000001) == 0x00000001);

        }

        public com.lsk.lyphone.LYPhoneMessage.LoginResult getLogonResult() {

            return logonResult_;

        }

       

        // required .UserInfo userInfo = 2;

        public static final int USERINFO_FIELD_NUMBER = 2;

        public boolean hasUserInfo() {

            return ((bitField0_ & 0x00000002) == 0x00000002);

        }

        public com.lsk.lyphone.LYPhoneMessage.UserInfo getUserInfo() {

            return userInfo_;

        }

        //Message类的通用性函数定义。可参照上一小节中的代码和注释。

        ... ...

       

        public static final class Builder extends

            com.google.protobuf.GeneratedMessageLite.Builder<

            com.lsk.lyphone.LYPhoneMessage.LogonRespMessage, Builder>

            implements com.lsk.lyphone.LYPhoneMessage.LogonRespMessageOrBuilder {

 

            //一些适用于绝大多数Builder对象的通用性方法。

            ... ...

           

            //当前示例中Builder生成的代码和上一小节中生成的代码非常类似,这里就不一一赘述了。

            //和前面的例子相比一个重要的差别是setUserInfo函数多提供了一种函数签名,其参数为

            //UserInfo类的Builder对象。这样调用者在使用时可以直接将Builder对象作为参数传入。

            public Builder setUserInfo(com.lsk.lyphone.LYPhoneMessage.UserInfo.Builder builderForValue) {

                userInfo_ = builderForValue.build();

                bitField0_ |= 0x00000002;

                return this;

            }

        }

    }

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

private static void testNestedMessage() {

        System.out.println("==================This is nested message.================");

        LogonRespMessage.Builder logonRespBuilder = LogonRespMessage.newBuilder();

        logonRespBuilder.setLogonResult(LoginResult.LOGON_RESULT_SUCCESS);

        UserInfo.Builder userInfo = UserInfo.newBuilder();

        userInfo.setAcctID(200);

        userInfo.setName("Tester");

        userInfo.setStatus(UserStatus.OFFLINE);

        //这里也可以直接传递userInfo对象作为参数。因为LogonRespBuilder类提供了setUserInfo的方法重载。

        logonRespBuilder.setUserInfo(userInfo.build());

        LogonRespMessage logonResp = logonRespBuilder.build();

        int length = logonResp.getSerializedSize();

        System.out.println("The result length is " + length);

        byte[] buf = logonResp.toByteArray();

 

        try {

            LogonRespMessage logonResp2 = LogonRespMessage.parseFrom(buf);

            UserInfo userInfo2 = logonResp2.getUserInfo();

            System.out.println("LogonResult = " + logonResp2.getLogonResult().toString() + " acctID = "

                    + userInfo2.getAcctID() + " name = " + userInfo2.getName() + " status = " + userInfo2.getStatus().toString());

        } catch (InvalidProtocolBufferException e) {

            e.printStackTrace();

        }

        System.out.println("Reading data from local file generated by C++");

        try {

            LogonRespMessage logonResp3 = LogonRespMessage.parseFrom(new FileInputStream("C:/Mine/LogonResp.dat"));

            UserInfo userInfo3 = logonResp3.getUserInfo();

            System.out.println("LogonResult = " + logonResp3.getLogonResult().toString() + " acctID = "

                    + userInfo3.getAcctID() + " name = " + userInfo3.getName() + " status = " + userInfo3.getStatus().toString());

        } catch (FileNotFoundException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

3.9 repeated嵌套message生成的Java代码

message BuddyInfo {

          required UserInfo userInfo = 1;

          required int32 groupID = 2;

      }

      message RetrieveBuddiesResp {

          required int32 buddiesCnt = 1;

          repeated BuddyInfo buddiesInfo = 2;

      }

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

public static final class RetrieveBuddiesResp extends

        com.google.protobuf.GeneratedMessageLite

        implements RetrieveBuddiesRespOrBuilder {

        //这里均为Protocol Buffer生成的通用性代码。

        ... ...

        // repeated .BuddyInfo buddiesInfo = 2;

        public static final int BUDDIESINFO_FIELD_NUMBER = 2;

        //对于repeated类型的字段,均返回类型参数为字段类型的泛型容器对象。

        public java.util.List<com.lsk.lyphone.LYPhoneMessage.BuddyInfo> getBuddiesInfoList() {

            return buddiesInfo_;

        }

        public java.util.List<? extends com.lsk.lyphone.LYPhoneMessage.BuddyInfoOrBuilder> getBuddiesInfoOrBuilderList() {

            return buddiesInfo_;

        }

        public int getBuddiesInfoCount() {

            return buddiesInfo_.size();

        }

        public com.lsk.lyphone.LYPhoneMessage.BuddyInfo getBuddiesInfo(int index) {

            return buddiesInfo_.get(index);

        }

        public com.lsk.lyphone.LYPhoneMessage.BuddyInfoOrBuilder getBuddiesInfoOrBuilder(int index) {

            return buddiesInfo_.get(index);

        }

       

        //这里仍有一些Protocol Buffer生成的通用性代码。

        ... ...

       

        public static final class Builder extends

            com.google.protobuf.GeneratedMessageLite.Builder<

            com.lsk.lyphone.LYPhoneMessage.RetrieveBuddiesResp, Builder>

            implements com.lsk.lyphone.LYPhoneMessage.RetrieveBuddiesRespOrBuilder {

           

            //这里仅列出和操作repeated字段相关的方法,其他的方法和前面的例子基本一致。

            // repeated .BuddyInfo buddiesInfo = 2;

            //本来打算给出比较详细的说明,但是看到Google为每个函数的命名之后就放弃这个想法,

            //这样一来不仅可以避免画蛇添足,而且也节省了时间。:)           

            public java.util.List<com.lsk.lyphone.LYPhoneMessage.BuddyInfo> getBuddiesInfoList() {

                return java.util.Collections.unmodifiableList(buddiesInfo_);

            }

            public int getBuddiesInfoCount() {

                return buddiesInfo_.size();

            }

            public com.lsk.lyphone.LYPhoneMessage.BuddyInfo getBuddiesInfo(int index) {

                return buddiesInfo_.get(index);

            }

            public Builder setBuddiesInfo(int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo value) {

                ... ...

            }

            public Builder setBuddiesInfo(int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo.Builder builderForValue) {

                ... ...

            }

            public Builder addBuddiesInfo(com.lsk.lyphone.LYPhoneMessage.BuddyInfo value) {

                ... ...

            }

            public Builder addBuddiesInfo(int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo value) {

                ... ...

            }

            public Builder addBuddiesInfo(com.lsk.lyphone.LYPhoneMessage.BuddyInfo.Builder builderForValue) {

                ... ...

            }

            public Builder addBuddiesInfo(

                int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo.Builder builderForValue) {

                ... ...

            }

            public Builder addAllBuddiesInfo(

                java.lang.Iterable<? extends com.lsk.lyphone.LYPhoneMessage.BuddyInfo> values) {

                ... ...

            }

            public Builder clearBuddiesInfo() {

                ... ...

            }

            public Builder removeBuddiesInfo(int index) {

                ... ...

            }

        }

    }

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

private static void testRepeatedMessage() {

        System.out.println("==================This is repeated message.================");

        RetrieveBuddiesResp.Builder retrieveBuddiesBuilder = RetrieveBuddiesResp.newBuilder();

        retrieveBuddiesBuilder.setBuddiesCnt(2);

        BuddyInfo.Builder buddyInfoBuilder = BuddyInfo.newBuilder();

        buddyInfoBuilder.setGroupID(20);

        UserInfo.Builder userInfoBuilder = UserInfo.newBuilder();

        userInfoBuilder.setAcctID(200);

        userInfoBuilder.setName("user1");

        userInfoBuilder.setStatus(UserStatus.OFFLINE);

        buddyInfoBuilder.setUserInfo(userInfoBuilder.build());

        retrieveBuddiesBuilder.addBuddiesInfo(buddyInfoBuilder.build());

       

        buddyInfoBuilder = BuddyInfo.newBuilder();

        buddyInfoBuilder.setGroupID(21);

        userInfoBuilder = UserInfo.newBuilder();

        userInfoBuilder.setAcctID(201);

        userInfoBuilder.setName("user2");

        userInfoBuilder.setStatus(UserStatus.ONLINE);

        buddyInfoBuilder.setUserInfo(userInfoBuilder);

        retrieveBuddiesBuilder.addBuddiesInfo(buddyInfoBuilder);

        RetrieveBuddiesResp buddiesResp = retrieveBuddiesBuilder.build();

       

        int length = buddiesResp.getSerializedSize();

        System.out.println("The result length is " + length);

        byte[] buf = buddiesResp.toByteArray();

       

        try {

            RetrieveBuddiesResp buddiesResp2 = RetrieveBuddiesResp.parseFrom(buf);

            System.out.println("BuddiesCount = " + buddiesResp2.getBuddiesCnt());

            System.out.println("Repeated Size = " + buddiesResp2.getBuddiesInfoCount());

            for (int i = 0; i < buddiesResp2.getBuddiesInfoCount(); ++i) {

                BuddyInfo buddyInfo = buddiesResp2.getBuddiesInfo(i);

                UserInfo userInfo = buddyInfo.getUserInfo();

                System.out.println("GroupID = " + buddyInfo.getGroupID() + " UserInfo.acctID = " + userInfo.getAcctID()

                        + " UserInfo.name = " + userInfo.getName() + " UserInfo.status = " + userInfo.getStatus());

            }

           

        } catch (InvalidProtocolBufferException e) {

            e.printStackTrace();

        }

        System.out.println("Reading data from local file generated by C++");

        try {

            RetrieveBuddiesResp buddiesResp3 = RetrieveBuddiesResp.parseFrom(new FileInputStream("C:/Mine/RetrieveBuddiesResp.dat"));

            System.out.println("BuddiesCount = " + buddiesResp3.getBuddiesCnt());

            System.out.println("Repeated Size = " + buddiesResp3.getBuddiesInfoCount());

            List<BuddyInfo> buddiesInfo = buddiesResp3.getBuddiesInfoList();

            for (BuddyInfo buddyInfo : buddiesInfo) {

                UserInfo userInfo = buddyInfo.getUserInfo();

                System.out.println("GroupID = " + buddyInfo.getGroupID() + " UserInfo.acctID = " + userInfo.getAcctID()

                        + " UserInfo.name = " + userInfo.getName() + " UserInfo.status = " + userInfo.getStatus());

            }

        } catch (FileNotFoundException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

 

3.10 写入message

#include"addressbook.pb.h"

#include<iostream>

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<stdio.h>

#include<string.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#include<stdlib.h>

#include<string>

using namespace std;

{

//填充消息

void PromptInput(tutorial::Person* person){

         person->set_id(2008);

         person->set_name("Joe");

         person->set_email("joepayne@163.com");

         int i=0;

         while(i<3){

                   tutorial::Person::PhoneNumber* phone_number=person->add_phone();

                   phone_number->set_number("13051889399");

                   phone_number->set_type(tutorial::Person::MOBILE);

                   i++;

         }

}

}

int main(){

{

//建立SOCKET连接

         }

         int n=0;

         tutorial::AddressBook msg1;

         PromptInput(msg1.add_person());

         string s;

         int uSize;

         {

     //发送1个消息包

         while(n<1){

    //两次发送过程

         {

         msg1.SerializeToString(&s);

//把消息包的长度发送出去

         usize=s.size();

         write(sockfd,&uSize,sizeof(uSize));

    //把消息包发送出去

    //注意如果消息数据比较大的话,一次IO写操作不能写完,那就需要自己另作处理了

             write(sockfd,s.data(),s.size());

         }

        //输出包的大小

        cout<<"Message size:"<<uSize<<endl;

                   n++;

         }

                   return 0;             

}

 

3.11 读出message

#include"addressbook.pb.h"

#include<iostream>

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<stdio.h>

#include<string.h>

#include<stdlib.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#include<stdlib.h>

using namespace std;

//列出消息中的所有数据

void List(const tutorial::AddressBook& address_book){

         for(int i=0;i<address_book.person_size();i++){

                   const tutorial::Person& person=address_book.person(i);

                   cout<<"Person ID:"<<person.id()<<endl;

                   cout<<"Name:"<<person.name()<<endl;

                   if(person.has_email()){

                            cout<<"E-mail address:"<<person.email()<<endl;

                   }

                   for(int j=0;j<person.phone_size();j++){

                            const tutorial::Person::PhoneNumber& phone_number=person.phone(j);

                            switch(phone_number.type()){

                                     case tutorial::Person::MOBILE:

                                     cout<<"Mobile phone:";

                                     break;

                                     case tutorial::Person::HOME:

                                     cout<<"Home phone:";

                                     break;

                                     case tutorial::Person::WORK:

                                     cout<<"WOrk Phone:";

                                     break;

                            }

                            cout<<phone_number.number()<<endl;

                   }

         }

}

 

int main(){

{

//建立连接

}

         tutorial::AddressBook msg2;

         int uLen;

         char* uData;

         int nRead;

{

//读取数据

//先读取消息包的长度信息

         while((nRead=read(connfd,&uLen,sizeof(uLen)))>0){

                   cout<<"The length of the message is:"<<uLen<<endl;

          //根据消息包的长度信息,分配适当大小的缓冲区来接收消息包数据

                   uData=(char*)malloc(uLen);

         //注意此次read可能一次性读不完一个包,如果包比较大的话,这样的话就得自己再做进一步处理

                   read(connfd,uData,uLen);

         //解码操作,从缓冲区中解到msg2消息空间中去,此处用ParseFromArray 而没有使用ParseFromString 因为前者的效率相对于后者效率要高很多。

                   if(!msg2.ParseFromArray(uData,uLen)){

    cout<<”Parse failed!”<<endl;

    return -1;

}

                   free(uData);

                   uData=NULL;

         //输出消息的数据信息

                   List(msg2);

         }

  }

return 0;             

}

附件1:驼峰命名法

驼峰命名法就是当变量名或函式名是由一个或多个单字连结在一起,而构成的唯一识别字时,第一个单字以小写字母开始;

第二个单字的首字母大写或每一个单字的首字母都采用大写字母,例如:myFirstName、myLastName,这样的变量名看上去就像骆驼峰一样此起彼伏,故得名。

驼峰命名法(Camel-Case)一词来自 Perl 语言中普遍使用的大小写混合格式,而 Larry Wall 等人所著的畅销书《Programming Perl》(O'Reilly 出版)的封面图片正是一匹骆驼。

  驼峰命名法的命名规则可视为一种惯例,并无绝对与强制,为的是增加识别和可读性。

数据库:

   表名:首字母大写+驼峰式命名 eg:Article;UserDept

   表中列名:首字母大写+驼峰式命名 eg:UserId;UnitCost

   存储过程命名:表名_首字母大写+驼峰式命名 eg:Admin_UserRegister

项目名称:

   公认简写:全部大写 eg:DAL;BLL

   其他:首字母大写+驼峰式命名 eg:DBUtility;OracleDAL

类:

   类名:首字母大写+驼峰式命名 eg:PetShop.cs;AssemblyInfo.cs

   私有变量名:_首字母小写+驼峰式命名 eg:_publishTime;_rootCategoryId

   公共属性名:首字母大写+驼峰式命名 eg:Description;PublishTime

函数:

   函数名:首字母大写+驼峰式命名 eg:GetItemByProduct

   参数名:首字母小写+驼峰式命名 eg:userId,itemInfo

参考网站:

http://blog.sina.com.cn/s/blog_56dee71a01015i13.html

http://www.cnblogs.com/stephen-liu74/archive/2013/01/04/2842533.html

http://www.cnblogs.com/stephen-liu74/archive/2013/01/06/2842972.html

http://www.cnblogs.com/stephen-liu74/archive/2013/01/02/2841485.html

原文地址:https://www.cnblogs.com/langqi250/p/7283702.html