Protocol Buffers 语法指南

前两篇文章,咱们归纳介绍《Google Protocol Buffers 概述》以及带领你们简单的《Google Protocol Buffers 入门》,接下来,再稍微详细一点介绍Protocol Buffers书写语言。该篇文章主要讲解如何使用PB语言构建数据,包括.proto文件语法及若是使用.proto文件生成数据存取类。java

本篇主要包括:数组

  • 定义一个PB message类型
  • 介绍PB 数据类型
  • Optional字段及其默认值
  • 枚举类型
  • 使用其余Message类型做为filed类型
  • 嵌套类型
  • 更新Message

2. 定义一个PB message类型

假如如今须要定义搜索请求的message格式,每条message包含三个字段:搜索语句(query string),须要的返回结果页数(page_number),以及该页上的结果数。可以下定义.proto文件。ide

?
1
2
3
4
5
message SearchRequest {
   required string query = 1;
   optional int32 page_number = 2;
   optional int32 result_per_page = 3;
}

该message定义声明三个字段(name/value pairs),每一个字段有一个名字和类型。性能

 2.1 声明字段类型

上例中,全部的字段类型均为标准类型:两个整型和一个字符串类型。固然,也能够指定复合类型:枚举类型和其余自定义message类型。ui

2.2 给字段赋值数字标签

从上例中能够发现,message中定义的每一个字段都有一个惟一的数字标签。该标签的做用是在二进制message中惟一标示该字段,一旦定义该字 段的值就不可以再更改。有一点须要强调:1~15的数字标签编码后仅占一个字节(byte),包括数字标签和字段类型。16~2047的数字标签占两个字 节(byte)。所以,1~15的数字标签应该用于最频繁出现的元素。设计时要考虑到不要一次用完1~15的标签,要考虑到未来也可能出现频繁出现的元 素。this

最小的数字标签是1,最大的数字标签是2的29次方-1,也即 536,870,911。可是并非这之间全部的数字标签你都能用,例如 19000~19999。这个区间的数字标签就像是java中的保留字同样,他们是PB的保留数字标签。若是该区间的数字标签出如今.proto文件 中,PB编译器会出错。google

2.3 字段标示符

字段标示符有三个:编码

message的没一个字段,都要用以下的三个修饰符(modifier)来声明:url

  1. required:必须赋值,不能为空,不然该条message会被认为是“uninitialized”。build一个 “uninitialized” message会抛出一个RuntimeException异常,解析一条“uninitialized” message会抛出一条IOException异常。除此以外,“required”字段跟“optional”字段并没有差异。
  2. optional:字段能够赋值,也能够不赋值。假如没有赋值的话,会被赋上默认值。对于简单类型,默认值能够本身设定,例如上例的 PhoneNumber中的PhoneType字段。若是没有自行设定,会被赋上一个系统默认值,数字类型会被赋为0,String类型会被赋为空字符 串,bool类型会被赋为false。对于内置的message,默认值为该message的默认实例或者原型,即其内全部字段均为设置。当获取没有显式 设置值的optional字段的值时,就会返回该字段的默认值。
  3. repeated:该字段能够重复任意次数,包括0次。重复数据的顺序将会保存在protocol buffer中,将这个字段想象成一个能够自动设置size的数组就能够了。

因为一些历史缘由,数字类型的repeated字段性能有些不尽人意,可是,PB已经作了改进,可是须要再添加一点改动,即在声明后添加[packed=true]例如:spa

?
1
repeated int32 samples = 4 [packed= true ];

Notice:应该格外当心定义Required字段。当由于某缘由要把Required字段改成 Optional字段是,会有问题,老版本读取器会认为消息中没有该字段不完整,可能会拒绝或者丢弃该字段(Google文档是这么说的,可是我试了一 下,将required的改成optional的,再用原来required时候的解析代码去读,若是字段赋值的话,并不会出错,可是若是字段未赋值,会 报这样错误:Exception in thread “main” com.google.protobuf.InvalidProtocolBufferException: Message missing required fields:fieldname)。在设计时,尽可能将这种验证放在应用程序端的完成。Google的一些工程师对此也很困惑,他们觉 得,required类型坏处大于好处,应该尽可能仅适用optional或者repeated的。但也并非全部的人都这么想。

2.4 同一.proto文件定义多个message

