菜鸟笔记
提升您的技术认知

protobuf入门

protobuf3

概述

protobuf协议2和3有一定的区别,但是可以混用(除了枚举)。

基本结构

syntax = "proto3"; //定义协议类型,注释方式和c/c++注释方式一样,还支持/**/
option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";// 指定生成的代码的路径
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

注意:跟着的数字为对应消息的位置,一旦定义不要更改。

关键字:

  • singular: 默认字段,表示该类型字段可以有1个或0个该字段。

  • optional: 功能与singular一样。(在proto2还有一个required,代表必须有该值,不过现在已经全部在初始状态赋值默认值了)

  • repeated: 该字段可以重复0次到多次,相当于切片

  • map: 键值对。

  • reserved: 保留字段,可以防止某些字段被再次使用。在更新新功能时可以将某些字段/标识符保留(reserved),这样就不会被重新使用了,protoc会检查。

  • 另:proto2可以设置默认值,在proto2以字段类型默认值替换。

编译指令:

 protoc --proto_path=. --go_out=paths=source_relative:. ./proto/person.proto //指定编译的文件到proto文件当前位置

保留字段:

枚举类型适用于提供一组预定义的值,选择其中一个。如将性别定义为枚举。

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

字段类型

.proto Type Notes C++ Type Java/Kotlin Type[1] Python Type[3] Go Type
double double double float float64
float float float float float32
int32 编码负数效率低下—— 如果您的字段可能有负值,请改用 sint32 int32 int int int32
int64 编码负数效率低下—— 如果您的字段可能有负值,请改用 sint64 int64 long int/long[4] int64
uint32 uint32 int[2] int/long[4] uint32
uint64 uint64 long[2] int/long[4] uint64
sint32 int32 int int int32
sint64 int64 long int/long[4] int64
fixed32 总是四个字节。 如果值通常更大,则比 uint32 更有效 比 2^28 。 uint32 int[2] int/long[4] uint32
fixed64 总是四个字节。 如果值通常更大,则比 uint32 更有效 比 2^56 。 uint64 long[2] int/long[4] uint64
sfixed32 总是四个字节。 int32 int int int32
sfixed64 总是八个字节。 int64 long int/long[4] int64
bool bool boolean bool bool
string 字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本,并且不能 长于2^32。 string String str/unicode[5] string
bytes 可能包含任何不超过任意长度的字节序列2^32。 string ByteString str (Python 2) bytes (Python 3) []byte

默认值:

  • 对于字符串,默认值为空字符串。

  • 对于字节,默认值为空字节。

  • 对于布尔值,默认值为 false。

  • 对于数字类型,默认值为零。

  • 对于 枚举 ,默认值是第 一个定义的枚举值 , 必须为 0。

枚举:

第一个选项的值必须为0(在proto2里面不必须为0),且不应为负数(负数编码效率低下)

enum Corpus {
  CORPUS_UNSPECIFIED = 0;
  CORPUS_UNIVERSAL = 1;
  CORPUS_WEB = 2;
  CORPUS_IMAGES = 3;
  CORPUS_LOCAL = 4;
  CORPUS_NEWS = 5;
  CORPUS_PRODUCTS = 6;
  CORPUS_VIDEO = 7;
}

枚举也支持reserved

嵌套类型:

message SearchResponse {
  message Result { // 可以在类型里面定义类型
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

嵌套类型可以被其他对象使用:

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

更新字段

  • 不要更改任何现有字段的字段编号。
  • 如果添加新字段,任何使用您的“旧”字段通过代码序列化的消息 新生成的代码仍然可以解析消息格式。解析的时候自动忽略旧字段即可。
  • 可以删除字段,但是保证该编号不再被使用。可以重命名字段,但是编码能被修改。
  • int32, uint32, int64, uint64, 和 bool都是兼容的。如果位数不够会把多余的截断。
  • sint32sint64彼此兼容但和其他整数类型兼容。
  • stringbytes只要字节是有效的 UTF-8 就兼容。
  • 为了 string, bytes和消息字段, optional兼容 repeated. 给定重复字段的序列化数据作为输入,客户端 期望这个领域是 optional将采用最后一个输入值,如果它是 原始类型字段或合并所有输入元素(如果它是消息类型) 场地。 请注意,这 对于数字类型通常不安全 ,包括 布尔值和枚举。 数字类型的重复字段可以序列化在 打包 格式, 当一个 optional预计领域。
  • enum兼容 int32, uint32, int64, 和 uint64就而言 有线格式(请注意,如果不合适,值将被截断)。 但是请注意,当 消息被反序列化:例如,无法识别的 proto3 enum类型将 被保留在消息中,但是当消息是 反序列化依赖于语言。 Int 字段总是只保留它们的 价值。
  • 将单个可选字段或扩展更改为一个新字段或扩展的成员是二进制兼容的,但是对于某些语言(特别是Go),生成的代码的API将以不兼容的方式更改。因此,如AIP-180中所述,谷歌不会在其公共api中进行此类更改。同样要注意源代码兼容性,如果您确定没有代码一次设置多个字段,那么将多个字段移动到一个新的字段中可能是安全的。将字段移动到现有的字段中是不安全的。同样,将单个字段之一更改为可选字段或扩展是安全的。

未知领域

对无法解析的字段,现在会携带并不会被丢弃。

Any

Any消息类型将消息用作嵌入式类型,而无需 他们的 .proto 定义。 一个 Any包含任意序列化消息作为 bytes,连同充当全球唯一标识符的 URL 和 解析为该消息的类型。 使用 Any类型,你需要 import google/protobuf/any.proto.

import "google/protobuf/any.proto";

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

oneof

如果你有一个带有多个字段的消息,并且同时最多只能设置一个字段,你可以通过使用oneof特性强制执行此行为并节省内存。

除了共享内存中的所有字段外,其中一个字段就像普通字段一样,并且最多可以同时设置一个字段。设置其中的任何成员将自动清除所有其他成员。您可以使用特殊case()或whohoneof()方法检查其中一个中的哪个值被设置了(如果有的话),这取决于您选择的语言。

注意,如果设置了多个值,最后一个由proto中顺序决定的值将覆盖之前的所有值。

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

map

map<key_type, value_type> map_field = N;

key_type必须是系统默认类型(出了枚举类型)

map不能有repeated,不能保证取值顺序,

定义服务

定义一个rpc服务:

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

JSON映射

proto3协议支持json映射。

发出具有默认值的字段

忽略未知字段

使用proto字段名而不是lowerCamelCase 名称

将枚举值作为整数而不是字符串发出 :枚举的名称 值在 JSON 输出中默认使用。 可以提供一个选项来使用 枚举值的数值代替。

option

option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";

支持go语言的导出包的路径和包名设置,其他有需要的语言(比如java)也有相应的字段。

编译

指令:

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 --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
  • IMPORT_PATH 指定在解析导入指令时查找.proto文件的目录。如果省略,则使用当前目录。可以通过多次传递——proto_path选项来指定多个导入目录;他们将按顺序被搜查。-I=_IMPORT_PATH_可以用作——proto_path的简写形式。