Protobuf的基础知识

ProtoBuf的字段编号

消息定义中的每个字段都有一个唯一的编号。这些字段编号用于在消息二进制格式中标识您的字段,一旦您的消息类型被使用,就不应更改(消息类型被使用了,更改字段编码会影响已有数据的编解码)。

1到15范围内的字段编号占用一个字节进行编码,包括字段编号和字段类型(您可以在协议缓冲区编码中找到更多相关信息)。16到2047范围内的字段编号占用两个字节。因此,您应该为非常频繁出现的消息元素保留数字1到15。可以为将来可能添加的频繁出现的元素留出一些空间(可以这么理解吧,必填字段用1~15,选填字段使用16~2047)。

不能使用数字19000到19999,因为它们是为Protocol Buffers实现保留的。同样,也不能使用任何以前保留的字段编号。

保留字段

保留字段存在的必要性:如果只通过注释或删除字段来更新消息类型,可能导致删除或注释的字段被复用,从而使用新的消息类型解析已有的消息时,会带来困惑。

确保不会发生这种情况的一种方法是指定已删除字段的字段编号、名称(之所以保留名称,是担心JSON序列化的问题),如果任何未来的用户尝试使用这些字段标识符,协议缓冲区编译器会报错。

1
2
3
4
5
6

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

不可以在同一个reserved中混合使用字段名称和字段编号。

默认值

解析消息时,如果编码的消息不包含特定的singular元素(不是很理解这个前提),则解析对象中的相应字段将设置为该字段的默认值。这些默认值是特定于类型的:

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于bool,默认值为false。
  • 对于数字类型,默认值为零。
  • 对于enums,默认值是第一个定义的enum value,它必须是 0。
  • 对于消息字段,未设置该字段。它的确切值取决于语言。有关详细信息,请参阅生成的代码指南。
  • 重复字段的默认值为空(通常是相应语言的空列表)。

请注意,对于标量消息字段,一旦消息被解析,就无法判断字段是否明确设置为默认值(例如,布尔值是否设置为false)或根本没有设置:您应该记住这一点在定义消息类型时。例如,false如果您不希望默认情况下也发生该行为,则不要设置一个布尔值来开启某些行为。还要注意的是,如果一个标消息字段被设置为默认值,该值将不会在电线上连载。

有关默认值如何在生成的代码中工作的更多详细信息,请参阅所选语言的生成代码指南。

枚举类型

每个枚举定义都必须包含一个零的常量作为其第一个元素,这是因为:

  • 必须有一个零值,以便我们可以使用0作为数字默认值
  • 零值需要是第一个元素,以便与proto2语义兼容,其中第一个枚举值始终是默认值

需要注意的事项:

  1. 枚举常量必须在32位整数范围内。
  2. 由于enum值在线路上使用varint编码,负值效率低下,因此不推荐使用。
  3. 可以在消息类型内部或者消息外部定义枚举,这些枚举当前proto文件中的任何消息类型复用。
  4. 可以使用_MessageType_._EnumType_使用其他消息内部定义的枚举。

不是很理解的知识

在反序列化期间,无法识别的枚举值将保留在消息中,尽管在反序列化消息时如何表示取决于语言。在支持值超出指定符号范围的开放枚举类型的语言(例如 C++ 和 Go)中,未知枚举值只是作为其底层整数表示存储。在 Java 等具有封闭枚举类型的语言中,枚举中的 case 用于表示无法识别的值,并且可以使用特殊访问器访问底层整数。在任何一种情况下,如果消息被序列化,无法识别的值仍将与消息一起序列化。

使用其他消息类型作为字段类型

很好理解,看代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

syntax = "proto3";

message SearchResponse {
  repeated Result results = 1;
}

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

嵌套类型

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

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

要在其父消息类型之外重用此消息类型,类似如下代码:

1
2
3
4
5

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

嵌套消息没有层数限制:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

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

未知字段

未知字段是格式良好的协议缓冲区序列化数据,表示解析器无法识别的字段。例如,当旧二进制文件用新字段解析新二进制文件发送的数据时,这些新字段将成为旧二进制文件中的未知字段。

最初,proto3消息在解析过程中总是丢弃未知字段,但在 3.5 版本中,我们重新引入了未知字段的保留以匹配 proto2 行为。在 3.5 及更高版本中,未知字段在解析过程中保留并包含在序列化输出中。