PB支持同一.proto文件定义多个message。这在须要定义相关message的时候很是有用,例如:除了搜索请求message,还须要定义搜索响应message,能够再同一.proto文件中定义:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message SearchRequest {
 
required string query = 1 ;
 
optional int32 page_number = 2 ;
 
optional int32 result_per_page = 3 ;
 
}
 
message SearchResponse {
 
...
 
}

2.5 添加评论

使用C/C++风格的注释 // syntax,以下例子:

?
1
2
3
4
5
6
7
8
9
message SearchRequest {
 
required string query = 1;
 
optional int32 page_number = 2;// Which page number do we want?
 
optional int32 result_per_page = 3;// Number of results to return per page.
 
}

2.6 编译.proto文件后产生了什么?

用PB 编译器运行.proto文件后,会按照定义的格式,生成指定语言的一系列代买,这些代码的功能包括:字段值的getter,setter,序列化message并写入到输出流,从输入流接写成message等。

对于Java,编译器生成一个.java文件,该java文件内包含几个内部类,分别对应.proto文件中定义的message 类型,以及未来用于建立message类实例的Builder类。

3. 标准值类型

.proto Type Notes C++ Type Java Type Python Type[2]
double   double double float
float   float float float
int32 使用可变长编码. 对于负数比较低效,若是负数较多,请使用sint32 int32 int int
int64 使用可变长编码. 对于负数比较低效,若是负数较多,请使用sint64 int64 long int/long
uint32 使用可变长编码 uint32 int
int/long
uint64 使用可变长编码 uint64 long
int/long
sint32 使用可变长编码. Signed int value. 编码负数比int32更高效 int32 int int
sint64 使用可变长编码. Signed int value. 编码负数比int64更高效 int64 long int/long
fixed32 恒定四个字节。若是数值几乎老是大于2的28次方,该类型比unit32更高效。 uint32 int
int
fixed64 恒定四个字节。若是数值几乎老是大于2的56次方,该类型比unit64更高效。 uint64 long
int/long
sfixed32 恒定四个字节 int32 int int
sfixed64 恒定八个字节 int64 long int/long
bool   bool boolean boolean
string A string must always contain UTF-8 encoded or 7-bit ASCII text. string String str/unicode
bytes 包含任意数量顺序的字节 string ByteString str

4. Optional字段及其默认值

上面提到,PB容许设置可选字段(optional)。顾名思义,在一条message中,该字段可设值也可不设。假如没有设置,那么在解析该字段的时候,会根据该字段类型,给其赋一个类型默认值。除此以外,也能够在定义message格式的时候,就为optional字段设置一个默认值,以下:

?
1
optional int32 result_per_page = 3 [default = 10];

假如没有赋值的话,会被赋上默认值。对于简单类型,默认值能够本身设定,例如上例的PhoneNumber中的PhoneType字段。若是没有自行设定,会被赋上一个系统默认值,数字类型会被赋为0,String类型会被赋为空字符串,bool类型会被赋为false。对于枚举类型,默认值是枚举列表中第一个值。

5. 枚举类型

在定义message类型的时候,也许会有这样一种需求:其中的一个字段仅须要包含预约义的若干个值便可。好比,对于每个搜索请求,现须要增长一个分类字段,分类包含:UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS or VIDEO。要实现该功能,仅须要增长一个枚举类型字段。以下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message SearchRequest {
     required string query = 1;
     optional int32 page_number = 2;
     optional int32 result_per_page = 3 [default = 10];
     enum Corpus {
        UNIVERSAL = 0;
        WEB = 1;
        IMAGES = 2;
        LOCAL = 3;
        NEWS = 4;
        PRODUCTS = 5;
        VIDEO = 6;
     }
     optional Corpus corpus = 4 [default = UNIVERSAL];
}

还能够给枚举值设置别名,仅需将相同的数字标签设置给不一样的名称便可。这里,必须得设置allow_alias为true,不然PB编译器会报错。

?
1
2
3
4
5
6
7
8
9
10
11
12
enum EnumAllowingAlias {
     option allow_alias = true;
     UNKNOWN = 0;
     STARTED = 1;
     RUNNING = 1;
}
 
