赞
踩
命名:Protocol Buffers— 协议缓冲区
Protobuf 的诞生之初是为了解决服务端新旧协议(高低版本)兼容性问题,同时被寄予2 个特点:
发展历程:
Protobuf 对外开源是从Protobuf2 开始
性能:
使用:
使用范围:跨平台、跨语言(支持Java, Python, Objective-C, C+, Dart, Go, Ruby, and C#等),可扩展性好
在 proto 中,所有结构化的数据都被称为 message。
syntax = "proto3";
package hello;
message helloworld
{
required int32 id = 1;
required string name = 2;
optional int32 age = 3;
}
如果开头第一行不声明 syntax = “proto3”;,则默认使用 proto2 进行解析。
声明package,来防止命名冲突。 Packages是可选的。
由于一些历史原因,repeated字段并没有想象中那么高效,新版本中允许使用特殊的选项来获得更高效的编码:
repeated int32 samples = 4 [packed=true];
完整数据类型映射—>《支持的全部数据类型》
.proto 类型 | C++类型 | Go 类型 | Java 类型 |
---|---|---|---|
double | double | float64 | double |
int32 | int32 | int32 | int |
int64 | int64 | int64 | long |
sint32 | int32 | int32 | int |
sint64 | int64 | int64 | long |
bool | bool | bool | boolean |
每个消息定义中的每个字段都有唯一的编号。
注意:
通过 reserved 确保删除的字段不会重复使用。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
注意,不能在同一个 reserved 语句中混合字段名称和字段编号。如有需要需要像上面这个例子这样写。
通过完全删除某个字段或将其注释掉来更新消息类型,那么未来的用户可以在对该类型进行自己的更新时重新使用该字段号。如果稍后加载到了的旧版本 .proto 文件,则会导致服务器出现严重问题,例如数据混乱,隐私错误等等。
在 proto3 中,纯数字类型的 repeated 字段编码时候默认采用 packed 编码。
在 message 中可以嵌入枚举类型。
message MyMessage1 {
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
}
枚举类型需要注意的是,一定要有 0 值。
通过设置 allow_alias 为 true,允许将不同的枚举常量指定为相同的值。
Protobuf 使用有2 个前置:
工作流程:
graph LR
.proto文件-->protoc编译器
protoc编译器-->C++/Python/Jave等平台目标文件
C++/Python/Jave等平台目标文件-->文件导入项目
文件导入项目-->引入Google提供的相应库
引入Google提供的相应库-->开始序列化/反序列化
执行protoc命令对.proto文件进行编译。Linux系统通过 help protoc 查看protoc命令的使用详解。
protoc --proto_path=$SRC_DIR --cpp_out=$DST_DIR xxx.proto
–proto_path 有一个别名 -I 。
protobuf采用TLV(tag-length-value)编码格式。
tag值: 由field_number和wire_type两部分组成。
wrie_type | 编码方案 | 编码长度 | 存储方式 | 对应的数据类型 |
---|---|---|---|---|
0 | Varint (负数为ZigZag) | 变长(1-10个字节) | T-V | int32,int64,uint32,uint64,bool enum,int32,int64(负数使用) |
1 | 64-bit | 固定8个字节 | T-V | fixed64,sfixed64,double |
2 | Lenght-deliml | 变长 | T-L-V | string,bytes,repeated |
5 | 32-bit | 固定4个字节 | T-V | fixed32,sfixed32,float |
字段标识号(Field_Number),尽量控制在1-15。超过了则需要2个字节或更多。
在 proto2 中为我们提供了可选的设置 [packed = true],而这一可选项在 proto3 中已成默认设置。
Protobuf 中编码有两种:Varints 和 ZigZag。
ZigZag用于解决varint对负数编码效率低的问题。负数推荐使用 sint32 或 sint64。
Varint 是一种使用一个或多个字节序列化整数的方法,也可以说是一种压缩算法,值越小的数字使用越少的字节数。压缩的依据是:越小的数字,越经常使用。
Varints 的编码规则如下【注:大端字节序下】:
以 150 为例,首先转换为二进制:
1001 0110
7 位一组进行分割:
000 0001, 001 0110
翻转组:
001 0110, 000 0001
每一组最前面插入 msb,除最后一组插入 0 外,其余组插入 1:
1001 0110, 0000 0001
转换为十六进制表示:
96, 01
Varints 的解码就是对编码的逆操作,以 150 的编码结果进行解码为例:
以 96, 01 为例,首先转换为二进制:
1001 0110, 0000 0001
去除每个字节最高位的 msb:
001 0110, 000 0001
翻转:
000 0001, 001 0110
转换回十进制:
128 + 16 +4 + 2 = 150
Zigzag 编码规则
Zigzag 映射函数
整数的补码(十六进制)与hash函数的对应关系如下:
n | hex | h(n) | ZigZag (hex) |
---|---|---|---|
0 | 00 00 00 00 | 00 00 00 00 | 00 |
-1 | ff ff ff ff | 00 00 00 01 | 01 |
1 | 00 00 00 01 | 00 00 00 02 | 02 |
-2 | ff ff ff fe | 00 00 00 03 | 03 |
2 | 00 00 00 02 | 00 00 00 04 | 04 |
… | … | … | … |
-64 | ff ff ff c0 | 00 00 00 7f | 7f |
64 | 00 00 00 40 | 00 00 00 80 | 80 01 |
下面以int32类型的数-2为例,分析它的编码过程。如下图所示:
解码:
#include <iostream> using namespace std; // zigzag 编码 unsigned int zigzag_encode_32(int val) { return (unsigned int)((val<<1)^(val>>31)); } // zigzag解码 int zigzag_decode_32(unsigned int val) { return (int)((val>>1) ^ -(val&1)); } int main() { int n; while(1) { cout <<"\n请输入原码:"; cin >> n; unsigned int zn = zigzag_encode_32(n); int uzn = zigzag_decode_32(zn); cout << "ZigZag编码:" << zn << ", 解码:" << uzn << endl; } return 0; }
执行:
请输入原码:0 ZigZag编码:0, 解码:0 请输入原码:-1 ZigZag编码:1, 解码:-1 请输入原码:1 ZigZag编码:2, 解码:1 请输入原码:-2 ZigZag编码:3, 解码:-2 请输入原码:2 ZigZag编码:4, 解码:2 请输入原码:-3 ZigZag编码:5, 解码:-3 请输入原码:3 ZigZag编码:6, 解码:3 请输入原码:-64 ZigZag编码:127, 解码:-64 请输入原码:64 ZigZag编码:128, 解码:64 请输入原码:-65 ZigZag编码:129, 解码:-65 请输入原码:65 ZigZag编码:130, 解码:65
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。