Protobuffer 官方文档学习

戏子无情 提交于 2020-05-01 19:26:40

Protobuffer

proto3

proto3 比 proto2 新,所以一般选用proto3进行构建项目。

下面英文版可以在Language Guide (proto3)上看到。

假设您要定义搜索请求消息格式,其中每个搜索请求都有一个查询字符串。

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;  // Which page number do we want?
  int32 result_per_page = 3;  // Number of results to return per page.
}

该文件的第一行指定您使用的是proto3语法:如果不这样做,协议缓冲区编译器将假定您正在使用proto2。 这必须是文件的第一个非空,非注释行。

指定类型

在上述示例中,所有字段都是标量类型:两个整数(page_number和result_per_page)和一个字符串(查询)。

但是,您也可以为字段指定复合类型,包括枚举和其他消息类型。

分配标签

消息定义中的每个字段都有唯一的编号标签。这些标签用于以消息二进制格式标识字段,并且在使用消息类型后不应更改它们。

请注意,值范围为1到15的标签需要一个字节进行编码,包括标识号和字段的类型。16到2047范围内的标签需要两个字节。

为非常频繁出现的消息元素预留标签1到15。记住要为将来可能添加的频繁出现的元素留下一些空间。

您可以指定的最小标签号码是1,最大的标签号码是2的29次方减1(536,870,911)。您也不能使用号码19000到19999(FieldDescriptor :: kFirstReservedNumber到FieldDescriptor :: kLastReservedNumber),因为它们被保留用于协议缓冲区实现。

编码

