kpyes 发表于 2024-1-20 15:16

深入protobuf(Protocol Buffers)道理:简化你的数据序列化

深入protobuf(Protocol Buffers)道理:简化你的数据序列化
一、protobuf 概述

Protocol buffers 是⼀种语⾔中⽴,平台⽆关,可扩展的序列化数据的格式,可⽤于通信协议,数据存储 等。Protocol buffers 在序列化数据具有灵活、⾼效的特点。
相⽐于 XML 来说,Protocol buffers 更加 ⼩巧,更加快速,更加简单。⼀旦定义了要措置的数据的数据布局之后,就可以利⽤ Protocol buffers 的 代码⽣成⼯具⽣成相关的代码。甚⾄可以在⽆需从头部署法式的情况下更新数据布局。只需使⽤ Protobuf 对数据布局进⾏⼀次描述,即可利⽤各种分歧语⾔或从各种分歧数据流中对你的布局化数据轻松 读写。
Protocol buffers 很适合做数据存储或 RPC 数据交换格式。可⽤于通讯协议、数据存储等范围的语⾔⽆ 关、平台⽆关、可扩展的序列化布局数据格式。

Protocol buffers在游戏和即时通信用的斗劲多。使用常见分析:
协议场景举例xml主要在当地使用UI,游戏信息jsonhttp apiHTTP网页注册账户protobuf处事与处事的长途调用rpc,游戏,即时通讯,tars brpc二、protobuf 协议的⼯作流程

要使用protobuf序列化方式,要先编写proto文件。
syntax=”proto3”;                                         // 版本,proto2和proto3
package IM.Login;                                        // 类似CPP的定名空间
import ”IM.BaseDefine.proto”;                // 引用其他的proto文件
option optimize_for = LITE_RUNTIME;        // 编译优化

