一种数据描述语言,用于描述一种轻便高效的结构化数据存储格式.
Protobuf可以用于结构化数据串行化,或者说序列化.
Message
syntax = "proto3";
//注释
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
repeated int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
}
:::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编译器将发出提示。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
枚举类型
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}
Corpus enum 的第一个常量映射为零: 每个 enum 定义必须包含一个常量,该常量映射为零作为它的第一个元素。这是因为:
- 必须有一个零值,这样我们就可以使用0作为数值默认值。
- 零值必须是第一个元素,以便与 proto2语义兼容,其中第一个枚举值总是默认值。
其他消息类型
可以使用其他消息类型作为字段类型
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
导入消息定义
上面的例子中,Result消息类型定义在与 SearchResponse相同的文件中,可以直接使用,若不在同一文件中,则需要使用import。
可以通过导入来使用其他.proto文件中的定义。要导入另一个.proto的定义,请在文件顶部添加一个import语句:
import "myproject/other_protos.proto";
当.proto文件中的内容需要移动到新的地方时,可以在旧的文件中使用import public声明新文件的位置
new.proto // 所有的内容都移动到了这个文件中
old.proto // 被其他.proto引用的文件
import public "new.proto"; // 声明新文件的位置
import "other.proto"; // 正常的引用
client.proto // 引用了old.proto的文件
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
定义服务
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}