Protocol Buffer Encoding

  • 单一性:创建的消息可以有0或1个这个说明的属性(但是不能超过一个)。
  • 重复性:该字段可以在格式正确的消息中重复任意次数(包括零。 重复值的顺序将被保留。

在proto3中,repeated 数字类型的重复字段默认使用压缩编码。

注释

使用**"//"** 双斜杠进行注释

Reserved (保留字段)

如果通过删除某个字段或者将这个字段注释掉,将来又使用了这个数字编号,并且使用的又是老版本.proto文件,会导致严重的问题(数据损坏,私有属性bugs等)。

解决方法:在"reserved"后指定已删除字段, 当以后使用这些字段的时候protobuffer解释器会自动报错。

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

注意:您不能在相同的保留语句中混合添加字段名称和标签号。,也就是要不用编号,要不用字段名称。

proto 生成文件类型

当.proto运行协议编译器时,会根据选择的语言将消息序列化成输出流,并从输入流解析消息。

  • C++:编译器从每个.proto生成.h和.cc文件,并为文件中描述的每个消息类型分配一个类。
  • Java:编译器会为每个消息类型生成一个包含类的.java文件,以及一个用于创建消息类实例的特殊Builder类。
  • Python: Python编译器生成一个模块,其中包含.proto中每个消息类型的静态描述符,然后使用元类在运行时创建必要的Python数据访问类。
  • Go: 编译器将生成一个.pb.go文件,其文件中包含每种消息类型的类型。
  • Ruby: 编译器会生成一个包含消息类型的Ruby模块的.rb文件。
  • JavaNano: 编译器输出与Java类似,但没有Builder类。
  • Objective-C: 编译器会从每个.proto生成一个pbobjc.h和pbobjc.m文件,其中包含文件中描述的每个消息类型的类。
  • C#: 编译器会从每个.proto生成.cs文件,其中包含文件中描述的每个消息类型的类。

详细信息可以查看API Reference:

proto 中支持的类型

.proto Notes C++ Java Python Go Ruby C# PHP
double double double float float64 Float double float
float float float float float32 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 int int32 Fixnum or Bignum (as required) int integer
int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. int64 long int/long[3] int64 Bignum long integer/string[5]
uint32 Uses variable-length encoding. uint32 int[1] int/long[3] uint32 Fixnum or Bignum (as required) uint integer
uint64 Uses variable-length encoding. uint64 long[1] int/long[3] uint64 Bignum ulong integer/string[5]
sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. int32 int int int32 Fixnum or Bignum (as required) int integer
sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. int64 long int/long[3] int64 Bignum long integer/string[5]
fixed32 Always four bytes. More efficient than uint32 if values are often greater than 228. uint32 int[1] int uint32 Fixnum or Bignum (as required) uint integer
fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 256. uint64 long[1] int/long[3] uint64 Bignum ulong integer/string[5]
sfixed32 Always four bytes. int32 int int int32 Fixnum or Bignum (as required) int integer
sfixed64 Always eight bytes. int64 long int/long[3] int64 Bignum long integer/string[5]
bool bool boolean bool bool TrueClass/FalseClass bool boolean
string A string must always contain UTF-8 encoded or 7-bit ASCII text. string String str/unicode[4] string String (UTF-8) string string
bytes May contain any arbitrary sequence of bytes. string ByteString str []byte String (ASCII-8BIT) ByteString string
  • [1]在Java中,无符号的32位和64位整数使用它们的带符号表示,顶部位被简单地存储在符号位中。
  • [2]在所有情况下,将值设置为字段将执行类型检查以确保其有效。
  • [3] 64位或无符号32位整数在解码时始终表示为长,但如果在设置字段时给出int,则可以为int。 在所有情况下,该值必须适合设置时表示的类型。 见[2]。
  • [4] Python字符串在解码时表示为unicode,但如果给出ASCII字符串(可能会更改),则可以是str。
  • [5]整数用于64位机器,字符串用于32位机器。

默认值

当消息被解析时,如果编码的消息不包含特定的单个元素,则解析对象中的相应字段将被设置为该字段的默认值。:

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于bools,默认值为false。
  • 对于数值类型,默认值为零。
  • 对于枚举,默认值是第一个定义的枚举值,它必须为0。
  • 对于消息字段,该字段未设置。其确切的值取决于语言。有关细节,请参阅API Reference

重复字段的默认值为空(通常为适当语言的空列表)。

注意:当message 属性被解析,就没办法明确设置值为默认值(例如布尔值是否设置为false,或者根本不设置,例如,如果您不希望默认情况下也会发生这种行为,那么在设置为false时,不要使用布尔值来切换某些行为。

另外如果当message属性被解析设置为默认值,那么这个值就不会被序列化。

枚举

在下面的示例中,我们添加了一个名为Corpus的枚举,其中包含所有可能的值,以及一个类型为Corpus的字段:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

枚举初始化常量是0, 每个枚举都必须包含一个常量。定义为0为第一个元素。

必须有一个零值,所以我们可以使用0作为数字默认值。

可以通过为不同的枚举常量分配相同的值来定义别名。为此,您需要将allow_aliasoptions设置为true,否则协议编译器将在找到别名时生成错误消息。

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

枚举器常量必须在32位整数的范围内。由于枚举值在线上使用varint编码,所以负值无效,因此不推荐使用。

还可以使用语法MessageType.EnumType在一个消息中声明的枚举类型作为不同消息中的字段的类型。

在反序列化期间,消息中将保留无法识别的枚举值,尽管消息反序列化的方式与语言有关。在任一情况下,如果消息被序列化,则无法识别的值仍将被序列化为消息。

包含其他消息类型

可以在一个消息中包含其他消息

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

导入已定义消息

在上述示例中,Result消息类型与SearchResponse相同的文件中定义,如果要用作字段类型的消息类型已经在另一个.proto文件中定义了

您可以通过导入来自其他.proto文件的定义。

import "myproject/other_protos.proto";

默认情况下,您只能使用直接导入的.proto文件中的定义。 但是,有时您可能需要将.proto文件移动到新的位置。 而不是直接移动.proto文件,并在一次更改中更新所有调用站点,现在可以在旧位置放置一个虚拟.proto文件,以使用导入公开概念将所有导入转发到新位置。 任何导入包含导入公开声明的proto的任何进口公共依赖都可以被过渡性地依赖。 例如:

新proto:

// new.proto
// All definitions are moved here

旧proto:

// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";

使用proto

// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

编译器使用**-I/--proto_path**标示,指定一组目录中搜索导入的文件。一般来说将--proto_path设置为项目的根,并对所有导入使用全路径名。

proto3中使用proto2

可以导入proto2消息类型并在proto3消息中使用它们,反之亦然。 然而,proto2枚举不能直接在proto3语法中使用(如果导入的proto2消息使用它们就可以了)。

嵌套

可以任意嵌套层次(不过使用中还是少嵌套)

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}

更新message

比如需要添加额外字段,但仍然想使用与哪有创建的代码,这个时候就可以使用更新。

  • 不要更改任何现有字段的数字标签。
  • 如果添加新字段,则使用“旧”消息格式的代码序列化的任何消息仍然可以通过新生成的代码进行解析。您应该记住这些元素的默认值,以便新代码可以正确地与旧代码生成的邮件进行交互。类似地,您的新代码创建的消息可以由您的旧代码解析:旧的二进制文件在解析时只是忽略新的字段。有关详细信息,请参阅Unknown Fields
  • 只要在更新的消息类型中不再使用标签号,就可以删除字段。您可能需要重命名字段,也可以添加前缀"OBSOLETE_",或者使保留标记,以便.proto的未来用户不会意外重用该数字。
  • int32,uint32,int64,uint64和bool都是兼容的,这意味着您可以将这些类型之一的字段更改为另一个,而不会破坏前向或后向兼容性。如果一个数字从不符合相应类型的流中解析出来,您将获得与C++中将该数字转换为该类型相同的效果(例如,如果将64位数字读为int32,它将被截断到32位)。
  • sint32和sint64相互兼容,但与其他整数类型不兼容。
  • 只要字节是有效的UTF-8,字符串和字节是兼容的。
  • Embedded messages are compatible with bytes if the bytes contain an encoded version of the message.(没看懂,看懂的告诉一下)
  • fixed32与sfixed32兼容,fixed64与sfixed64兼容。
  • 枚举兼容于int32,uint32,int64和uint64(请注意,如果值不合适,那么值将被截断)。但是请注意,客户端代码可以在消息反序列化时对它们进行不同的处理:例如,消息中将保留无法识别的proto3枚举类型,但是当消息反序列化时,如何表示它是与语言相关的。 Int字段始终保持其值。

未知字段

表示解析器无法识别的字段。

Proto3实现可以成功地解析具有未知字段的消息。实现支持或不支持未知字段。 未知字段在proto3中运行时间不可访问,并在反序列化时间被遗忘和遗忘。 这对于proto2是不同的行为,其中未知的字段总是与消息一起保留和序列化。

Any 类型

Any类型可以包含任意序列化的消息作为内容。充当唯一标示符并解析为该消息类型的URL.

使用Any类型需要导入google/protobuf/any.proto .

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

默认给的消息类型是type.googleapis.com/packagename.messagename

不同的语言支持运行时以类型安全的方式打包和解包,例如,在Java中,Any类型将具有特殊的pack()和unpack()访问器,而在C ++中有PackFrom()和UnpackTo()方法:

// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);

// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
  if (detail.Is<NetworkErrorDetails>()) {
    NetworkErrorDetails network_error;
    detail.UnpackTo(&network_error);
    ... processing network_error ...
  }
}

