Protobuf语言指南——.proto文件语法详解
Posted 面具人生lql
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Protobuf语言指南——.proto文件语法详解相关的知识,希望对你有一定的参考价值。
Protobuf语言指南
l 定义一个消息(message)类型
l 标量值类型
l Optional 的字段及默认值
l 枚举
l 使用其他消息类型
l 嵌套类型
l 更新一个消息类型
l 扩展
l 包(package)
l 定义服务(service)
l 选项(option)
l 生成访问类
本指南描述了怎样使用protocolbuffer语言来构造你的protocol buffer数据,包括.proto文件语法以及怎样生成.proto文件的数据访问类。
本文是一个参考指南——如果要查看如何使用本文中描述的多个特性的循序渐进的例子,请在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/tutorials.html中查找需要的语言的教程。
l 定义一个消息类型
先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:
message SearchRequest required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3;
|
SearchRequest消息格式有3个字段,在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个名字和一种类型。
Ø 指定字段类型
在上面的例子中,所有字段都是标量类型:两个整型(page_number和result_per_page),一个string类型(query)。当然,你也可以为字段指定其他的合成类型,包括枚举(enumerations)或其他消息类型。
Ø 分配标识号
正如上述文件格式,在消息定义中,每个字段都有唯一的一个标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改 变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。
最小的标识号可以从1开始,最大到229 - 1, or 536,870,911。不可以使用其中的[19000-19999]的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。
Ø 指定字段规则
所指定的消息字段修饰符必须是如下之一:
² required:一个格式良好的消息一定要含有1个这种字段。表示该值是必须要设置的;
² optional:消息格式中该字段可以有0个或1个值(不超过1个)。
² repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。表示该值可以重复,相当于java中的List。
由于一些历史原因,基本数值类型的repeated的字段并没有被尽可能地高效编码。在新的代码中,用户应该使用特殊选项[packed=true]来保证更高效的编码。如:
repeated int32 samples = 4 [packed=true]; |
required是永久性的:在将一个字段标识为required的时候,应该特别小心。如果在某些情况下不想写入或者发送一个required的 字段,将原始该字段修饰符更改为optional可能会遇到问题——旧版本的使用者会认为不含该字段的消息是不完整的,从而可能会无目的的拒绝解析。在这 种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。Google的一些工程师得出了一个结论:使用required弊多于利;他们更 愿意使用optional和repeated而不是required。当然,这个观点并不具有普遍性。
Ø 添加更多消息类型
在一个.proto文件中可以定义多个消息类型。在定义多个相关的消息的时候,这一点特别有用——例如,如果想定义与SearchResponse消息类型对应的回复消息格式的话,你可以将它添加到相同的.proto文件中,如:
message SearchRequest required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3;
message SearchResponse …
|
Ø 添加注释
向.proto文件添加注释,可以使用C/C++/java风格的双斜杠(//) 语法格式,如:
message SearchRequest required string query = 1; optional int32 page_number = 2;// 最终返回的页数 optional int32 result_per_page = 3;// 每页返回的结果数
|
Ø 从.proto文件生成了什么?
当用protocolbuffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。
² 对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
² 对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
² 对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。
你可以从如下的文档链接中获取每种语言更多API。http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html
l 标量数值类型
一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:
.proto类型 | Java 类型 | C++类型 | 备注 |
double | double | double |
|
float | float | float |
|
int32 | int | int32 | 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint32。 |
int64 | long | int64 | 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint64。 |
uint32 | int[1] | uint32 | Uses variable-length encoding. |
uint64 | long[1] | uint64 | Uses variable-length encoding. |
sint32 | int | int32 | 使用可变长编码方式。有符号的整型值。编码时比通常的int32高效。 |
sint64 | long | int64 | 使用可变长编码方式。有符号的整型值。编码时比通常的int64高效。 |
fixed32 | int[1] | uint32 | 总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。 |
fixed64 | long[1] | uint64 | 总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。 |
sfixed32 | int | int32 | 总是4个字节。 |
sfixed64 | long | int64 | 总是8个字节。 |
bool | boolean | bool |
|
string | String | string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 |
bytes | ByteString | string | 可能包含任意顺序的字节数据。 |
你可以在文章http://code.google.com/apis/protocolbuffers/docs/encoding.html 中,找到更多“序列化消息时各种类型如何编码”的信息。
l Optional的字段和默认值
如上所述,消息描述中的一个元素可以被标记为“可选的”(optional)。一个格式良好的消息可以包含0个或一个optional的元素。当解 析消息时,如果它不包含optional的元素值,那么解析出来的对象中的对应字段就被置为默认值。默认值可以在消息描述文件中指定。例如,要为 SearchRequest消息的result_per_page字段指定默认值10,在定义消息格式时如下所示:
optional int32 result_per_page = 3 [default = 10]; |
如果没有为optional的元素指定默认值,就会使用与特定类型相关的默认值:对string来说,默认值是空字符串。对bool来说,默认值是false。对数值类型来说,默认值是0。对枚举来说,默认值是枚举类型定义中的第一个值。
l 枚举
当需要定义一个消息类型的时候,可能想为一个字段指定某“预定义值序列”中的一个值。例如,假设要为每一个SearchRequest消息添加一个 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一个。 其实可以很容易地实现这一点:通过向消息定义中添加一个枚举(enum)就可以了。一个enum类型的字段只能用指定的常量集中的一个值作为其值(如果尝 试指定不同的值,解析器就会把它当作一个未知的字段来对待)。在下面的例子中,在消息格式中添加了一个叫做Corpus的枚举类型——它含有所有可能的值 ——以及一个类型为Corpus的字段:
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];
|
枚举常量必须在32位整型值的范围内。因为enum值是使用可变编码方式的,对负数不够高效,因此不推荐在enum中使用负数。如上例所示,可以在 一个消息定义的内部或外部定义枚举——这些枚举可以在.proto文件中的任何消息定义里重用。当然也可以在一个消息中声明一个枚举类型,而在另一个不同 的消息中使用它——采用MessageType.EnumType的语法格式。
当对一个使用了枚举的.proto文件运行protocol buffer编译器的时候,生成的代码中将有一个对应的enum(对Java或C++来说),或者一个特殊的EnumDescriptor类(对 Python来说),它被用来在运行时生成的类中创建一系列的整型值符号常量(symbolic constants)。
关于如何在你的应用程序的消息中使用枚举的更多信息,请查看所选择的语言http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html。
l 使用其他消息类型
你可以将其他消息类型用作字段类型。例如,假设在每一个SearchResponse消息中包含Result消息,此时可以在相同的.proto文件中定义一个Result消息类型,然后在SearchResponse消息中指定一个Result类型的字段,如:
message SearchResponse repeated Result result = 1;
message Result required string url = 1; optional string title = 2; repeated string snippets = 3;
|
Ø 导入定义
在上面的例子中,Result消息类型与SearchResponse是定义在同一文件中的。如果想要使用的消息类型已经在其他.proto文件中已经定义过了呢?
你可以通过导入(importing)其他.proto文件中的定义来使用它们。要导入其他.proto文件的定义,你需要在你的文件中添加一个导入声明,如:
import "myproject/other_protos.proto"; |
protocol编译器就会在一系列目录中查找需要被导入的文件,这些目录通过protocol编译器的命令行参数-I/–import_path指定。如果不提供参数,编译器就在其调用目录下查找。
l 嵌套类型
你可以在其他消息类型中定义、使用消息类型,在下面的例子中,Result消息就定义在SearchResponse消息内,如:
message SearchResponse message Result required string url = 1; optional string title = 2; repeated string snippets = 3;
repeated Result result = 1;
|
如果你想在它的父消息类型的外部重用这个消息类型,你需要以Parent.Type的形式使用它,如:
message SomeOtherMessage optional SearchResponse.Result result = 1;
|
当然,你也可以将消息嵌套任意多层,如:
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;
|
Ø 组
注:该特性已被弃用,在创建新的消息类型的时候,不应该再使用它——可以使用嵌套消息类型来代替它。
“组”是指在消息定义中嵌套信息的另一种方法。比如,在SearchResponse中包含若干Result的另一种方法是 :
message SearchResponse repeated group Result = 1 required string url = 2; optional string title = 3; repeated string snippets = 4;
|
一个“组”只是简单地将一个嵌套消息类型和一个字段捆绑到一个单独的声明中。在代码中,可以把它看成是含有一个Result类型、名叫result的字段的消息(后面的名字被转换成了小写,所以它不会与前面的冲突)。
因此,除了数据传输格式不同之外,这个例子与上面的SearchResponse例子是完全等价的。
l 更新一个消息类型
如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。
² 不要更改任何已有的字段的数值标识。
² 所添加的任何字段都必须是optional或repeated的。这就意味着任何使用“旧”的消息格式的代码序列化的消息可以被新的代码所解析,因为它们 不会丢掉任何required的元素。应该为这些元素设置合理的默认值,这样新的代码就能够正确地与老代码生成的消息交互了。类似地,新的代码创建的消息 也能被老的代码解析:老的二进制程序在解析的时候只是简单地将新字段忽略。然而,未知的字段是没有被抛弃的。此后,如果消息被序列化,未知的字段会随之一 起被序列化——所以,如果消息传到了新代码那里,则新的字段仍然可用。注意:对Python来说,对未知字段的保留策略是无效的。
² 非required的字段可以移除——只要它们的标识号在新的消息类型中不再使用(更好的做法可能是重命名那个字段,例如在字段前添加“OBSOLETE_”前缀,那样的话,使用的.proto文件的用户将来就不会无意中重新使用了那些不该使用的标识号)。
² 一个非required的字段可以转换为一个扩展,反之亦然——只要它的类型和标识号保持不变。
² int32, uint32, int64, uint64,和bool是全部兼容的,这意味着可以将这些类型中的一个转换为另外一个,而不会破坏向前、 向后的兼容性。如果解析出来的数字与对应的类型不相符,那么结果就像在C++中对它进行了强制类型转换一样(例如,如果把一个64位数字当作int32来 读取,那么它就会被截断为32位的数字)。
² sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容。
² string和bytes是兼容的——只要bytes是有效的UTF-8编码。
² 嵌套消息与bytes是兼容的——只要bytes包含该消息的一个编码过的版本。
² fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。
l 扩展
Protobuf 语法指南