// 一个类
message IMLoginReq{
        // 各种字段
        string user_name=1;
        string password=2;
        IM.BaseDefine.UserStatType online_status=3;
        IM.BaseDefine.ClientType client_type=4;
        string client_version=5;
}
然后操作东西生成.cc和.h文件。
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/*.proto
最后让法式调用。
proto文件在发送端和接收端是公用的,及发送端和接收端使用的是同样的proto文件。



IDL是Interface description language的缩写,指接⼝描述语⾔。
可以看到,对于序列化协议来说,使⽤⽅只需要存眷业务对象本身,即IDL定义(.proto),序列化和反序 列化的代码只需要通过⼯具⽣成即可。
protobuf不能完全替代json,比如对外注册,json只需要把格式提供给对方,而protobuf还需要一些复杂的流程,会降低可读性。
同一个proto文件可以生成分歧的语言。



视频讲解

B站千万级弹幕通信协议protobuf工程实践
高性能处事器通信协议设计之xml-json-protobuf对比分析
LinuxC++高级全栈开发免费学习地址
【文章福利】:小编整理了一些个人感觉斗劲好的学习册本、视频资料共享在群文件里面,有需要的可以自行添加哦!~点击832218493插手(需要自取)




三、protobuf 的编译安装及使用

⾕歌开源的协议尺度+⼯具。
安装⼯具 —> 按照编写的proto⽂件产⽣c++代码。
(1)下载。
wget https://github.com/protocolbuffers/protobuf/releases/download/v21.7/protobuf-cpp-3.21.7.tar.gz
(2)解压。
tar zxvf protobuf-cpp-3.21.7.tar.gz
(3)编译。时间可能会有点长。
cd protobuf-3.21.7/
./configure
make
sudo make install
sudo ldconfig
(4)查看版本信息。
protoc --version
(5)编写proto文件。
示例:
syntax=”proto3”;                                         // 版本,proto2和proto3
package IM.Login;                                        // 类似CPP的定名空间
//import ”IM.BaseDefine.proto”;                // 引用其他的proto文件
option optimize_for = LITE_RUNTIME;        // 编译优化

// 一个类
message IMLoginReq{
        // 各种字段
        string user_name=1;
        string password=2;
        //IM.BaseDefine.UserStatType online_status=3;
        //IM.BaseDefine.ClientType client_type=4;
        string client_version=5;
}
(6)将proto文件生成相应的.http://pb.cc文件和.pb.h。
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/*.proto
SRC_DIR是.proto地址的路径。DST_DIR是.cc和.h生成的位置。
示例:
将指定proto⽂件⽣成.http://pb.cc和.pb.h 。
protoc -I=./ --cpp_out=./ test.proto
将对应⽬录的所有proto⽂件⽣成.http://pb.cc和.pb.h
protoc -I=./ --cpp_out=./ *.proto
(7)编译典型。
g++ -std=c++11 -o list_people list_people.cc addressbook.pb.cc -lprotobuf - lpthread
注意要有-lprotobuf来指定库。
protobuf option部门选项:
option optimize_for = LITE_RUNTIME;
optimize_for是⽂件级此外选项,Protocol Buffer定义三种优化级别 :PEED/CODE_SIZE/LITE_RUNTIME。缺省情况下是SPEED。

[*]SPEED: 暗示⽣成的代码运⾏效率⾼,但是由此⽣成的代码编译后会占⽤更多的空间。
[*]CODE_SIZE: 和SPEED恰恰相反,代码运⾏效率较低,但是由此⽣成的代码编译后会占⽤更少的空 间,凡是⽤于资源有限的平台,如Mobile。
[*]LITE_RUNTIME: ⽣成的代码执⾏效率⾼,同时⽣成代码编译后的所占⽤的空间也是⾮常少。这是以牺牲Protocol Buffer提供的反射功能为代价的。因此我们在C++中链接Protocol Buffer库时仅需链接 libprotobuf-lite,⽽⾮libprotobuf。
-rw-r--r--1 root root   89501198 10月 13 16:02 libprotobuf.a
-rwxr-xr-x1 root root      986 10月 13 16:02 libprotobuf.la*
-rw-r--r--1 root root   15320786 10月 13 16:02 libprotobuf-lite.a
-rwxr-xr-x1 root root       1021 10月 13 16:02 libprotobuf-lite.la*
lrwxrwxrwx1 root root         26 10月 13 16:02 libprotobuf-lite.so -> libprotobuf-lite.so.32.0.7*
lrwxrwxrwx1 root root         26 10月 13 16:02 libprotobuf-lite.so.32 -> libprotobuf-lite.so.32.0.7*
-rwxr-xr-x1 root root    5827448 10月 13 16:02 libprotobuf-lite.so.32.0.7*
lrwxrwxrwx1 root root         21 10月 13 16:02 libprotobuf.so -> libprotobuf.so.32.0.7*
lrwxrwxrwx1 root root         21 10月 13 16:02 libprotobuf.so.32 -> libprotobuf.so.32.0.7*
-rwxr-xr-x1 root root   33984952 10月 13 16:02 libprotobuf.so.32.0.7*
-rw-r--r--1 root root130421776 10月 13 16:02 libprotoc.a
-rwxr-xr-x1 root root       1002 10月 13 16:02 libprotoc.la*
lrwxrwxrwx1 root root         19 10月 13 16:02 libprotoc.so -> libprotoc.so.32.0.7*
lrwxrwxrwx1 root root         19 10月 13 16:02 libprotoc.so.32 -> libprotoc.so.32.0.7*
-rwxr-xr-x1 root root   43255928 10月 13 16:02 libprotoc.so.32.0.7*

四、protobuf 标量数值类型

⼀个标量动静字段可以含有⼀个如下的类型——该表格展示了定义于.proto⽂件中的类型,以及与之对应 的、在⾃动⽣成的访谒类中定义的类型:
.proto TypeNotesC++ TypeJava TypeGo Typedoubledoubledoublefloat64floatfloatfloatfloat32int32使⽤变⻓编码,对于负值的效率很低,如果你的域 有可能有负值,请使⽤sint64替代int32intint32uint32使⽤变⻓编码uint32intuint32uint64使⽤变⻓编码uint64longuint64sint32使⽤变⻓编码,这些编码在负值时⽐int32⾼效的多int32intint32sint64使⽤变⻓编码,有符号的整型值。编码时⽐凡是的 int64⾼效。int64longint64fixed32总是4个字节,如果数值总是⽐总是⽐228228⼤的 话,这个类型会⽐uint32⾼效。uint32intuint32fixed64总是8个字节,如果数值总是⽐总是⽐2^56⼤的 话,这个类型会⽐uint64⾼效。uint64longuint64sfixed32总是4个字节int32intint32sfixed64总是8个字节int64longint64boolboolbooleanboolstring⼀个字符串必需是UTF-8编码或者7-bit ASCII编 码的⽂本。stringStringstringbytes可能包含任意挨次的字节数据。stringByteString[]byte变长编码:值小的时候,减少暗示字节数。
五、protobuf的编码道理

主要说明varints和zigzag。
只讲解重点道理;把上面的各种变量类型归为6大类,除去官方不再保举的deprecated还有四大类。
protobuf的高效表示在:
(1)解析高效。
(2)字节数占用少。
TypeMeaningUsed For0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum164-bitfixed64, sfixed64, double2Length- delimited (⻓度分割)string, bytes, embedded messages, packed repeated fields3Start groupgroups (deprecated)4End groupgroups (deprecated)532-bitfixed32, sfixed32, float总结下来就是:
(1)变长编码类型Varints。
(2)固定32 bits类型。
(3)固定64 bits类型。
(4)有长度标识表记标帜类型。
5.1、Varints 编码(变⻓的类型才使⽤)

为什么设计变长编码: 普通的int数据类型,无论其值的大小,所占用的存储空间都是相等的。⽐如 不管是0x12345678 还是0x12都占⽤4字节,那能否让0x12在暗示的时候只占⽤1个字节呢?是否可以按照数值的⼤⼩来动态地占⽤存储空间, 使得值⽐较⼩的数字占⽤较少的字节数, 值相对⽐ 较⼤的数字占⽤较多的字节数, 这便是变⻓整型编码的基本思想。
采⽤变⻓整型编码的数字, 其占⽤的字节数不是完全⼀致的, Varints 编码使⽤每个字节的最⾼有效 位作为标识表记标帜位, ⽽残剩的 7 位以⼆进制补码的形式来存储数字值本身, 当最⾼有效位为 1 时, 代表其 后还跟有字节, 当最⾼有效位为 0 时, 代表已经是该数字的最后的⼀个字节。
在 Protobuf 中, 使⽤的是 Base128 Varints 编码, 在这种⽅式中, 使⽤ 7 bit (即7的2次⽅为128) 来存储数字, 在 Protobuf 中, Base128 Varints 采⽤的是⼩端序(即数字的低位存放在⾼地址)。
举例 来看, 对于数字 1, 假设 int 类型占 4 个字节, 以尺度的整型存储, 其⼆进制暗示应为:
00000000 00000000 00000000 00000001
可⻅, 只有最后⼀个字节存储了有效数值, 前 3 个字节都是 0, 若采⽤ Varints 编码, 其⼆进制形式为:
00000001
因为其没有后续字节, 因此其最⾼有效位为 0, 其余的 7 位以补码形式存放 1。
再⽐如数字 666, 其以 尺度的整型存储, 其⼆进制暗示为:
00000000 00000000 00000010 10011010
若采⽤ Varints 编码, 其⼆进制形式为:
10011010 00000101
还原可以得到正确的值:
00000010 10011010
从上⾯的编码解码过程可以看出, 可变⻓整型编码对于分歧⼤⼩的数字, 其所占⽤的存储空间是 分歧的。
通俗的说:
每个字节用7bit暗示数值的信息,用1 bit标识表记标帜结束(1暗示没有结束,0暗示结束,也就是最后一个字节的位置)。编码时从低位开始取7bit,放在高位。还原时从高位取,放到低位。
那么,如果一个值很大,比如0xFFFFFFFF,需要多少字节存储呢?
0xFFFFFFFF需要分配32个bit,使⽤base128 Varints 编码需要的字节数:32/7=4.57, 只要 不整除就要进位, 就是需要5个字节存储。从这⾥看得出来,小于等于28bit的整数适合使⽤变⻓编码, 如果整数都是32bit>= 变量 >28bit可以考虑使⽤fixed32, sfixed32等固定4字节的类型。
5.2、ZigZag 编码(针对负数的)

Varints 编码的本色在于去掉数字开头的 0, 因此可缩短数字所占的存储字节数, 在上⾯的例⼦ 中, 只举例说明了正数的 Varints 编码, 但如果数字为负数, 则采⽤ Varints 编码会恒定占⽤ 10 个字 节, 原因在于负数的符号位为 1, 对于负数其从符号位开始的⾼位均为 1, 在 Protobuf 的具体实现中, 会将此视为⼀个很⼤的⽆符号数, 以C++语⾔的实现为例, 对于 int32 类型的 pb 字段, 对于如下 定义的 proto:
message Tint32{
        int32 n1 = 1;
}
Request 中包含类型为 int32 类型的字段, 当 a 为负数时, 其序列化之后将恒定占⽤ 10 个字节。
比如 对于 int32 类型的数字 -5, 其序列化之后的⼆进制为:
11111011 11111111 11111111 1111111 11111111 11111111 11111111 00000001
其原因在于 Protobuf 的内部将 int32 类型的负数转换为 uint64 来措置。
// 取 5 的 原 码 :
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
// 得 反 码 :
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111010
// 对 反 码 加 1 最 后 得 补 码 :
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111011
// 即 -5 在 计 算 机 ⾥ ⽤ ⼆ 进 制 表 示 结 果
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111011
转成每7bit占⽤1个字节:
//( ⾼ 位 )
1 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111011
//(低位)
然后⾼地址存储到低地址,而且不是结束字节最⾼位为1,便是 :
// (低位)
11111011 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 00000001
//(⾼位)
转成16进制:fb ff ff ff ff ff ff ff ff 01 数据本身就占⽤了10字节。
转换后的 uint64 数值的⾼位全为 1, 相当于是⼀个 8 字节的很⼤的⽆符号数, 因此采⽤ Base128 Varints 编码后将恒定占⽤ 10 个字节的空间, 可⻅ Varints 编码对于暗示负数毫⽆优势, 甚⾄⽐普通 的固定 32 位存储还要多占 4 个字节。Varints 编码的本色在于设法移除数字开头的 0 ⽐特, ⽽对于 负数, 由于其数字⾼位都是 1, 因此 Varints 编码在此场景下掉效, Zigzag 编码便是为了解决这个问 题, Zigzag 编码的⼤致思想是⾸先对负数做⼀次变换, 将其映射为⼀个正数, 变换以后便可以使⽤ Varints 编码进⾏压缩, 这⾥关键的⼀点在于变换的算法, ⾸先算法必需是可逆的, 即可以按照变换后 的值计算出原始值, 否则就⽆法解码, 同时要求变换算法要尽可能简单, 以避免影响 Protobuf 编码、 解码的速度。
sint32 = Zigzag 编码 +varints编码合起来。
sint32 序列化:负数 -> Zigzag 编码 -> varints编码。
sint32 反序列化:varints解码 -> Zigzag 解码 -> 负数 。
重点在于:同样是暗示-5,sint32只需要2个字节,int32需要11字节。
对于Zigzag的算法不必太细究。其⽬的是把多个1转成多个0暗示。
5.3、protobuf 的数据组织

⾸先来看⼀个例⼦,假设客户端和处事端使⽤ protobuf 作为数 据交换格式, proto 的具体定义为:
syntax = ”proto3”;
package pbTest;
message Request {
        int32 age = 1;
}
Request 中包含了⼀个名称为 age 的字段, 客户端和处事端双⽅都⽤同⼀份不异的 proto ⽂件是没有任 何问题的, 假设客户端⾃⼰将 proto ⽂件做了改削, 改削后的 proto ⽂件如下:
syntax = ”proto3”;
package pbTest;
message Request {
        int32 age_test = 1;
}
在这种情形下, 处事端不改削应⽤法式仍能够正确地解码,原因在于序列化后的 Protobuf 没有使⽤ 字段名称,⽽仅仅采⽤了字段编号。
与 json xml 等相⽐,Protobuf 不是⼀种完全⾃描述的协议格 式,即接收端在没有 proto ⽂件定义的前提下是⽆法解码⼀个 protobuf 动静体的, 与此相对的, json xml 等协议格式是完全⾃描述的,拿到了 json 动静体,便可以知道这段动静体中有哪些字段, 每 个字段的值分袂是什么, 其实对于客户端和处事端通信双⽅来说, 约定好了动静格式之后完全没有必 要在每⼀条动静中都携带字段名称, Protobuf 在通信数据中移除字段名称, 这可以⼤⼤降低动静的⻓ 度, 提⾼通信效率, Protobuf 进⼀步将通信线路上动静类型做了划分, 如下表所示:
TypeMeaningUsed For0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum164-bitfixed64, sfixed64, double2Length- delimited (⻓度分割)string, bytes, embedded messages, packed repeated fields3Start groupgroups (deprecated)4End groupgroups (deprecated)532-bitfixed32, sfixed32, float对于 int32, int64, uint32 等数据类型在序列化之后城市转为 Varints 编码。
Protobuf 除了存储字段的值之外, 还存储了字段的编号以及字段在通信线路上的格式类型(wire- type),具体的存储⽅式为:
field_num << 3 | wire type
即将字段标号逻辑左移 3 位, 然后与该字段的 wire type 的编号按位或。接收端可以利⽤这些信息,结合 proto ⽂件来解码动静布局体。
上例子中,假设 age 为 5,由于 age 在 proto ⽂件中定义 的是 int32 类型, 因此序列化之后它的 wire type 为 0,其字段编号为 1,因此按照上⾯的计算⽅式, 即 1 << 3 | 0, 所以其类型和字段编号的信息只占 1 个字节, 即 00001000, 后⾯跟上字段值 5 的 Varints 编码, 所以整个布局体序列化之后为:
00001000 00000101
有了字段编号和 wire type,其后所跟的数据的⻓度便是确定的,因此 Protobuf 是⼀种⾮常紧密的数 据组织格式,其不需要出格地加⼊额外的分隔符来分割⼀个动静字段,这可⼤⼤提升通信的效率, 规避 冗余的数据传输。
(1)wire_type=0 的时候。 二进制布局为:Tag-Value。
value的编码也采⽤Varints编码⽅式,故不需要额外的位来暗示整个value的⻓度。因为Varint的msb位标 识下⼀个字节是否是有效的就起到了指示⻓度的作⽤。
例如:
// 666 int1Size = 3 ⼗六进制:
08 9a 05
// 0x1 int1Size = 2 ⼗六进制:
08 01
(2)wire_type=1、5 的时候。⼆进制布局也为:Tag-Value。
因为都是取固定32位或者64位,因此也不需要额外的位来暗示整个value的⻓度。
例如:
// 0x12 n1Size = 9 ⼗六进制:
09 12 00 00 00 00 00 00 00
// -5 n1Size = 9 ⼗六进制:
09 fb ff ff ff ff ff ff ff

// 0x12 n1Size = 5 ⼗六进制:
0d 12 00 00 00
// -5 n1Size = 5 ⼗六进制:
0d fb ff ff ff
(3)wire_type=2 的时候。⼆进制布局为:Tag--Value 。
因为暗示的是可变⻓度的值,需要有额外的位来指示⻓度。
例如:
// 1 str1Size = 3 ⼗六进制:
0a 01 31        //(这里1暗示长度)
// 1234 str1Size = 6 ⼗六进制:
0a 04 31 32 33 34 //(这⾥4暗示⻓度)
//⽼师 str1Size = 8 ⼗六进制:
0a 06 e8 80 81 e5 b8 88 //(这⾥6暗示⻓度)
repeat也是这种模式,此时length代表元素个数。
message TRepeatedfields{
        repeated int32 n1 = 1;
        repeated Tbytes n2 = 2;
}

message Tbytes{
        bytes n1 = 1;
}




(4)filed_num范围:

[*]1到15,仅使⽤1bytes。每个byte包含两个部门:⼀个是field_number,⼀个是tag,此中field-number就是protobuf中每个值后等号后的数字。可以认为这个field_number是必需的。那么⼀个byte⽤来表达这个值就是 000000000,此中bit 8暗示是否有后续字节,如果为0暗示没有也就是这是⼀个字节,bit 3~bit 7部门暗示 field-number,bit 0 ~ bit 2部门则是wire_type部门,暗示数据类型。也就是(field_number << 3) | wire_type。此中wire_type只有3位,暗示数据类型。那么能够暗示field_number的就是bit 3 ~ bit 7的数 字,能够表达的最⼤范围就是1-15(此中0是⽆效的)。
[*]16到2047,与上⾯的法则其实类似(类似base128的⽅式)。
以2bytes为例⼦,那么就有 10000000 00000000,此中bit7和bit15依然是符号位,因为每个byte的第⼀位都⽤来暗示下⼀byte是否 和⾃⼰有关,那么对于>1byte的数据,bit15⼀定是1,因为这⾥假设是2byte。那么bit7是0暗示结束,刨除这两位,再扣掉3个wire_type位,剩下11位(2*8-2-3),能够表达的数字范围 就是2047(2的11次方)。
[*]当filed_num > 15时,依次类推。
六、protobuf协议动静升级

如果后⾯发现之前定义 message 需要增加字段了,这个时候就浮现出 Protocol Buffer 的优势了,不需 要改动之前的代码。不外需要满⾜以下 10 条法则:
(1)不要改动原有字段的数据布局。
(2) 如果您添加新字段,则任何由代码使⽤“旧”动静格式序列化的动静仍然可以通过新⽣成的代码进⾏分析。应该记住这些元素的默认值,以便新代码可以正确地与旧代码⽣成的动静进⾏交互。同样,由 新代码创建的动静可以由旧代码解析:旧的⼆进制⽂件在解析时会简单地忽略新字段。
(3)只要字段号在更新的动静类型中不再使⽤,字段可以被删除。您可能需要重定名该字段,可能会添加 前缀“OBSOLETE_”,或者标识表记标帜成保留字段号 reserved ,以便将来的 .proto ⽤户不会不测重 复使⽤该号码。
syntax ”proto3”;
message Stock {
reserved 3, 4; //通过,隔开
int32 id = 1;
string symbol = 2; }
message Info {
reserved 2, 9 to 11, 15; // 可以通过to指定持续返回
// ...
}
(4)int32,uint32,int64,uint64 和 bool 全都兼容。这意味着您可以将字段从这些类型之⼀更改为另⼀ 个字段⽽不粉碎向前或向后兼容性。如果⼀个数字从不适合相应类型的线路中解析出来,则会得到与 在 C++ 中将该数字转换为该类型不异的效果(例如,如果将 64 位数字读为 int32,它将被截断为 32 位)。
(5) sint32 和 sint64 彼此兼容,但与其他整数类型不兼容。
(6) 只要字节是有效的UTF-8,string 和 bytes 是兼容的。
(7) 嵌⼊式 message 与 bytes 兼容,如果 bytes 包含 message 的 encoded version。
(8) fixed32与sfixed32兼容,⽽fixed64与sfixed64兼容。
(9) enum 就数组⽽⾔,是可以与 int32,uint32,int64 和 uint64 兼容(请注意,如果它们不适合,值将 被截断)。但是请注意,当动静反序列化时,客户端代码可能会以分歧的⽅式对待它们:例如,未识 此外 proto3 枚举类型将保留在动静中,但动静反序列化时如何暗示是与语⾔相关的。(这点和语⾔相 关,上⾯提到过了)Int 域始终只保留它们的值。
(10) 将单个值更改为新的成员是安全和⼆进制兼容的。如果您确定⼀次没有代码设置多个字段,则将多个 字段移⾄新的字段可能是安全的。将任何字段移到现有字段中都是不安全的。(注意字段和值的区 别,字段是 field,值是 value)
protobuf⼯程经验:

[*]proto⽂件定名法则;
[*]proto定名空间;
[*]引⽤⽂件;
[*]多个平台使⽤同⼀份proto⽂件。
总结


[*]Protobuf 采⽤ Varints 编码和 Zigzag 编码来编码数据, 此中 Varints 编码的思想是移除数字⾼ 位的 0, ⽤变⻓的⼆进制位来描述⼀个数字, 对于⼩数字, 其编码⻓度短, 可提⾼数据传输效率, 但 由于它在每个字节的最⾼位额外采⽤了⼀个标识表记标帜位来标识表记标帜其后是否还跟有有效字节, 因此对于⼤ 的正数, 它会⽐使⽤普通的定⻓格式占⽤更多的空间, 此外对于负数, 直接采⽤ Varints 编码将恒 定占⽤ 10 个字节, Zigzag 编码可将负数映射为⽆符号的正数, 然后采⽤ Varints 编码进⾏数据 压缩, 在各种语⾔的 Protobuf 实现中, 对于 int32 类型的数据, Protobuf 城市转为 uint64 ⽽后 使⽤ Varints 编码来措置, 因此当字段可能为负数时, 我们应使⽤ sint32 或 sint64, 这样 Protobuf 会按照 Zigzag 编码将数据变换后再采⽤ Varints 编码进⾏压缩, 从⽽缩短数据的⼆进 制位数
[*]Protobuf 不是完全⾃描述的信息描述格式, 接收端需要有相应的解码器(即 proto 定义)才可解析 数据格式, 序列化后的 Protobuf 数据不携带字段名, 只使⽤字段编号来标识⼀个字段, 因此更改 proto 的字段名不会影响数据解析(但这显然不是⼀种好的⾏为), 字段编号会被编码进⼆进制的 动静布局中, 因此我们应尽可能地使⽤⼩字段编号
[*]protobuf 是⼀种紧密的动静布局, 编码后字段之间没有间隔, 每个字段头由两部门组成: 字段编 号和 wire type, 字段头可确定数据段的⻓度, 因此其字段之前⽆需加⼊间隔, 也⽆需引⼊特定的 数据来标识表记标帜字段末尾, 因此 Protobuf 的编码⻓度短, 传输效率⾼。
[*]协议设计的边界问题、版本号放在哪里、command id需要与否,要考虑清楚。
[*]熟悉protocol、json、xml的序列化和反序列化。出格是json。
[*]熟悉proto文件编写。.
原文:网页链接
页: [1]
查看完整版本: 深入protobuf(Protocol Buffers)道理:简化你的数据序列化