Oneof (不了解)

(不太理解这个方法,后续了解后补充)

如果您有一个包含多个字段的消息,并且最多可以同时设置一个字段,则可以通过使用该功能强制执行此行为并节省内存。

Oneof字段就像常规字段,除了一个共享内存中的所有字段,最多可以同时设置一个字段。

设置任何成员自动清除所有其他成员。 您可以根据您选择的语言检查使用 case() or WhichOneof()方法设置一个值(如果有)。

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

Oneof特点

  • 设置一个字段将自动清除其中一个的所有其他成员。 所以如果你设置几个字段,只有你设置的最后一个字段仍然有一个值。

Maps

map<key_type, value_type> map_field = N;

其中key_type可以是任何整数或字符串类型(因此,除浮点类型和字节之外的任何标量类型)。 value_type可以是任何类型。

map<string, Project> projects = 3;
  • map的值不能重复
  • map是无序的,不能依赖特定的顺序。
  • 当从.proto文件中解析出来,key_type按数字排序。
  • 当解析的时候有重复的key则看到的是最后一个key表示的值。从文本中解析如果存在重复的键,则解析将失败。

向后兼容

map等同于下面的内容,不支持map的任然可以解析

message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

Packages

可以添加包,以防止协议消息类型之间的名称冲突。

package foo.bar;
message Open { ... }

