1.Protocol Buffer 概念java
Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。python
Protocol Buffers 是一种轻便高效的结构化数据存储格式,能够用于结构化数据串行化,或者说序列化。它很适合作数据存储或 RPC 数据交换格式。可用于通信协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。编程
2.Protocol Buffer 使用 数组
使用ProtocolBuffer,要先编写一个.proto文件,用这个文件来描述你但愿保存的数据结构。而后用ProtocolBuffer编译器建立一个类,这个类用高效的二进制的格式实现了ProtocolBuffer数据的自动编解码。生成的类提供了组成ProtocolBuffer字段的getter和setter方法,以及提供了负责读写一个ProtocolBuffer单位的方法。重要的是ProtocolBuffer格式支持向后的兼容性,新的代码依然能够读取用旧格式编码的数据。数据结构
2.1定义第一个Protocol Buffer消息 编程语言
建立扩展名为.proto的文件,如:MyMessage.proto,并将如下内容存入该文件中。
message LogonReqMessage {
required int64 acctID = 1;
required string passwd = 2;
}
这里将给出以上消息定义的关键性说明。
1. message是消息定义的关键字,等同于C++中的struct/class,或是Java中的class。
2. LogonReqMessage为消息的名字,等同于结构体名或类名。
3. required前缀表示该字段为必要字段,既在序列化和反序列化以前该字段必须已经被赋值。与此同时,在Protocol Buffer中还存在另外两个相似的关键字,optional和repeated,带有这两种限定符的消息字段则没有required字段这样的限制。相比于optional,repeated主要用于表示数组字段。具体的使用方式在后面的用例中均会一一列出。
4. int64和string分别表示长整型和字符串型的消息字段,在Protocol Buffer中存在一张类型对照表,既Protocol Buffer中的数据类型与其余编程语言(C++/Java)中所用类型的对照。该对照表中还将给出在不一样的数据场景下,哪一种类型更为高效。该对照表将在后面给出。
5. acctID和passwd分别表示消息字段名,等同于Java中的域变量名,或是C++中的成员变量名。
6. 标签数字1和2则表示不一样的字段在序列化后的二进制数据中的布局位置。在该例中,passwd字段编码后的数据必定位于acctID以后。须要注意的是该值在同一message中不能重复。另外,对于Protocol Buffer而言,标签值为1到15的字段在编码时能够获得优化,既标签值和类型信息仅占有一个byte,标签范围是16到2047的将占有两个bytes,而Protocol Buffer能够支持的字段数量则为2的29次方减一。有鉴于此,咱们在设计消息结构时,能够尽量考虑让repeated类型的字段标签位于1到15之间,这样即可以有效的节省编码后的字节数量。
2.2定义第二个(含有枚举字段)Protocol Buffer消息
//在定义Protocol Buffer的消息时,可使用和C++/Java代码一样的方式添加注释。
enum UserStatus {
OFFLINE = 0; //表示处于离线状态的用户
ONLINE = 1; //表示处于在线状态的用户
}
message UserInfo {
required int64 acctID = 1;
required string name = 2;
required UserStatus status = 3;
}
这里将给出以上消息定义的关键性说明(仅包括上一小节中没有描述的)。
1. enum是枚举类型定义的关键字,等同于C++/Java中的enum。
2. UserStatus为枚举的名字。
3. 和C++/Java中的枚举不一样的是,枚举值之间的分隔符是分号,而不是逗号。
4. OFFLINE/ONLINE为枚举值。
5. 0和1表示枚举值所对应的实际整型值,和C/C++同样,能够为枚举值指定任意整型值,而无需老是从0开始定义。如:
enum OperationCode {
LOGON_REQ_CODE = 101;
LOGOUT_REQ_CODE = 102;
RETRIEVE_BUDDIES_REQ_CODE = 103;
LOGON_RESP_CODE = 1001;
LOGOUT_RESP_CODE = 1002;
RETRIEVE_BUDDIES_RESP_CODE = 1003;
}工具
2.3定义第三个(含有嵌套消息字段)Protocol Buffer消息
咱们能够在同一个.proto文件中定义多个message,这样即可以很容易的实现嵌套消息的定义。如:
enum UserStatus {
OFFLINE = 0;
ONLINE = 1;
}
message UserInfo {
required int64 acctID = 1;
required string name = 2;
required UserStatus status = 3;
}
message LogonRespMessage {
required LoginResult logonResult = 1;
required UserInfo userInfo = 2;
}
这里将给出以上消息定义的关键性说明(仅包括上两小节中没有描述的)。
1. LogonRespMessage消息的定义中包含另一个消息类型做为其字段,如UserInfo userInfo。
2. 上例中的UserInfo和LogonRespMessage被定义在同一个.proto文件中,那么咱们是否能够包含在其余.proto文件中定义的message呢?Protocol Buffer提供了另一个关键字import,这样咱们即可以将不少通用的message定义在同一个.proto文件中,而其余消息定义文件能够经过import的方式将该文件中定义的消息包含进来,如:
import "myproject/CommonMessages.proto"布局
2.4限定符(required/optional/repeated)的基本规则
1. 在每一个消息中必须至少留有一个required类型的字段。
2. 每一个消息中能够包含0个或多个optional类型的字段。
3. repeated表示的字段能够包含0个或多个数据。须要说明的是,这一点有别于C++/Java中的数组,由于后二者中的数组必须包含至少一个元素。
4. 若是打算在原有消息协议中添加新的字段,同时还要保证老版本的程序可以正常读取或写入,那么对于新添加的字段必须是optional或repeated。道理很是简单,老版本程序没法读取或写入新增的required限定符的字段。
2.5类型对照表post
.proto Type | Notes | C++ Type | Java Type |
double | double | double | |
float | float | float | |
int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int |
int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long |
uint32 | Uses variable-length encoding. | uint32 | int |
uint64 | Uses variable-length encoding. | uint64 | long |
sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int |
sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long |
fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 228. | uint32 | int |
fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 256. | uint64 | long |
sfixed32 | Always four bytes. | int32 | int |
sfixed64 | Always eight bytes. | int64 | long |
bool | bool | boolean | |
string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String |
bytes | May contain any arbitrary sequence of bytes. | string | ByteString |
2.6 Protocol Buffer消息升级原则
在实际的开发中会存在这样一种应用场景,既消息格式由于某些需求的变化而不得不进行必要的升级,可是有些使用原有消息格式的应用程序暂时又不能被马上升级,这便要求咱们在升级消息格式时要遵照必定的规则,从而能够保证基于新老消息格式的新老程序同时运行。规则以下:
1. 不要修改已经存在字段的标签号。
2. 任何新添加的字段必须是optional和repeated限定符,不然没法保证新老程序在互相传递消息时的消息兼容性。
3. 在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段能够被移除,可是他们以前使用的标签号必须被保留,不能被新的字段重用。
4. int3二、uint3二、int6四、uint64和bool等类型之间是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着若是想修改原有字段的类型时,为了保证兼容性,只能将其修改成与其原有类型兼容的类型,不然就将打破新老消息格式的兼容性。
5. optional和repeated限定符也是相互兼容的。
优化
2.7Packages
咱们能够在.proto文件中定义包名,如:
package ourproject.lyphone;
该包名在生成对应的C++文件时,将被替换为名字空间名称,既namespace ourproject { namespace lyphone。而在生成的Java代码文件中将成为包名。
2.8 Options
Protocol Buffer容许咱们在.proto文件中定义一些经常使用的选项,这样能够指示Protocol Buffer编译器帮助咱们生成更为匹配的目标语言代码。Protocol Buffer内置的选项被分为如下三个级别:
1. 文件级别,这样的选项将影响当前文件中定义的全部消息和枚举。
2. 消息级别,这样的选项仅影响某个消息及其包含的全部字段。
3. 字段级别,这样的选项仅仅响应与其相关的字段。
下面将给出一些经常使用的Protocol Buffer选项。
1. option java_package = "com.companyname.projectname";
java_package是文件级别的选项,经过指定该选项可让生成Java代码的包名为该选项值,如上例中的Java代码包名为com.companyname.projectname。与此同时,生成的Java文件也将会自动存放到指定输出目录下的com/companyname/projectname子目录中。若是没有指定该选项,Java的包名则为package关键字指定的名称。该选项对于生成C++代码毫无影响。
2. option java_outer_classname = "LYPhoneMessage";
java_outer_classname是文件级别的选项,主要功能是显示的指定生成Java代码的外部类名称。若是没有指定该选项,Java代码的外部类名称为当前文件的文件名部分,同时还要将文件名转换为驼峰格式,如:my_project.proto,那么该文件的默认外部类名称将为MyProject。该选项对于生成C++代码毫无影响。
注:主要是由于Java中要求同一个.java文件中只能包含一个Java外部类或外部接口,而C++则不存在此限制。所以在.proto文件中定义的消息均为指定外部类的内部类,这样才能将这些消息生成到同一个Java文件中。在实际的使用中,为了不老是输入该外部类限定符,能够将该外部类静态引入到当前Java文件中,如:import static com.company.project.LYPhoneMessage.*。
3. option optimize_for = LITE_RUNTIME;
optimize_for是文件级别的选项,Protocol Buffer定义三种优化级别SPEED/CODE_SIZE/LITE_RUNTIME。缺省状况下是SPEED。
SPEED: 表示生成的代码运行效率高,可是由今生成的代码编译后会占用更多的空间。
CODE_SIZE: 和SPEED偏偏相反,代码运行效率较低,可是由今生成的代码编译后会占用更少的空间,一般用于资源有限的平台,如Mobile。
LITE_RUNTIME: 生成的代码执行效率高,同时生成代码编译后的所占用的空间也是很是少。这是以牺牲Protocol Buffer提供的反射功能为代价的。所以咱们在C++中连接Protocol Buffer库时仅需连接libprotobuf-lite,而非libprotobuf。在Java中仅需包含protobuf-java-2.4.1-lite.jar,而非protobuf-java-2.4.1.jar。
注:对于LITE_MESSAGE选项而言,其生成的代码均将继承自MessageLite,而非Message。
4. [pack = true]: 由于历史缘由,对于数值型的repeated字段,如int3二、int64等,在编码时并无获得很好的优化,然而在新近版本的Protocol Buffer中,可经过添加[pack=true]的字段选项,以通知Protocol Buffer在为该类型的消息对象编码时更加高效。如:
repeated int32 samples = 4 [packed=true]。
注:该选项仅适用于2.3.0以上的Protocol Buffer。
5. [default = default_value]: optional类型的字段,若是在序列化时没有被设置,或者是老版本的消息中根本不存在该字段,那么在反序列化该类型的消息是,optional的字段将被赋予类型相关的缺省值,如bool被设置为false,int32被设置为0。Protocol Buffer也支持自定义的缺省值,如:
optional int32 result_per_page = 3 [default = 10]。
2.9 命令行编译工具
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
这里将给出上述命令的参数解释。
1. protoc为Protocol Buffer提供的命令行编译工具。
2. --proto_path等同于-I选项,主要用于指定待编译的.proto消息定义文件所在的目录,该选项能够被同时指定多个。
3. --cpp_out选项表示生成C++代码,--java_out表示生成Java代码,--python_out则表示生成Python代码,其后的目录为生成后的代码所存放的目录。
4. path/to/file.proto表示待编译的消息定义文件。
注:对于C++而言,经过Protocol Buffer编译工具,能够将每一个.proto文件生成出一对.h和.cc的C++代码文件。生成后的文件能够直接加载到应用程序所在的工程项目中。如:MyMessage.proto生成的文件为MyMessage.pb.h和MyMessage.pb.cc