protobuf文档翻译-安装,数据格式及编码规范
Install
Download protobuf: https://github.com/protocolbuffers/protobuf/releases
unzip protoc-3.8.0-linux-x86_64.zip
sudo cp -r include/* /usr/local/include/
sudo cp bin/protoc /usr/local/bin/
Download Go support for protobuf:
go get -u github.com/golang/protobuf/protoc-gen-go
Download faster and also more customizable Go support:
go get github.com/gogo/protobuf/protoc-gen-gofast
Proto2.0
每个proto文件中可以定义一组message。为了防止依赖关系等泛滥,一般只在一个proto文件中定义一组相关联的message。
定义一个message
message SomeMessage {
[required|optional] type fieldName = fieldNumber [default=10];
}
fieldNumber 从1开始。
注释
注释的语法和C++相同:
/* 这是一段注释 */
// 这也是一段注释
保留字段(Reserved Fields)
当一个字段被删除后,后续的用户可以重新使用这个字段数字或名字, 就导致proto迁移过程中可能造成一些潜藏的bug。可以通过保留声明他们,
将来如果有用户重用了这些字段,就会报错:
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
数据类型
proto | 注释 | Go | Python | C++ | Java |
---|---|---|---|---|---|
double | *float64 | float | double | double | |
float | *float32 | float | float | float | |
int32 | 编码长度是可变的,不能用于编码负数。如果要用于负数,应当使用sint32 | *int32 | int | int32 | int |
int64 | 类似int32,应当使用sint64编码负数 | *int64 | int/log | int64 | long |
uint32 | 变长编码 | *uint32 | int/long | uint32 | int |
uint64 | 变长编码 | *uint64 | int/long | uint64 | long |
sint32 | 变长编码 | *int32 | int | int32 | int |
sint64 | 变长编码 | *int64 | int/long | int64 | long |
fixed32 | 4字节,比uint32更适合编码超过2^28的数字 | *uint32 | int/long | uint32 | int |
fixed64 | 8字节,2^56 | *uint64 | int/long | uint64 | long |
sfixed32 | 4字节 | *int32 | int | int32 | int |
sfixed64 | 8字节 | *int64 | int/long | int64 | long |
bool | *bool | bool | bool | boolean | |
string | 必须是UTF-8编码或是7bit的ASCII | *string | unicode(Py2), str(Py3) | string | String |
bytes | 可以包含任意byte序列 | []byte | bytes | string | ByteString |
注意: 当说到更适合,或应当使用时,并不是说这个类型的表达范围不能够比得上另一种类型,而是其编码长度更有效率。
可选字段和默认值
一个message中可以设定一个字段为optional,当消息解码时发现该字段缺席,就会按照默认值(default)给定值,否则给定该类型的默认值(类似Go),枚举类型则会使用第一个枚举值。
形如:
optional int32 result_per_page = 3 [default=10];
枚举类型
枚举类型使用的是32位整形数,如果使用了负数,编码效率会变低。
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
}
枚举类型中默认第二个值是不能重复的,如果要有重复,那么就要指定允许别名:
enum State {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
保留枚举
如果你删除了枚举中的一个值,那么以后别人就可以重新用它们。可以这样保留这些字段:
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
导入定义
import "myproject/other_proto.proto"
import public "new.proto"
要想让protoc找到它们,需要通过 -I 或 --proto_path 来定义,一般是将项目根目录作为 --proto_path 参数。
类型嵌套
message SearchResponse {
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
repeated Result result = 1;
}
message SomeOtherMessage {
optional SearchResponse.Result result = 1; // 从外部引用一个嵌套类型
}
Proto3.0
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
- singular: 0或1个该字段
- repeated: 可以出现0到多个该字段
proto3中数字类型默认使用packed编码
类型更新
- 不要更改任何现有字段的field number
- 旧message的默认值应当保持不变
- 字段可以移除,在该field number不再使用的前提下
- int32/uint32/int64/uint64/bool 是兼容的,可以任意修改类型,但是可能会出现精度问题等
- sint32与sint64 兼容,但不与其它类型兼容
- string和bytes兼容,前提是它们都是有效的UTF-8编码
- 如果bytes包含了编码的message,嵌套message与bytes兼容
- fixed32 与 sfixed32/fixed64/sfixed64兼容
- enum与 int32/uint32/int64/uint64兼容
- 把单个字段放入一个新的oneof是安全且兼容的;把多个字段放入一个新的oneof 可能 是安全且兼容的,如果你确认没有代码同时设定这多个字段的前提下。 把任何字段放入一个现存的oneof是不安全兼容的。
未知字段
由于proto版本兼容问题,导致旧的废弃字段不能被使用,proto3开始时会直接丢弃它们,但从3.5开始将它们保留
任意类型
任意类型 Any 本质上就是序列化好的bytes类型, 使用该类型需要引入 google/protobuf/any.proto:
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
Oneof
如果message中有若干字段,一次最多只会设置其中的一个字段,那么就可以通过oneof来约定这种关系,类似于C语言的union:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
oneof 的向后兼容
如果检查一个oneof的值返回 None/NOT_SET, oneof可能没有设置值,也可能是版本不同,但我们无法断定这件事。
Maps
map<key_type, value_type> map_field = N;
- map类型字段不可以是 repeated
- 遍历顺序/传输序列化顺序是不可知的
- 从map专程text格式时会对key做排序
- 从传输数据传入或合并时,如果map中的key存在重复,使用最后一个,从text格式处理map时,如果key重复会导致失败
- 如果只提供key但不提供值,那么序列化行为随语言而定
Map类型在传输时等价于:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
Packages
package foo.bar;
message Open { ... }
message Foo {
foo.bar.Open open = 1;
}
在Go中这会被当成包名
Services
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
Options
options不会修改数据定义,但是会影响一些语言生成的行为等等。
optimize_for
这是一个文件级选项,用于设定生成代码的优化方向。
optimize_for [SPEED|CODE_SIZE|LITE_RUNTIME]
SPEED: 默认值,优先生成高效编解码的代码
CODE_SIZE: 利用反射等语言特性生成体积小的代码,但操作可能会更慢
LITE_RUNTIME: 使用一个体积更小,也更少特性(如反射)的运行时来进行编解码
deprecacted
字段选项,用于暗示字段已弃用,新代码不应当再使用它。Java中会加入@Deprecated,其它语言暂不支持。
自定义选项
protobuf允许自定义选项,虽然大部分情况用不上: https://developers.google.com/protocol-buffers/docs/proto.html#extensions
JSON
protobuf 提供了JSON输出格式,部分类型可能与想象有所不同:
proto3 | JSON | JSON Example | Notes |
---|---|---|---|
enum | string | "FOO_BAR" | 默认使用枚举的名字,不过处理时使用int值也是可以的 |
map<K,V> | object | 所有的key都会被转换成字符串 | |
repeated V | array | [v1, ...] | null 会被解析城空的列表 |
bytes | base64 string | "YWJjMT..." | 编码时会自动使用带padding的标准Base64编码成字符串 |
int32/fixed32/uint32 | number | 1, -10, 0 | JSON值会是一个10进制数字,解析时数字或者字符串都可以接受 |
int64/fixed64/uint64 | string | "1", "-10" | 由于64位整数已经超出了JSON number的表示范围,编码解码都是用string |
float/double | number | 1.1, -10.0, "NaN", "Infinity","-Infinity" | 编码时会被做成number或特殊字符串,解码时数字或字符串都可以 |
Any | object | @type暗示了真实的数据类型,如果Any中包含了一个值是特定的原生JSON数据,则会直接被解析出来 | |
Timestamp | string | "1972-01-01T10:00:20.021Z" | RFC3339,尾部小数点后可以有0,3,6,9位数字 |
Duration | string | "1.0002s", "1s" | 尾部必须是s结尾,前面可以使用很多小数精度,但精度范围要在纳秒范围内 |
struct | object | see struct.proto | |
包装类型(别名) | 与实际对应的类型行为保持一致 | ||
Empty | object | {} | 空对象 |
NullValue | null | JSON null |
在渲染JSON时,默认proto3不会输出值与默认值相等的字段,除非特别通过option指定
使用protoc
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
编码规范
文件名使用蛇形:
lower_snake_case.proto
文件格式
- 一行最长80字符
- 2空格缩进
文件结构
- License header
- File overview
- Syntax
- Package
- Imports (sorted)
- File options
- Everything else
重复字段(数组)
使用复数形式:
repeated string keys = 1;
repeated MyMessage accounts = 17;
枚举
枚举使用C语言的标准形式:全大写,下划线
Service
Service使用CamelCase
需要避免的
required和groups,这是给proto2用的,proto3不需要