用的时候

message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}

使用取决于选择的语言

  • 在C++中,生成的类被包装在C++命名空间中。例如,Open将在命名空间foo::bar中。
  • 在Java中,该包用作Java包,除非您在.proto文件中显式提供了一个选项java_package。
  • 在Python中,包指令被忽略,因为Python模块根据文件系统中的位置进行组织。
  • 在Go中,该包用作Go包名称,除非您在.proto文件中显式提供了一个选项go_package。
  • 在Ruby中,生成的类包装在嵌套的Ruby命名空间中,转换为所需的Ruby大小写样式(第一个字母大写;如果第一个字符不是字母,则为PB_)。例如,Open将在命名空间Foo::Bar中。
  • 在JavaNano中,该包用作Java包,除非您在.proto文件中显式提供了一个选项java_package。
  • 在C#中,该包在转换为PascalCase后用作命名空间,除非在.proto文件中显式提供了一个选项csharp_namespace。例如,Open将在命名空间Foo.Bar中。

解析从内向外进行解析,也就是内部包到其父包。

定义服务(方法)

如果要使用RPC系统的消息类型,可以定义一个RPC服务接口。

如果要使用一个使用SearchRequest并返回SearchResponse的方法定义RPC服务,可以在.proto文件中定义它,如下所示:

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

可以使用grpc或自定义rpc进行使用

JSON Mapping

Proto3支持JSON中的规范编码,使得更容易在系统之间共享数据。

如果JSON编码数据中缺少值,或者如果其值为空,则在解析为协议缓冲区时将被解释为适当的默认值。 如果某个字段在协议缓冲区中具有默认值,则默认情况下将在JSON编码数据中省略该节点以节省空间。

proto3 JSON JSON example Notes
message object {"fBar": v, "g": null, …} Generates JSON objects. Message field names are mapped to lowerCamelCase and become JSON object keys. null is accepted and treated as the default value of the corresponding field type.
enum string "FOO_BAR" The name of the enum value as specified in proto is used.
map<K,V> object {"k": v, …} All keys are converted to strings.
repeated V array [v, …] null is accepted as the empty list [].
bool true, false true, false
string string "Hello World!"
bytes base64 string "YWJjMTIzIT8kKiYoKSctPUB+"
int32, fixed32, uint32 number 1, -10, 0 JSON value will be a decimal number. Either numbers or strings are accepted.
int64, fixed64, uint64 string "1", "-10" JSON value will be a decimal string. Either numbers or strings are accepted.
float, double number 1.1, -10.0, 0, "NaN", "Infinity" JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". Either numbers or strings are accepted. Exponent notation is also accepted.
Any object {"@type": "url", "f": v, … } If the Any contains a value that has a special JSON mapping, it will be converted as follows: {"@type": xxx, "value": yyy}. Otherwise, the value will be converted into a JSON object, and the "@type" field will be inserted to indicate the actual data type.
Timestamp string "1972-01-01T10:00:20.021Z" Uses RFC 3339, where generated output will always be Z-normalized and uses 0, 3, 6 or 9 fractional digits.
Duration string "1.000340012s", "1s" Generated output always contains 0, 3, 6, or 9 fractional digits, depending on required precision. Accepted are any fractional digits (also none) as long as they fit into nano-seconds precision.
Struct object { … } Any JSON object. See struct.proto.
Wrapper types various types 2, "2", "foo", true, "true", null, 0, … Wrappers use the same representation in JSON as the wrapped primitive type, except that null is allowed and preserved during data conversion and transfer.
FieldMask string "f.fooBar,h" See fieldmask.proto.
ListValue array [foo, bar, …]
Value value Any JSON value
NullValue null JSON null

options

.proto文件中的单独声明可以使用多个options进行注释。 options不会更改声明的整体含义,但可能会影响其在特定上下文中的处理方式。 可用options的完整列表在google/protobuf/descriptor.proto中定义。

  • 一些options是文件级options,这意味着它们应该写在顶级作用域中,而不是在任何消息,枚举或服务定义内。
  • 一些options是消息级options,这意味着它们应该写入消息定义内。
  • 一些options是字段级options,这意味着它们应该写在字段定义中。 枚举类型,枚举值,服务类型和服务方式也可以选择options。 然而,目前没有任何有用的options。

