上一篇博文介绍了一个综合案例,这篇将详细介绍protocol buffer。html
首先,要使用protocol buffer得保证maven安装成功,maven的下载地址:http://maven.apache.org/download.cgi 。java
1.解压完以后请将maven的bin目录配置到你的环境变量当中。python
2.请确保你的JAVA_HOME的变量是指向你的JDK的主目录,若是你的系统变量中没有JAVA_HOME这一项,请点击新建添加。apache
3.打开命令行,输入“mvn --version”若是输出正确则表示安装成功编程
安装完maven以后就要进行protocol buffer的安装了,下载地址: http://code.google.com/p/protobuf/downloads/list 。下载protobuf-2.4.1.zip 和 protoc-2.4.1-win32.zip 两个包。windows
1. 解压完成以后有两种选择,第一:将protoc-2.4.1-win32中的protoc.exe所在的目录配置到环境变量当中,第二:将protoc.exe拷贝到c:\windows\system32目录下,这里推荐第二种作法。数组
2. 将proto.exe文件拷贝到解压后的protobuf-2.4.1\src目录中.网络
3. 进入protobuf-2.4.1\java 目录 执行mvn package命令编辑该包,系统将会在target目录中生成protobuf-java-2.4.1.jar文件(注意运行时须要联网,首次安装可能须要必定的时间)。数据结构
4. 假设你的数据文件目录在XXX\data目录,把上一步生成的jar拷贝到该目录中便可。eclipse
5. 进入XXX\protobuf-2.4.1\examples目录,能够看到addressbook.proto文件,在命令行中执行 protoc --java_out=. addressbook.proto 命令(特别注意. Addressbook.proto中间的空格,我第一次安装就由于没注意而反复失败),若是生成com文件夹而且最终生成AddressBookProtos类则说明安装成功。
6. 打开eclipse,选择windows-->preferences-->java-->Installed JREs编辑你默认的java源码包,并将上面所提到的protobuf-java-2.4.1.jar文件添加进去。
如下文章摘抄http://www.cnblogs.com/stephen-liu74/archive/2013/01/02/2841485.html
Protobuf 有如 XML,不过它更小、更快、也更简单。你能够定义本身的数据结构,而后使用代码生成器生成的代码来读写这个数据结构。你甚至能够在无需从新部署程序的状况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,便可利用各类不一样语言或从各类不一样数据流中对你的结构化数据轻松读写。
它有一个很是棒的特性,即“向后”兼容性好,人们没必要破坏已部署的、依靠“老”数据格式的程序就能够对数据结构进行升级。这样您的程序就能够没必要担忧由于消息结构的改变而形成的大规模的代码重构或者迁移的问题。由于添加新的消息中的 field 并不会引发已经发布的程序的任何改变。
Protobuf 语义更清晰,无需相似 XML 解析器的东西(由于 Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操做)。
使用 Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf 比其余的技术更加有吸引力。
2、定义第一个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之间,这样即可以有效的节省编码后的字节数量。
3、定义第二个(含有枚举字段)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;
}
4、定义第三个(含有嵌套消息字段)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"
5、限定符(required/optional/repeated)的基本规则。
1. 在每一个消息中必须至少留有一个required类型的字段。
2. 每一个消息中能够包含0个或多个optional类型的字段。
3. repeated表示的字段能够包含0个或多个数据。须要说明的是,这一点有别于C++/Java中的数组,由于后二者中的数组必须包含至少一个元素。
4. 若是打算在原有消息协议中添加新的字段,同时还要保证老版本的程序可以正常读取或写入,那么对于新添加的字段必须是optional或repeated。道理很是简单,老版本程序没法读取或写入新增的required限定符的字段。
6、类型对照表。
.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 |
7、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限定符也是相互兼容的。
8、Packages。
咱们能够在.proto文件中定义包名,如:
package ourproject.lyphone;
该包名在生成对应的C++文件时,将被替换为名字空间名称,既namespace ourproject { namespace lyphone。而在生成的Java代码文件中将成为包名。
9、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]。
10、命令行编译工具。
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。