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.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);
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_size和buddiesinfo函数亦可循环遍历。 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接口。 //该接口会为消息中的每个字段均提供getter和setter方法。 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 { //清空当前对象中的所有设置。调用该函数之后,本例中的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 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() + "\tpassword = " + 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() + "\tpassword = " + 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