Protobuf语法指南(proto2)

本文是一篇译文,原文地址为:https://developers.google.com/protocol-buffers/docs/proto#generating定义一个消息类型先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:mess...
摘要由CSDN通过智能技术生成

本文是一篇译文,原文地址为:https://developers.google.com/protocol-buffers/docs/proto#generating

定义一个消息类型

先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.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开始,最大到2^29 - 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++风格的 // 和 /* … */ 语法格式

/* SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response. */

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.
}

保留字段

如果通过将字段完全删除或将其注释来更新消息类型,则在将来,当用户更新其消息类型时,他们可以重用那些字段的编号。 如果以后加载相同.proto文件的旧版本,这可能会导致严重问题,包括数据损坏,隐私错误等。 确保不会发生这种情况的一种方法是指定已删除字段的字段编号为“reserved”。 如果将来的任何用户尝试使用这些字段标识符,协议缓冲编译器将会发出抱怨。

message Foo {
   
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

注意:不能在同一 “reserved” 语句中将字段名称和字段编号混合在一起指定。

从.proto文件生成了什么?

当用protocolbuffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。

  • 对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
  • 对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
  • 对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。

标量类型

一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:

.proto Type Notes C++ Type Java Type Python Type[2] Go Type
double double double float *float64
float float float float *float32
int32 使用可变长度编码。编码负数的效率低 - 如果你的字段可能有负值,请改用 sint32 int32 int int *int32
int64 使用可变长度编码。编码负数的效率低 - 如果你的字段可能有负值,请改用 sint64 int64 long int/long[3] *int64
uint32 使用可变长度编码 uint32 int[1] int/long[3] *uint32
uint64 使用可变长度编码 uint64 long[1] int/long[3] *uint64
sint32 使用可变长度编码。有符号的 int 值。这些比常规 int32 对负数能更有效地编码 int32 int int *int32
sint64 使用可变长度编码。有符号的 int 值。这些比常规 int64 对负数能更有效地编码 int64 long int/long[3] *int64
fixed32 总是四个字节。如果值通常大于 228,则比 uint32 更有效。 uint32 int[1] int/long[3] *uint32
fixed64 总是八个字节。如果值通常大于 256,则比 uint64 更有效。 uint64 long[1] int/long[3] *uint64
sfixed32 总是四个字节 int32 int int *int32
sfixed64 总是八个字节 int64 long int/long[3] *int64
bool bool boolean bool *bool
string 字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本 string String str/unicode[4] *string
bytes 可以包含任意字节序列 string ByteString str []byte

在 Protocol Buffer 编码 中你可以找到有关序列化 message 时这些类型如何被编码的详细信息。
[1] 在 Java 中,无符号的 32 位和 64 位整数使用它们对应的带符号表示,第一个 bit 位只是简单的存储在符号位中。
[2] 在所有情况下,设置字段的值将执行类型检查以确保其有效。
[3] 64 位或无符号 32 位整数在解码时始终表示为 long,但如果在设置字段时给出 int,则可以为int。在所有情况下,该值必须适合设置时的类型。见 [2]。
[4] Python 字符串在解码时表示为 unicode,但如果给出了 ASCII 字符串,则可以是 str(这条可能会发生变化)。

Optional字段和默认值

如上所述,消息描述中的一个元素可以被标记为“可选的”(optional)。一个格式良好的消息可以包含0个或一个optional的元素。当解析消息时,如果没有给它的optional字段指定一个值,那么该消息的相应字段的值就会被置为默认值。默认值可以在消息描述文件中指定。例如,要为 SearchRequest消息的result_per_page字段指定默认值10,在定义消息格式时如下所示:

optional int32 result_per_page = 3 [default = 10];

如果没有为optional字段指定默认值,就会使用与特定类型相关的默认值:对string来说,默认值是空字符串。对bytes来说,默认值是一个空的字节串。对bool来说,默认值是false。对数值类型来说,默认值是0。对枚举来说,默认值是枚举类型定义中的第一个值。

枚举

当需要定义一个消息类型的时候,可能想为一个字段指定某“预定义值序列”中的一个值。例如,假设要为每一个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
  • 17
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值