1.java_package (file option):要用于生成的Java类的包。 如果在.proto文件中没有提供明确的java_package选项,则默认情况下将使用proto包(使用.proto文件中的“package”关键字指定)。 然而,由于原始软件包不期望以反向域名开始,因此原始软件包通常不会生成好的Java软件包。 如果不生成Java代码,则此选项不起作用。

option java_package = "com.example.foo";

2.java_multiple_files (file option):导致在包级别定义顶级消息,枚举和服务,而不是以.proto文件命名的外部类。

option java_multiple_files = true;

3.java_outer_classname (file option): 要生成的最外层Java类的类名(因此是文件名)。 如果在.proto文件中没有指定明确的java_outer_classname,则通过将.proto文件名转换为camel-case来构造类名称(因此foo_bar.proto变为FooBar.java)。 如果不生成Java代码,则此选项不起作用。

option java_outer_classname = "Ponycopter";

4.optimize_for (file option):可以设置为SPEED,CODE_SIZE或LITE_RUNTIME。 这将影响C ++和Java代码生成器(以及可能的第三方生成器),方法如下:

  • SPEED(默认):协议缓冲区编译器将生成用于对消息类型进行序列化,解析和执行其他常见操作的代码。此代码非常优化。
  • CODE_SIZE:协议缓冲区编译器将生成最少的类,并且将依赖基于共享的基于反射的代码来实现序列化,解析和各种其他操作。因此,生成的代码将比SPEED小得多,但操作速度会更慢。类仍将实现与SPEED模式中完全相同的公共API。此模式在包含非常大的.proto文件的应用程序中非常有用,并且不需要它们全部快速地打开。
  • LITE_RUNTIME:协议缓冲区编译器将生成仅依赖于“lite”运行时库(libprotobuf-lite而不是libprotobuf)的类。精简的运行时间远小于完整的库(大约在一个数量级上),但省略了某些功能,如描述符和反射。这对于在受限平台(如手机)上运行的应用程序尤其有用。编译器仍将像SPEED模式一样生成所有方法的快速实现。生成的类只会在每个语言中实现MessageLite接口,该接口仅提供完整Message接口的方法的一部分。
option optimize_for = CODE_SIZE;

5.cc_enable_arenas (file option):Enables arena allocation for C++ generated code.

6.objc_class_prefix (file option):将Objective-C类前缀设置为所有Objective-C生成的类和该.proto的枚举前缀。 没有默认值。 您应该使用Apple推荐的3-5个大写字母之间的前缀。 请注意,所有2个字母的前缀都由Apple保留。

7.deprecated (field option):如果设置为true,则表示该字段已被弃用,不应由新代码使用。 在大多数语言中,这没有实际的效果。 在Java中,这将成为@Deprecated注释。 将来,其他特定于语言的代码生成器可能会在该字段的访问器上生成废弃注释,这将在编译尝试使用该字段的代码时产生警告。 如果任何人不使用该字段,并且您想要阻止新用户使用该字段,请考虑将字段声明替换为保留语句。

int32 old_field = 6 [deprecated=true];

Custom Options

允许您定义和使用您自己的options。 这是大多数人不需要的高级功能。 如果您认为您需要创建自己的options,请参阅“Proto2语言指南”了解详细信息。 请注意,创建自定义options使用的扩展名只能在proto3中的自定义options中使用。

protoc

要生成Java,Python,C ++,Go,Ruby,JavaNano,Objective-C或C#代码,您需要使用.proto文件中定义的消息类型,您需要在.proto上运行编译器protoc 。

protoc

