Google Protobuf 编解码

52次阅读

共计 3372 个字符,预计需要花费 9 分钟才能阅读完成。

Google Protobuf 优点:

在谷歌内部长期使用, 产品成熟度高.
跨语言、支持多种语言, 包括 C++、Java 和 Python.
编码后的消息更小, 更加有利于存储和传输.
编解码的性能非常高.
支持不同协议版本的前向兼容.
支持定义可选和必选字段.

Protobuf 的入门
Protobuf 是一个灵活、高效、结构化的数据序列化框架, 相比与 xml 等传统的序列化工具, 它更小、更快、更简单.
Protobuf 支持数据结构化一次可以到处使用, 甚至跨语言使用, 通过代码生成工具可以自动生成不同语言版本的源代码, 甚至可以在使用不同版本的数据结构进程间进行数据传递, 实现数据结构前向兼容.
定义消息类型
syntax = “proto3”;

message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
该文件的第一行指定使用 proto3 语法, 如果不写的话表示 proto2.
分配字段编号
string query = 1; 1 就是字段编号, 字段号主要用来标识二进制格式字段的. 1 到 15 字段号占一个字节. 16 到 2047 字段号需要两个字节.
我们将对象转换为报文的时候, 是按照字段编号进行报文封装的; 我们接收到数据之后框架会帮我们按照字段号进行赋值.
不能使用数字 19000 到 19999, 因为它们是为 Google Protobuf 保留的.
字段类型对应

.proto Type
Notes
C++ Type
Java Type

double

double
double

float

float
float

int32
使用可变长度编码, 对负数编码效率低下如果您的字段可能有负值, 则使用 sint32 代替.
int32
int

int64
使用可变长度编码, 对负数编码效率低下如果您的字段可能有负值, 则使用 sint64 代替.
int64
long

uint32
使用可变长度编码
uint32
int

uint64
使用可变长度编码
uint64 long

sint32
使用可变长度编码有符号的 int 值这些编码比常规 int32 更有效地编码负数
uint32
int

sint64
使用可变长度编码有符号的 int 值这些编码比常规 int64 更有效地编码负数
int64
long

fixed32
四个字节, 如果值通常大于 2 的 28 次方, 则比 uint32 更有效
uint32
int

fixed64
四个字节, 如果值通常大于 2 的 56 次方, 则比 uint64 更有效
uint64
long

sfixed32
四个字节
int32
int

sfixed64
四个字节
int64
long

bool

bool
boolean

string
字符串必须始终包含 UTF- 8 编码或 7 位 ASCII 文本
string
String

bytes
字符串必须始终包含 UTF- 8 编码或 7 位 ASCII 文本
string
ByteString

默认值

对于字符串, 默认值是空字符串.
对于字节, 默认值为空字节.
对于 bool, 默认值为 false.
对于数字类型, 默认值为零.
对于枚举, 默认值是第一个定义的枚举值, 必须为 0.

还请注意, 如果消息字段设置为默认值, 则该值将不会序列化.
允许嵌套
Protocol Buffers 定义 message 允许嵌套组合成更加复杂的消息
message SearchResponse {
repeated Result results = 1;
}

message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
更多的例子:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}

message SomeOtherMessage {
SearchResponse.Result result = 1;
}
message Outer {// Level 0
message MiddleAA {// Level 1
message Inner {// Level 2
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB {// Level 1
message Inner {// Level 2
int32 ival = 1;
bool booly = 2;
}
}
}
导入定义
可以在文件的顶部添加一个 import 语句:
import “myproject/other_protos.proto”;
未知字段
未知字段就是解析器无法识别的字段. 例如, 当服务端使用新消息发送数据, 客户端使用旧消息解析数据, 那么这些新字段将成为旧消息中的未知字段.
在 3.5 和更高版本中, 未知字段在解析过程中被保留, 并包含在序列化中输出.
Map 类型
repeated 类型可以用来表示数组, Map 类型则可以用来表示字典.
map<key_type, value_type> map_field = N;

map<string, Project> projects = 3;
key_type 可以是任何 int 或者 string 类型 (任何的标量类型, 具体可以见上面标量类型对应表格, 但是要除去 float、double 和 bytes)
枚举值也不能作为 key.
key_type 可以是除去 map 以外的任何类型.
需要特别注意的是:

map 是不能用 repeated 修饰的.
map 迭代顺序的是不确定的, 所以你不能确定 map 是一个有序的.
为 .proto 生成文本格式时, map 按 key 排序. 数字的 key 按数字排序.
从数组中解析或合并时, 如果有重复的 key, 则使用所看到的最后一个 key(覆盖原则). 从文本格式解析映射时, 如果有重复的 key, 解析可能会失败.

Protocol Buffer 虽然不支持 map 类型的数组, 但是可以转换一下, 用以下思路实现 maps 数组:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}

repeated MapFieldEntry map_field = N;
上述写法和 map 数组是完全等价的,所以用 repeated 巧妙的实现了 maps 数组的需求.
Protocol Buffer 命名规范
message 采用驼峰命名法. message 首字母大写开头. 字段名采用下划线分隔法命名.
message SongServerRequest {
required string song_name = 1;
}
枚举类型采用驼峰命名法. 枚举类型首字母大写开头. 每个枚举值全部大写, 并且采用下划线分隔法命名.
enum Foo {
FIRST_VALUE = 0;
SECOND_VALUE = 1;
}
每个枚举值用分号结束, 不是逗号.
服务名和方法名都采用驼峰命名法. 并且首字母都大写开头.
service FooService {
rpc GetSomething(FooRequest) returns (FooResponse);
}
总结
message SubscribeReq {
int32 subReqID = 1;
string userName = 2;
string productName = 3;
string address = 4;
}
默认值
比如我们创建了上面的消息类型, 我们在代码中设置 builder.setSubReqID(0); 为 0, 零是数值类型的默认值; 所以我们会看到序列化后的数据中, 没有对此字段进行序列化.
byte[] arry = builder.build().toByteArray();
arry 长度为 0. 对于字段类型是 string 类型的也是一样的; 也就是说显示赋值默认值也不会对其进行序列化.
保留字段
message SubscribeReq {

reserved 2;

int32 subReqID = 1;
string userName = 2;
string productName = 3;
string address = 4;
}
顾名思义, 就是此字段会被保留可能在以后会使用此字段. 使用关键字 reserved 表示我要保留字段数 2.
上面代码我们在生成 Java 文件的时候会出现 ubscribeReqPeoro.proto: Field “userName” uses reserved number 2 错误信息, 所以我们需要将 string userName = 2; 注释, 或者删除.
保留后我们无法对其设置或序列化和反序列化.

正文完
 0