协议缓冲区-proto2

221 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 6 天,点击查看活动详情

一、消息定义


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

定义格式为:属性-类型-字段名-序号。

1,指定字段属性

  • required:表示该字段是必要需要的,非必要不设置为该属性,否则没有该字段时容易报错;
  • optional:表示该字段是可选的,零个或一个;
  • repeated:表示该字段可以在消息中重复任意次数(包括零次),重复值的顺序将被保留;

2,指定字段类型

字段类型可以是标量类型、复合类型、枚举和其他消息类型。

3,分配字段编号

消息定义中的每个字段都有一个唯一的编号,这些编号用于在消息二进制格式中标识对应的字段,并且一旦在某个编号消息类型中使用就不应更改。==1 到 15 范围内的字段编号需要一个字节进行编码,包括字段编号和字段类型,16 到 2047 范围内的字段编号占用两个字节==。因此,应该为非常频繁出现的消息元素保留字段编号 1 到 15,并为将来可能添加频繁出现的元素留出一些空间。最小字段编号是 1,最大的是 2^29 - 1,即 536870911。另外,编号 19000 到 19999 是为 Protocol Buffers 实现保留的,不能被使用。

4,保留字段

用户可以完全删除某个字段或将其注释掉来更新消息类型,在未来用户对类型进行更新时,可以重用删除或注释的字段编号。但用更新后的程序加载相同的旧版本时,可能会因为字段冲突导致严重问题.proto,包括数据损坏、隐私错误等。确保不会发生这种情况,可以指定已删除字段或编号为reserved. 如果将来有任何用户尝试使用这些字段标识符,protocol buffer 编译器会报错。

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

不能在同一reserved语句中混合字段名称和字段编号(9 to 11与 相同9, 10, 11)。

二、标量值类型

proto类型go类型说明
double*float64
float*float32
int32*int32使用可变长度编码。对负数进行编码效率低下——如果您的字段可能有负值,请改用 sint32
int64*int64使用可变长度编码。对负数进行编码效率低下——如果您的字段可能有负值,请改用 sint64
uint32*uint32使用可变长度编码
uint64*uint64使用可变长度编码
sint32*int32使用可变长度编码,带符号的 int 值,这些比常规 int32 更有效地编码负数
sint64*int64使用可变长度编码,带符号的 int 值,这些比常规 int64 更有效地编码负数
fix32*uint32总是四个字节,如果值通常大于 2 28 ,则比 uint32 更有效
fix64*uint64总是八个字节,如果值通常大于 2 56 ,则比 uint64 更有效
sfix32*int32总是四个字节
sfix64*int64总是八个字节
bool*bool
string*string字符串必须始终包含 UTF-8 编码的文本
bytes[]byte可以包含任意字节序列

三、可选字段默认类型

消息描述中的元素可以被标记optional,表示该元素是可选的,可以包含,也可以不包含。解析消息时,如果它不包含该可选元素,则将解析对象中的相应字段设置为该字段的默认值,但可以将默认值指定为消息描述的一部分。如果没有为可选元素指定默认值,则使用特定于类型的默认值:对于字符串,默认值为空字符串;对于字节,默认值为空字节字符串;对于布尔值,默认值为 false;对于数字类型,默认值为零;对于枚举,默认值是枚举类型定义中列出的第一个值,意味着在枚举值列表的开头添加值时必须小心。

optional int32 page = 3 [default = 10];

四、枚举

1,枚举定义

在定义消息类型时,希望某个字段的值仅为预定义值列表之一时,可以使用枚举(如果您尝试提供不同的值,解析器会将其视为未知数)。

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,否则协议缓冲区编译器会在找到别名时生成错误消息。枚举常量必须在 32 位整数范围内,由于enum 值使用 varint 编码,负值效率低下,因此不推荐使用。

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

2,枚举保留值

枚举保留值和其他保留值一样,可以采用 reserved 标识,max 可以指定保留的数值范围达到最大可能值,不能在同一语句中混合字段名称和数值。