对于Go,您还需要为编译器安装一个特殊的代码生成器插件:您可以在GitHub的golang/protobuf存储库中找到此和安装说明。

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
  • IMPORT_PATH 指定在解析导入指令时查找.proto文件的目录。 如果省略,则使用当前目录。 可以多次传递--proto_path选项来指定多个导入目录; 他们将按顺序搜索。 -I = IMPORT_PATH可以用作--proto_path的简写形式。
  • 可以提供一个或多个输出指令:
    • --cpp_out在DST_DIR中生成C ++代码。 有关更多信息,请参阅C++生成的代码参考
    • --java_out在DST_DIR中生成Java代码。 有关更多信息,请参阅Java生成的代码参考
    • --python_out在DST_DIR中生成Python代码。 有关详细信息,请参阅Python生成的代码参考
    • --go_out在DST_DIR中生成Go代码。 有关更多信息,请参阅Go生成的代码参考
    • --ruby_out在DST_DIR中生成Ruby代码。 Ruby生成代码引用即将推出!
    • --javanano_out在DST_DIR中生成JavaNano代码。 JavaNano代码生成器有许多选项可用于自定义生成器输出:您可以在生成器README中找到有关这些选项的更多信息。 JavaNano生成的代码参考即将推出!
    • --objc_out在DST_DIR中生成Objective-C代码。 有关更多信息,请参阅Objective-C生成的代码参考
    • --csharp_out在DST_DIR中生成C#代码。 有关更多信息,请参阅C#生成的代码参考
    • --php_out在DST_DIR中生成PHP代码。 有关更多信息,请参阅PHP生成代码参考

作为一个额外的便利,如果DST_DIR以.zip或.jar结尾,编译器将把输出写入具有给定名称的单个ZIP格式归档文件。 .jar输出也将被给予Java JAR规范要求的清单文件。

注意,如果输出存档已经存在,它将被覆盖; 编译器不够聪明,无法将文件添加到现有存档。

proto2

proto2 定义消息体有点不一样,接下来只会将不一样的写出来一样的请看上面。

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

方法定义规则

  • required: 一个格式正确的消息必须有这个字段。
  • optional: 格式正确的消息可以具有该字段的零个或一个(但不超过一个)。
  • repeated: 该字段可以在格式正确的消息中重复任意次数(包括零)。 重复值的顺序将被保留。

由于历史原因,标量数字类型的重复字段没有尽可能高效地编码。 新代码应该使用特殊选项[packed = true]来获得更有效的编码。 例如:

repeated int32 samples = 4 [packed=true];

查看Protocol Buffer Encoding

required是永远您应该非常小心根据需要标记字段。 如果您希望停止写入或发送required字段,则将该字段更改为可选字段将是有问题的.

Groups

已经被废弃不要再使用

Extensions 扩展

扩展名是一个字段的占位符,其类型未由原始.proto文件定义。 这允许其他.proto文件通过使用这些数字标签定义一些或所有字段的类型来添加到您的消息定义。

message Foo {
  // ...
  extensions 100 to 199;
}

这表示Foo中的字段数字[100,199]的范围被保留用于扩展。 其他用户现在可以在自己的.proto文件中向Foo添加新的字段,该文件导入.proto,使用指定范围内的标签,例如:

extend Foo {
  optional int32 bar = 126;
}

这将将一个名为bar的字段添加到Foo的原始定义中。

编译后格式和原油格式相同,但是在访问扩展字段和常规字段有所不同,需要使用特殊访问器。如c++中设置bar值

Foo foo;
foo.SetExtension(bar, 15);

类似地,Foo类定义了模板访问器HasExtension(), ClearExtension(), GetExtension(), MutableExtension(), and AddExtension()。

注意,扩展可以是任何字段类型,包括消息类型,但不能是oneofs或map。

嵌套扩展(一般不要用)

message Baz {
  extend Foo {
    optional int32 bar = 126;
  }
  ...
}

c++使用

Foo foo;
foo.SetExtension(Baz::bar, 15);

换句话说,唯一的效果是在Baz范围内定义了该条

声明嵌套在消息类型中的扩展块并不意味着外部类型和扩展类型之间的任何关系

上述示例并不意味着Baz是Foo的任何类别的子类。 这意味着符号栏被声明在Baz的范围内; 它只是一个静态成员。(意味着这样用不好呗,那么不要嵌套使用咯)

一个常见的模式是在扩展字段类型的范围内定义扩展名,例如,这是Baz类型的Foo扩展名,扩展名定义为Baz的一部分和下面这种其实是一样的。

message Baz {
  ...
}

// This can even be in a different file.
extend Foo {
  optional Baz foo_baz_ext = 127;
}

实际上,这种语法可能是首选,以避免混淆。 如上所述,嵌套语法经常被误认为尚不熟悉扩展名的用户进行子类化。

