protobuf 语法浅析

Protobuf

为何用Protobuf Buffer

跨语言平台编程,使用SOAP的话,该方式是使用xml的方式传输,会大大增长网络的IO,并且xml的解析复杂,下降报文的解析性能。java

定义一个Protobuf消息

message LogonReqMessage{
	required int64 actID=1;
	required string passwd=2;
}

关键说明:python

  1. message是消息的关键字
  2. LogonReqMessage是消息名字,至关于java的类名
  3. required前缀表示该字段未必要字段,序列化先后必须赋值的字段。protobuf还存在两个相似的关键字optionalrepeated主要用于表示数组字段。
  4. int64和string表示长整型和字符串类型的消息字段,在protobuf中存在一张类型对照表,即protobuf类型与其余语言的类型对照。
  5. actID和passwd分别表示消息的字段名,等同于java中的域名变量名。
  6. 标签数字12表示不一样的字段在序列化先后的二进制中的布局位置。在本例中,passwd字段编码后的数据必定位于actID后,该值在同一个message中不能重复。对于protobuf而言,标签1到15的字段在编码的时候是能够获得优化的(标签值和类型信息只占一个byte,标签范围16到2047占两个byte),protobuf可支持的字段数量是$2^{29}-1$。所以,应该将repeated类型的字段标签未与1~15,节省编码后的字节数量。

定义第二个protobuf消息

enum UserStatus{
	OFFLINE=0;
	ONLINE=1;
}

message UserInfo{
	required int64 actID=1;
	required string name=2;
	required UserStatus=3;
}

关键说明:c++

  1. enum是枚举类型的关键字,等同于java里面的enum
  2. UserStatus为枚举的名字
  3. 和java同样枚举之间的分隔符是分好;而不是逗号,
  4. OFFLINEONLINE表示枚举值
  5. 01表示枚举所对应的实际整型值,可为任意整型值,无需从0开始。

定义第三个protobuf消息

enum UserStatus{
	OFFLINE=0;
	ONLINE=1;
}

message UserInfo{
	required int64 actID=1;
	required string name=2;
	required UserStatus=3;
}

message LogonRespMessage{
	required LoginResult logonResult = 1;
	required UserInfo userInfo = 2;
}

关键说明:编程

  1. LogonRespMessage消息定义中包含另一个消息类型做为其字段,如UserInfo userInfo
  2. 上例的UserInfo和LogonRespMessage被定义在同一个.proto文件中,那么怎么包含其余proto文件中的message呢?ProtoBuf提供关键字import将其余proto文件的message引入到当前proto文件中。例如Import "myproject/CommonMessages.proto"

限定符号

  1. 每一个消息必须有一个字段是required类型的字段。
  2. 每一个消息包含0个或多个optional类型的字段。
  3. repeated表示的字段能够包含0个或多个数据,有别于java的数组,由于java数组中至少包含一个元素
  4. 若是打算在原有消息协议中添加新的字段,同时保证老的字段能正常的读取写入,那么新添加的字段必须是optional或者repeated类型的。

proto类型对照表

.proto Type Notes C++ Type Java Type
double double double
float float float
int32 "Uses variable-length encoding. Inefficient for encoding negative numbers ¨C if your field is likely to have negative values use sint32 instead." int32 int
int64 "Uses variable-length encoding. Inefficient for encoding negative numbers ¨C 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

protobuf消息升级原则

  1. 不要修改已经存在字段的标签号
  2. 任何新添加的字段必须是optional或者repeated限定符,不然没法保证新老程序在互传消息的时候消息的兼容性。
  3. 在原有消息中,不能移除已经存在的required字段,optionalrepeated类型字段能够移除,可是他们以前使用的标签不能使用了。
  4. int32uint32int64uint64bool等类型之间是兼容的,sint32sint64是兼容的,stirngbyte是兼容的,fixed32sfixed32是兼容的,fixed64sfixed64是兼容的,若是想修改原有字段类型,为了保证兼容性,只能将其修改成原有类型兼容的类型,不然打破新老消息格式的兼容性。
  5. optionalrepeated限定符是相互兼容的。

packages

能够在.proto文件中定义包名,如:package abc.lypgone,该包名生成c++时,替换成名字空间,而java为java的包名。数组

options

protobuf 在.proto文件中定义一些经常使用的选项,这样protobuf能够帮助咱们生成更匹配的目标语言代码。网络

protobuf的内置选项分为三个等级:工具

  1. 文件级,这样的选项影响当前文件的全部消息和枚举。
  2. 消息级,这样的选项仅影响某个消息及其包含的全部字段。
  3. 字段级,这样的选项近影响某个字段。

经常使用的protobuf选项有:布局

  1. option java_package="com.companyname.projectname"; java_package 是文件级的选项,指定让生成的java代码的包名为该选项的值;与此同时,输出的文件也自动输出到对应包目录下(上例的com/companyname/projectname目录下),该选项对C++没有影响。
  2. option java_outer_classname="LYPhoneMessage"; java_outer_classname是文件级别的选项,指定生成的java代码的外部类名称。若是没有指定,java代码的外部类名称为当前文件的文件名部分,同时将文件名转为驼峰格式,如my_project.proto,那么该文件的外部类名为MyProject,该选项对C++代码无影响。

注意,因为一个java文件只能有一个外部类或者外部接口,因此.proto文件中的message定义的消息均为外部类的内部类,这样才能将这些消息定义到一个文件中。c++没有此限制。性能

  1. option optimize_for = LITE_RUNTIME; optimize_for 是文件级选项,是protobuf优化选项。该选项又分为3个等级:
    1. SPEED:表示生成的代码运行效率高,可是由今生成的代码编译后会占用更多的空间
    2. CODE_SIZE: 和SPEED偏偏相反,代码运行效率较低,可是由今生成的代码编译后会占用更少的空间,一般用于资源有限的平台,如Mobile。
    3. 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优化

  1. [pack = true]: 由于历史缘由,对于数值型的repeated字段,如int3二、int64等,在编码时并无获得很好的优化,然而在新近版本的Protocol Buffer中,可经过添加[pack=true]的字段选项,以通知Protocol Buffer在为该类型的消息对象编码时更加高效。如:repeated int32 samples = 4 [packed=true]

注:该选项仅适用于2.3.0以上的Protocol Buffer

  1. [default = default_value]: optional类型的字段,若是在序列化时没有被设置,或者是老版本的消息中根本不存在该字段,那么在反序列化该类型的消息是,optional的字段将被赋予类型相关的缺省值,如bool被设置为false,int32被设置为0。Protocol Buffer也支持自定义的缺省值,如:optional int32 result_per_page = 3 [default = 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。

相关文章
相关标签/搜索