enum EnumNotAllowingAlias {
     UNKNOWN = 0;
     STARTED = 1;
     // RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}

能够定义枚举在一个message内部,如上例。也能够定义在message的外部,这样的枚举能够被其余任何.proto文件内的message复用。

6. 使用其余Message类型做为filed类型

PB容许使用message类型做为filed类型。例如,在搜索相应message中,包含一个结果message。此时,只须要定义一个结果 message,而后再.proto文件中,在搜索结果message中新增一个字段,该字段的类型设置为结果message便可。以下:

?
1
2
3
4
5
6
7
8
9
message SearchResponse {
     repeated Result result = 1;
}
 
message Result {
     required string url = 1;
     optional string title = 2;
     repeated string snippets = 3;
}

6.1 导入定义

在上例中,Result message类型与SearchResponse 定义在同一个文件中,假若有这么一种状况,这里所要使用的Resultmessage已经在其余的.proto文件中定义了呢?

能够经过导入其余.proto文件来使用其内的定义。为达此目的,须要在现.proto文件前增长一条import语句:

?
1
import "myproject/other_protos.proto";

7. 嵌套类型

PB支持message内嵌套message,以下例子中,Result message 定义在了SearchResponse内:

?
1
2
3
4
5
6
7
8
message SearchResponse {
   message Result {
     required string url = 1;
     optional string title = 2;
     repeated string snippets = 3;
   }
   repeated Result result = 1;
}

若是想要在父Message外复用该message的话,能够用Parent.Type格式来引用。

?
1
2
3
message SomeOtherMessage {
   optional SearchResponse.Result result = 1;
}

PB支持无限深层次的message嵌套:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
message Outer {                  // Level 0
   message MiddleAA {  // Level 1
     message Inner {   // Level 2
       required int64 ival = 1;
       optional bool  booly = 2;
     }
   }
   message MiddleBB {  // Level 1
     message Inner {   // Level 2
       required int32 ival = 1;
       optional bool  booly = 2;
     }
   }
}

8. 更新Message类型

若是现有message类型不能在知足业务需求,例如,须要新增一个字段,可是咱们却但愿依然可以使用原来的.proto生成的代码。彻底没有问题,仅需记住以下规则:

  1. 千万不要修改现有字段后边的数值标签
  2. 只能新增optional或者repeated字段
  3. 能够删除非必须字段,可是他们的数字标签不能再被使用。最好的方法是不删除,而是修更名字,好比在前缀上加OBSOLETE_,这样就能够避免后人尽可能少的出错。
  4. 非required字段能够转化成extension字段,反之亦然,同时保留原类型和数字标签
  5. int32, uint32, int64, uint64, 和bool是兼容的。即这些字段能够相互切换,在代码处理的时候,不会出错,可是当心范围小的数据接收范围大的数据会发生截断
  6. sint32, sint64是相互兼容的,可是不与其余整型类型兼容
  7. string和bytes是兼容的,由于bytes也是合法的UTF-8
  8. Embedded messages are compatible with bytes if the bytes contain an encoded version of the message(不知道怎么翻译了)
  9. fixed32与 sfixed32兼容, fixed64 与sfixed64兼容
  10. optional与repeated兼容,也存在数据截断,假如讲一个repeated的序列化后的数据做为输入给客户端,客户端会截取最后一个原子类型的字节。或者,若是是一个message类型的字段的话,合并全部的元素。
  11. 能够修改字段默认值

 9. Package

PB建议在.proto文件开头添加一个package说明符来避免不一样message类型的名字冲突:

?
1
2
3
package foo.bar;
 
message Open { ... }

这样,就可使用该package标示符来定义该message类型的字段:

?
1
2
3
4
5
6
7
8
9
message Foo {
 
     ...
 
     required foo.bar.Open open = 1;
 
     ...
 
}

不一样语言,由于添加package标示符,生成的代码也会有所不一样,Java中,该package将会被用做java文件的package。若是不想这样的话,也可在.proto文件中显式指明package,该字段是:java_package。

译自:https://developers.google.com/protocol-buffers/docs/proto

说实话,翻译下来整个文章很是辛苦,并且都要敲代码去亲自试验可否经过,因此若是您想转载,很是欢迎,但请注明出处,也算是对俺辛苦的尊重~ 

原创做品,容许转载,转载时请务必以超连接形式标明文章 原始出处 、做者信息 石头儿本声明。不然将追究法律责任。http://shitouer.cn/2013/04/protocol-buffers-language-guide/
相关文章
相关标签/搜索