您好,欢迎来到意榕旅游网。
搜索
您的当前位置:首页Protobuf使用手册

Protobuf使用手册

来源:意榕旅游网
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属性doublefloatint32intuint32uintsint32sintfixed32fixedsfixed32sfixedboolstringbytes1.3.2.1 Union类型定义

Protobuf没有提供union类型,如果希望使⽤union类型,可以采⽤enum和optional属性定义的⽅式。例如,如果已经定义了Foo、Bar、Baz等message,则可以采⽤如下定义。

message OneMessage {

C++属性java属性备注doubledouble固定8个字节floatfloat固定4个字节int32int32使⽤变长编码,对于负数编码效率较低,如果经常使⽤负数,建议使⽤sint32intint使⽤变长编码,对于负数编码效率较低,如果经常使⽤负数,建议使⽤sintuint32int使⽤变长编码uintlong使⽤变长编码int32int采⽤zigzag压缩,对负数编码效率⽐int32⾼intlong采⽤zigzag压缩,对负数编码效率⽐int⾼uint32int总是4字节,如果数据>2^28,编码效率⾼于unit32uintlong总是8字节,如果数据>2^56,编码效率⾼于unit32int32int总是4字节intlong总是8字节boolstringstringbooleanString ⼀个字符串必须是utf-8编码或者7-bit的ascii编码的⽂本ByteString可能包含任意顺序的字节数据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、int、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的情况基本⼀致。

1.6 Protocol Buffer消息升级原则

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

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

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

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

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

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是它的简化形式。当然也可以提供⼀个或多个输出路径:

作为⼀种额外的使得,如果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’sl 通过下脚标访问其中的数组成员组

l 通过下脚标返回其中的成员的mutable_的⽅法l _add⽅法:增加⼀个成员。 // nameinlinebool 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::< ::tutorial::Person_PhoneNumber >& phone() const;inline::google::protobuf::< ::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解码到messagel 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编码进ostreaml bool ParseFromIstream(istream* input);从istream解码到message

备注:发送接收端所使⽤的加码解码⽅法不⼀定⾮得配对,即发送端⽤SerializeToString 接收端不⼀定⾮得⽤ParseFromString ,可以使⽤其他解码⽅法。

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

这⾥先定义⼀个最简单的message,其中只是包含原始类型的字段。option optimize_for = LITE_RUNTIME; message LogonReqMessage { required int acctID = 1; required string passwd = 2; }

由于我们在MyMessage⽂件中定义选项optimize_for的值为LITE_RUNTIME,因此由该.proto⽂件⽣成的所有C++类的⽗类均

为::google::protobuf::MessageLite,⽽⾮::google::protobuf::Message。MessageLite类是Message的⽗类,在MessageLite中将缺少ProtocolBuffer对反射的⽀持,⽽此类功能均在Message类中提供了具体的实现。使⽤LITE版本的Protocol Buffer。这样不仅可以得到更⾼编码效率,⽽且⽣成代码编译后所占⽤的资源也会更少,⾄于反射所能带来的灵活性和极易扩展性。下⾯我们来看⼀下由messageLogonReqMessage⽣成的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 int 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字段的当前值,如果没有设置则返回int类型的缺省值。 inline ::google::protobuf::int acctid() const;

//为acctid字段设置新值,调⽤该函数后has_acctid函数将返回true。 inline void set_acctid(::google::protobuf::int 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 = %Id, password =

%s\\n\ 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 int 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 = %Id, 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\ printf(\"Repeated Size = %d\\n\ //这⾥仅提供了通过容器迭代器的⽅式遍历数组元素的测试代码。 //事实上,通过buddiesinfo_size和buddiesinfo函数亦可循环遍历。 RepeatedPtrField* buddiesInfo = retrieveResp2.mutable_buddiesinfo(); RepeatedPtrField::iterator it = buddiesInfo->begin(); for (; it != buddiesInfo->end(); ++it) {

printf(\"BuddyInfo->groupID = %d\\n\

printf(\"UserInfo->acctID = %Id, 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 int 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接⼝。 //该接⼝会为消息中的每个字段均提供getter和setter⽅法。 public interface LogonReqMessageOrBuilder

extends com.google.protobuf.MessageLiteOrBuilder {

// required int 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.

// 由于所有构造函数均为私有⽅法,由此可见,我们不能直接newLogonReqMessage的对象 // 实例,⽽是只能通过与其对应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.LogonReqMessageparseFrom(java.io.InputStream input) throws java.io.IOException {

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

// required int 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 { //清空当前对象中的所有设置。调⽤该函数之后,本例中的hasAcctID和hasPasswd都会返回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 int 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 simplemessage.================\");

//如前所述,不能直接构造该消息类对象,只能通过他的内部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(newFileInputStream(\"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 int 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.BuilderbuilderForValue) {

userInfo_ = builderForValue.build(); bitField0_ |= 0x00000002; return this; } } }

下⾯是读写LogonRespMessage对象的Java测试代码和说明性注释。private static void testNestedMessage() {

System.out.println(\"==================This is nestedmessage.================\");

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(newFileInputStream(\"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.ListgetBuddiesInfoList() { return buddiesInfo_; }

public java.util.ListgetBuddiesInfoOrBuilderList() { 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.BuddyInfoOrBuildergetBuddiesInfoOrBuilder(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.ListgetBuddiesInfoList() {

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.BuilderbuilderForValue) { ... ... }

public Builder addBuddiesInfo(

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

public Builder addAllBuddiesInfo(

java.lang.Iterablevalues) { ... ... }

public Builder clearBuddiesInfo() { ... ... }

public Builder removeBuddiesInfo(int index) { ... ... } } }

下⾯是读写RetrieveBuddiesResp对象的Java测试代码和说明性注释。private static void testRepeatedMessage() { System.out.println(\"==================This is repeatedmessage.================\");

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(newFileInputStream(\"C:/Mine/RetrieveBuddiesResp.dat\"));

System.out.println(\"BuddiesCount = \" + buddiesResp3.getBuddiesCnt()); System.out.println(\"Repeated Size = \" + buddiesResp3.getBuddiesInfoCount()); List 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#include#include#include#include#include#include#include#include#include#include#includeusing 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(\"130518399\"); 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:\"<return 0; } 3.11 读出message

#include\"addressbook.pb.h\"#include#include#include#include#include#include#include#include#include#include#include#includeusing namespace std;//列出消息中的所有数据void List(const tutorial::AddressBook& address_book){ for(int i=0;iconst tutorial::Person& person=address_book.person(i);

cout<<\"Person ID:\"<cout<<\"E-mail address:\"<for(int j=0;jswitch(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<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:\"<//注意此次read可能⼀次性读不完⼀个包,如果包⽐较⼤的话,这样的话就得⾃⼰再做进⼀步处理 read(connfd,uData,uLen);

//解码操作,从缓冲区中解到msg2消息空间中去,此处⽤ParseFromArray ⽽没有使⽤ParseFromString 因为前者的效率相对于后者效率要⾼很多。 if(!msg2.ParseFromArray(uData,uLen)){ cout<<”Parse failed!”<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参考⽹站:

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- yrrf.cn 版权所有 赣ICP备2024042794号-2

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务