enum Foo {
	reserved 2, 15, 9 to 11, 40 to max;
	reserved "FOO", "BAR";
}

五、使用其他消息类型

可以在一个消息中,将某个字段的类型定义为另一个消息类型。


message Response {
  repeated Result result = 1; // result 字段的类型为Result类型
}

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

六、导入定义

可以在文件顶部添加一个 import 语句:.proto,导入来气其他文件定义的消息类型。

import "myproject/other_protos.proto";

默认情况下,只能使用直接导入.proto文件中的定义。但是,有时可能需要将.proto文件移动到新位置,可以在旧位置放置一个占位符文件,以使用该概念将所有导入转发到新位置,而不是直接移动.proto文件并在一次更改中更新所有调用站点。import publicimport public任何导入包含该语句的原型的代码都可以传递依赖依赖项。协议编译器使用 -I 或 --proto_path 指定导入文件的搜索目录。如果没有给出该标志,它会在调用编译器的目录中查找。通常,应该将--proto_path标志设置为项目的根目录,并为所有导入使用完全限定名称。

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

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

七、嵌套类型

可以在其他消息类型中定义和使用消息类型:

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
      optional int64 ival = 1;
      optional bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      optional string name = 1;
      optional bool   flag = 2;
    }
  }
  // 两个Inner是完全独立的
}

八、更新消息类型

如果现有的消息类型不再满足所有需求,需要增加额外的字段,但仍然希望使用使用旧格式创建的代码,可以按照以下规则来更新:

  • 不要更改任何现有字段的字段编号;

  • 添加的任何新字段都应该是optional或repeated,意味着使用“旧”消息格式的代码序列化的任何消息都可以由新生成的代码解析,因为它们不会丢失任何required 元素。应该为这些元素设置合理的默认值,以便新代码可以与旧代码生成的消息正确交互。类似地,新代码创建的消息可以由旧代码解析:旧二进制文件在解析时会忽略新字段。但是,未知字段不会被丢弃,如果消息稍后被序列化,未知字段也会随之序列化——因此,如果将消息传递给新代码,新字段仍然可用。

  • 在更新的消息类型中不再使用的字段编号,可以删除非必填字段。需要重命名该字段时,可以添加前缀“OBSOLETE_”,或将字段编号设为 reserved,以便未来用户不会意外重用该编号。

  • 只要类型和编号保持不变,非必填字段可以转换为扩展名,反之亦然。

  • int32、uint32、int64、uint64和bool都是兼容的,这意味着可以将字段从其中一种类型更改为另一种类型,而不会破坏向前或向后兼容性。

  • 对于string、bytes和 消息字段,optional与repeated兼容。

  • 更改默认值通常是可以的,但默认值永远不会通过网络发送。如果程序接收到未设置特定字段的消息,则程序将看到在该程序的.proto中定义的默认值,不会看到发件人代码中定义的默认值。

  • enum与int32, uint32, int64, 和uint64兼容(如果值不合适,将被截断)。enum当消息被反序列化时,无法识别的值将被丢弃,这使得该字段的has..访问器返回 false 并且其 getter 返回定义中列出的第一个值enum,或者如果指定了默认值,则返回默认值。在重复枚举字段的情况下,任何无法识别的值都会从列表中删除,整数字段将始终保留其值。

九、扩展

允许声明消息中的一系列字段编号可用于第三方扩展,.proto扩展是原始文件未定义类型的字段的占位符,这允许其他 .proto文件通过使用这些字段编号定义部分或所有字段的类型来添加到您的消息定义中。

message Foo {
  // ...
  extensions 100 to 199;
}
extend Foo {
  optional int32 bar = 126;
}

这表示字段编号 [100, 199] 的范围Foo是为扩展保留的,其他用户现在可以使用指定范围内的字段编号,Foo在自己的.proto 导入文件中添加新字段。

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

可以在另一种类型的范围内声明扩展