Protobuf语法

官方文档

一. 定义消息类型

.proto文件定义消息类型

syntax = "proto3";
// option go_package = ".;pb";
/* 查询请求*/
message SearchRequest {
      reserved 4, 15, 9 to 11;
      string query = 1; // 查询
      int32 page_number = 2;
      int32 result_per_page = 3;
}

message SearchResponse {
      repeated Result results = 1;
}

message Result {
      string url = 1;
      string title = 2;
      repeated string snippets = 3;
}
  • 第一行定义了使用的protobuf的协议的版本,如果不设置,则为proto2版本. 文件第一行必须非空,非注释行

  • 可选定义go的包名

  • 采用C/C++风格的注释,即使///**/.

  • SearchRequest定义了三个域(名称/值对),每个字段都有一个名称和一个类型

    • 每个字段都有对应的标量类型( [scalar types](#二. 标量类型(scalar types)))

    • 每一个字段都定有唯一数值编号. 这些字段用于标识二进制编码的域位置,一旦定义并使用后,不应该再修改.

      • 字段数值编号1-15只使用一个字节进行编码,包括字段数值编号和字段类型.

      • 字段数值编号16-2047将使用两个字节进行编码

      • 最小的字段编号为1,最大2^29 - 1, or 536,870,911.

      • 19000-19999 (FieldDescriptor::kFirstReservedNumber-FieldDescriptor::kLastReservedNumber)为protobuf保留字段,不可使用.

        对于频繁出现的消息最好使用1-15,切记为将来可能添加的频繁出现的元素留出一些空间

    • 字段规则

      • singular: 字段为0或1个,这是默认规则.
      • repeated: 该字段可以在格式良好的消息中重复任意次数(包括0),重复值的顺序将保留. 在proto3中, 默认情况下, 标量数字类型的repeated字段使用打包编码
  • 保留字段reserved, 可以采用保留编号或保留字段名的方式,进行保留, 当删除一些旧的编号或字段时,可以防止后续使用,引起不必要的bug.

  • 可以使用其它消息类型作为字段. 如ResultSearchResponse里使用

  • 使用 protocol buffer compiler 编译将产生对应的代码

二. 标量类型(scalar types)

scalar types

.protoNotesC++GoDart
doubledoublefloat64double
floatfloatfloat32double
int32使用变长编码,负数编码效率低下,如果字段可能有负数,使用sint32int32int32int
int64使用变长编码,负数编码效率低下,如果字段可能有负数,使用sint64int64int64Int64
uint32使用变长编码uint32uint32int
uint64使用变长编码uint64uint64Int64
sint32使用变长编码。有符号的整型。它们比int32编码负数效率更高int32int32int
sint64使用变长编码。有符号的整型。它们比int64编码负数效率更高int64int64Int64
fixed32固定4字节编码,如果值大于2^28比uint32编码效率更高uint32uint32int
fixed64固定8字节编码,如果值大于2^68比uint32编码效率更高uint64uint64Int64
sfixed32固定4字节int32int32int
sfixed64固定8字节int64int64Int64
boolboolboolbool
stringutf-8或ASCII编码,长度不应超过2^32stringstringString
bytes包含任意的字节,长度不应超过2^32string[]byteList

三. 默认值

解析消息时, 如果编码的消息不包含特定的单数元素, 则已解析对象中的相应字段将设置为该字段的默认值。 这些默认值是特定于类型的:

  • string: 空字符串
  • bytes: 空字节流.
  • bools: false.
  • numeric types: 0
  • enums: 枚举的第一个值,且必须为0.
  • message fields: 取决于所用的语言(See generated code guide)

四. 枚举

message SearchRequest {
      string query = 1;
      int32 page_number = 2;
      int32 result_per_page = 3;
      enum Corpus {
            option allow_alias = true;
            UNIVERSAL = 0;
            WEB = 1;
            IMAGES = 2;
            LOCAL = 3;
            AliasLocal = 3,
      }
      Corpus corpus = 4;
}

如您所见, Corpus枚举的第一个常量映射为零:每个枚举定义必须包含一个映射为零的常量作为其第一个元素。 这是因为:

  • 必须有一个零值, 以便我们可以使用0作为数字默认值。
  • 零值必须是第一个元素, 以便与proto2语义兼容, 其中第一个枚举值始终是默认值。
  • 您可以通过将相同的值分配给不同的枚举常量来定义别名. 首先你得设置allow_aliastrue
  • 枚举值的范围为32位整型,采用 varint encoding 编码,不建议采用负数.

五. 导入定义

import "myproject/other_protos.proto";

默认情况下, 您只能使用直接导入的.proto文件中的定义。 但是, 有时您可能需要将.proto文件移动到新位置。

现在, 您可以直接在原始位置放置一个虚拟的.proto文件, 而不是直接移动.proto文件并一次更改所有呼叫站点, 而是使用导入import public将所有导入转发到新位置。

任何导入包含导入import public的原型的人都可以可传递地依赖导入import public依赖项。 例如

// 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

协议编译器使用-I /-proto_path标志在协议编译器命令行中指定的一组目录中搜索导入的文件。

如果未给出任何标志, 它将在调用编译器的目录中查找。 通常, 应将--proto_path标志设置为项目的根目录, 并对所有导入使用完全限定的名称。

六. 其它内置类型定义

1. Any

Any消息类型使您可以将消息用作嵌入式类型, 而无需定义它们的.proto

Any包含任意序列化消息(以字节为单位)以及URL, URL作为该消息的类型并解析为该消息的类型的全局唯一标识符。

import "google/protobuf/any.proto";

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

2. Oneof

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

可以添加任何字段到oneof,除了maprepeated

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

3. Maps

如果要在数据定义中创建关联映射, 则协议缓冲区提供了方便的快捷方式语法:

map<key_type, value_type> map_field = N;
  • key_type可以是任何integral或string类型(除了float和bytes之外),enum不是有效的key_type
  • value_type可以是除Maps以外的任何类型。
map<string, Project> projects = 3;
  • 映射字段不能重复。

  • map值的编码顺序和迭代顺序是未定义的, 因此您不能依赖于map处于特定的顺序。

  • 当为.proto生成文本格式时, 地图将按键排序。 数字键按数字排序。

  • 从数据解析或合并时, 如果存在重复的映射键, 则使用最后看到的键。 从文本格式解析时, 如果键重复, 则解析可能会失败。

  • 如果为映射字段提供键但没有值, 则序列化字段时的行为取决于语言。 在C ++, Java和Python中, 类型的默认值是序列化的, 而在其他语言中, 则没有序列化的值。

七. 包定义

可以在.proto文件中添加可选的包说明符, 以防止协议消息类型之间的名称冲突。

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

在Go中, 除非您在.proto文件中明确提供了go_package选项, 否则该包将用作Go包名称

八. 定义服务

如果要将消息类型与RPC(远程过程调用)系统一起使用, 则可以在.proto文件中定义RPC服务接口, 并且协议protocolbuf编译器将以您选择的语言生成服务接口代码和存根。

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

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