一种数据描述语言,用于描述一种轻便高效的结构化数据存储格式.
Protobuf可以用于结构化数据串行化,或者说序列化.

Message

  1. syntax = "proto3";
  2. //注释
  3. message SearchRequest {
  4. required string query = 1;
  5. optional int32 page_number = 2;
  6. repeated int32 result_per_page = 3;
  7. enum Corpus {
  8. UNIVERSAL = 0;
  9. WEB = 1;
  10. IMAGES = 2;
  11. LOCAL = 3;
  12. NEWS = 4;
  13. PRODUCTS = 5;
  14. VIDEO = 6;
  15. }
  16. }

:::warning 字段规则 类型 名称 = 字段编号 :::

syntax: 用于指定proto3语法.若省略默认使用proto2的语法
message: 定义一个类似于结构体的message,其中指定了三个字段

字段类型

类型 说明 默认值
double 0
float 0
int32 使用可变长度编码。编码负数的效率低 - 如果您的字段可能有负值,请改用sint32。 0
int64 使用可变长度编码。编码负数的效率低 - 如果您的字段可能有负值,请改用sint64。 0
uint32 使用可变长度编码 0
uint64 使用可变长度编码 0
sint32 使用可变长度编码。签名的int值。这些比常规int32更有效地编码负数 0
sint64 使用可变长度编码。签名的int值。这些比常规int64更有效地编码负数 0
fixed32 总是四个字节。如果值通常大于228,则比uint32更有效 0
fixed64 总是八个字节。如果值通常大于256,则比uint64更有效 0
sfixed32 总是四个字节 空字节
sfixed64 总是八个字节 空字节
bool false
string 字符串必须始终包含UTF-8编码或7位ASCII文本,且不能超过232 空字符串
bytes 可以包含不超过232的任意字节序列 空字节

字段编号

在message定义中每个字段都有一个唯一的编号,message编码成二进制消息体时字段编号1-15将会占用1个字节,16-2047将占用两个字节。用于二进制消息体中识别你定义的这些字段,不能重复,已经被调用之后不能修改

字段的规则

  • singular:一个格式良好的消息可以有零或一个这个字段(但不能超过一个)。而这是 proto3 语法的默认字段规则。

  • repeated:这个字段可以在一条格式良好的信息中重复任何次数(包括零)。重复值的顺序将被保留。

在 proto3 中,标量数字类型的重复字段默认使用 packed 编码。

保留字段

如果你通过完全删除字段或将其注释掉来更新消息类型,那么未来的用户在对该类型进行自己的更新时可以重用字段号。如果其他人以后加载旧版本的相同.proto文件,这可能会导致严重的问题,包括数据损坏,隐私漏洞等等。确保这种情况不会发生的一种方法是指定已删除字段的字段编号(和/或名称,这也可能导致 JSON 序列化问题)是保留的(reserved)。如果将来有任何用户尝试使用这些字段标识符,protocol buffer编译器将发出提示。

  1. message Foo {
  2. reserved 2, 15, 9 to 11;
  3. reserved "foo", "bar";
  4. }

枚举类型

  1. message SearchRequest {
  2. string query = 1;
  3. int32 page_number = 2;
  4. int32 result_per_page = 3;
  5. enum Corpus {
  6. UNIVERSAL = 0;
  7. WEB = 1;
  8. IMAGES = 2;
  9. LOCAL = 3;
  10. NEWS = 4;
  11. PRODUCTS = 5;
  12. VIDEO = 6;
  13. }
  14. Corpus corpus = 4;
  15. }

Corpus enum 的第一个常量映射为零: 每个 enum 定义必须包含一个常量,该常量映射为零作为它的第一个元素。这是因为:

  1. 必须有一个零值,这样我们就可以使用0作为数值默认值。
  2. 零值必须是第一个元素,以便与 proto2语义兼容,其中第一个枚举值总是默认值。

其他消息类型

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

  1. message SearchResponse {
  2. repeated Result results = 1;
  3. }
  4. message Result {
  5. string url = 1;
  6. string title = 2;
  7. repeated string snippets = 3;
  8. }

导入消息定义

上面的例子中,Result消息类型定义在与 SearchResponse相同的文件中,可以直接使用,若不在同一文件中,则需要使用import。

可以通过导入来使用其他.proto文件中的定义。要导入另一个.proto的定义,请在文件顶部添加一个import语句:

  1. import "myproject/other_protos.proto";

当.proto文件中的内容需要移动到新的地方时,可以在旧的文件中使用import public声明新文件的位置

  1. new.proto // 所有的内容都移动到了这个文件中
  2. old.proto // 被其他.proto引用的文件
  3. import public "new.proto"; // 声明新文件的位置
  4. import "other.proto"; // 正常的引用
  5. client.proto // 引用了old.proto的文件
  6. import "old.proto";

更新消息类型

如果现有的消息类型不再满足你的所有需要——例如,你希望消息格式有一个额外的字段——但是你仍然希望使用用旧格式创建的代码,不要担心!在不破坏任何现有代码的情况下更新消息类型非常简单,只需记住以下规则:

  • 不要改变已有字段的字段编号
  • 当你增加一个新的字段的时候,老系统序列化后的数据依然可以被你的新的格式所解析,只不过你需要处理新加字段的缺省值。 老系统也能解析你信息的值,新加字段只不过被丢弃了
  • 字段也可以被移除,但是建议你Reserved这个字段,避免将来会使用这个字段
  • int32, uint32, int64, uint64 和 bool类型都是兼容的
  • sint32 和 sint64兼容,但是不和其它整数类型兼容
  • string 和 bytes兼容,如果 bytes 是合法的UTF-8 bytes的话
  • 嵌入类型和bytes兼容,如果bytes包含一个消息的编码版本的话
  • fixed32和sfixed32, fixed64和sfixed64
  • enum和int32, uint32, int64, uint64格式兼容
  • 把单一一个值改变成一个新的oneof类型的一个成员是安全和二进制兼容的。把一组字段变成一个新的oneof字段也是安全的,如果你确保这一组字段最多只会设置一个。把一个字段移动到一个已存在的oneof字段是不安全的

修改测试
仅修改客户端proto文件:
新增字段:兼容
修改字段类型:发送成功,但接受端收到空值
修改字段名: ok

客户端和服务端都修改:
新增字段:ok
修改字段类型: ok
修改字段名: ok

定义服务

  1. service SearchService {
  2. rpc Search(SearchRequest) returns (SearchResponse);
  3. }