选择编号

确保两个用户不使用相同的数字标签添加相同消息类型的扩展非常重要

如果您的编号约定可能涉及具有非常大数字的扩展名作为标签,则可以使用max关键字指定扩展范围达到最大可能的字段数:

message Foo {
  extensions 1000 to max;
}

max是2的29次方减1, or 536,870,911.(同时要避免内部使用的号码 19000 though 19999)。

Custom Options

这是大多数人不需要的高级功能。

import "google/protobuf/descriptor.proto";

extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}

message MyMessage {
  option (my_option) = "Hello world!";
}

这里我们通过扩展MessageOptions定义了一个新的消息级选项。 当我们使用该选项时,选项名称必须用括号括起来表示它是一个扩展名。 我们现在可以用C ++读取my_option的值:

string value = MyMessage::descriptor()->options().GetExtension(my_option);

这里,My Message::descriptor()->options()返回MyMessage的Message Options协议消息。 从它读取自定义选项就像阅读任何其他扩展名。

Java 读取

String value = MyProtoFile.MyMessage.getDescriptor().getOptions().getExtension(MyProtoFile.myOption);

Python:

value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions().Extensions[my_proto_file_pb2.my_option]

可以为Protocol Buffers语言中的每种构造定义自定义选项。 这是一个使用各种选项的例子:

import "google/protobuf/descriptor.proto";

extend google.protobuf.FileOptions {
  optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
  optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
  optional float my_field_option = 50002;
}
extend google.protobuf.EnumOptions {
  optional bool my_enum_option = 50003;
}
extend google.protobuf.EnumValueOptions {
  optional uint32 my_enum_value_option = 50004;
}
extend google.protobuf.ServiceOptions {
  optional MyEnum my_service_option = 50005;
}
extend google.protobuf.MethodOptions {
  optional MyMessage my_method_option = 50006;
}

option (my_file_option) = "Hello world!";

message MyMessage {
  option (my_message_option) = 1234;

  optional int32 foo = 1 [(my_field_option) = 4.5];
  optional string bar = 2;
}

enum MyEnum {
  option (my_enum_option) = true;

  FOO = 1 [(my_enum_value_option) = 321];
  BAR = 2;
}

message RequestType {}
message ResponseType {}

service MyService {
  option (my_service_option) = FOO;

  rpc MyMethod(RequestType) returns(ResponseType) {
    // Note:  my_method_option has type MyMessage.  We can set each field
    //   within it using a separate "option" line.
    option (my_method_option).foo = 567;
    option (my_method_option).bar = "Some string";
  }
}

请注意,如果要在除了定义的包之外的包中使用自定义选项,则必须在选项名称前加上包名称,就像对类型名称一样。 例如:

// foo.proto
import "google/protobuf/descriptor.proto";
package foo;
extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}
// bar.proto
import "foo.proto";
package bar;
message MyMessage {
  option (foo.my_option) = "Hello world!";
}

最后一件事:由于自定义选项是扩展名,因此必须为任何其他字段或扩展名分配字段编号。 在上面的例子中,我们使用的范围是50000-99999。 此范围仅供个别组织内部使用,因此您可以自由使用本范围内的数字进行内部应用。 但是,如果您打算在公共应用程序中使用自定义选项,那么重要的是确保您的字段数字是全球唯一的。 要获取全球唯一的字段编号,请发送一个请求到protobuf-global-extension-registry@google.com。 只需提供您的项目名称(例如Object-C插件)和您的项目网站(如果有的话)。 通常你只需要一个分机号码。 您可以通过将它们放入子消息来声明只有一个分机号码的多个选项:

message FooOptions {
  optional int32 opt1 = 1;
  optional string opt2 = 2;
}

extend google.protobuf.FieldOptions {
  optional FooOptions foo_options = 1234;
}

// usage:
message Bar {
  optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = "baz"];
  // alternative aggregate syntax (uses TextFormat):
  optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: "baz" }];
}

参考

Language Guide (proto3)

Language Guide (proto2)

Protocol Buffer Encoding

API Reference

Go Generated Code

PS: 觉得不错的请点个赞吧!! (ง •̀_•́)ง

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!