1 go grpc-go 相关技术专栏 总入口
2 Protobuf介绍与实战 图文专栏 文章目录
1、protobuf编码解决什么问题? |
先看一个proto文件:
也就是说,可以使用protobuf编码按照一定的规则去存储数据类型,变量名,变量值等信息。
是存储数据信息的一种方式。
2、protobuf编码结构 |
既然,我们已经知道protobuf编码就是按照某种规则来存储数据类型、变量名、变量的值,
那么,至少有二个问题需要解决?
2.1、问题一:在网络IO等场景中传输时,数据类型、变量名是如何传输的? |
在proto文件中,有一点跟JSON,XML不同,就是每条属性记录,都有唯一的标识号,如下图所示:
也就是说,客户端、服务器端两侧使用相同的proto文件,根据标识号就可以获取到相应的类型,以及变量名称了。
即,在网络IO传输中,只需要传输标识号就可以表达数据类型,变量名了。
节省了很多空间。
2.2、问题二:在网络IO等场景中传输时,变量的值,是如何编码传输的? |
在proto文件中支持多种数据类型,如int32,int64,uint32,uint64,sfixed32,map,float等等;
那么,这些数据类型声明的变量的值是采用一种编码规则,还是多种呢?
如果是一种的话,从编码层面看,代码简单,实现方便。
但是,肯定会造成浪费,并没有根据具体的数据类型进行编码,编码规则统一,非差异化,编码效率是低的。
因此,应该是针对不同的数据类型采用不同的编码方案进行编码,实现不同类型的编码差异化。
Type | Meaning | 存储结构 | 编码长度 | 适合哪些数据类型 |
---|---|---|---|---|
0 | Varint | T-V | 变长(1-10个字节) | int32,int64,uint32,uint64,sint32,sint64,bool,enum |
1 | 64-bit | T-V | 固定8个字节 | fixed64,sfixed64,double |
2 | Length-delimited | T-L-V | 变长 | string,bytes,embedded messages,packed repeated fields |
3 | Start group | 弃用 | 弃用 | groups(deprecated) |
4 | End group | 弃用 | 弃用 | groups(deprecated) |
5 | 32-bit | T-V | 固定4个字节 | fixed32, sfixed32, float |
这个表一共有6行,代表什么意思呢?
- 这6行,代表6种编码形式。每种编码形式都有唯一的编号,
- 如第1列的十进制形式的0,1,2,3,4,5;就是具体的编号,可以用3个bit就完全表示了。
- 那么,编号有什么用呢?
- 如,发送方对某个数据类型的变量按照某种编码形式进行编码时,只需要传输唯一的编号即可;如,传输的是0;
- 接收方式接收到0后,就知道此变量是按照Varint进行编码的,那么解码时,按照Varint解码即可得到具体的值。
- 第二列,就是每种编码形式的名称;
- 第三列,就是每种编码的存储结构,主要分为两类T-V、T-L-V(下文解释具体含义)
- 第四列,表示每种编码占用的字节长度
- 第五列,表示每种编码针对的是proto文件中的哪些数据类型
如,fixed32类型的变量值,采用的是32-bit编码,用十进制5来表示32-bit编码方式;
在网络IO传输时,会将5转换为二进制形式传输。
同样,接收方接收到5后,会根据32-bit编码方式进行反向解码。
也就是说,在网络传输中,只会传输第1列的值;第2,3列不会传输的。
看第1列,类型Type,有效值为0,1,2,3,4,5;其中,3,4,已经废弃;
即,真正有效的编码形式也就0,1,2,5
https://www.cnblogs.com/onlysun/p/4569595.html
说明,无需分隔符
2.3、protobuf中的编码格式?或者说编码结构?一个完整的编码由那几部分组成? |
通过上面的分析,我们已经知道了数据类型,变量名,变量值是如何进行编码的,
那么这三者如何组织呢,即,谁在前,谁在后;
2.3.1、常见的编码结构TLV |
常用的编码格式结构,如TLV;
- T,表示tag; 表示的是变量值的元数据信息,如,编码方式,变量值是否压缩,变量值的字节数等等
- L,表示Length;表示传输数据的长度,相当于分隔符,告诉接收方,读取多少字节
- V,表示有效值; 表示,传输的具体内容。
如下图所示:
可以看出来,在proto文件中message类型的结构体是由一个一个的Field组成的;
每个Field是由三部分组成;Tag, Length, Value
其中,
当数据类型是string, bytes, embedded messages, packed repeated fields时(即type的值为2时),Length部分才允许写。
其他数据类型,不需要写。
从编码方式看Varint,64-bit, 32-bit中已经知道Value的长度了,没必要再添加Length部分了。
具体Varint编码方式,下文再分享。
当Length不存在时,Field的组成由两部分组成,TagValue,类似于KeyValue形式。
在T-L-V中:
- T的结构构成,第1个bit表示标志位,第2-5个bit表示proto文件中message里的标识号,第6-8个bit表示wire_type,
- L表示V的字节数量,采用的是Varint编码
- V传输的是具体的内容,可以采用4种编码方式:Varint编码,64-bit,Length-delimited,32-bit;(根据wire_type来采用不同的编码方式)
2.3.2、Tag结构中的wire_type结构说明(用来存储变量值的编码类型的) |
再看下Tag的组成形式,如下图所示:
wire_type占用3个bit,可以表示7中编码类型。
实际中,只有0,1,2,5 四种编码类型可以使用,3个bit足以。
2.3.3、Tag结构中的Field_number组成说明?(用来存储标识号的) |
2.3.4、总结 |
通过protobuf的编码结构,当接收方接收二进制流后,就可以获取到相应的数据,如
通过解析tag中的Field_number,来获取到proto文件中的标识号,从而获取到类型,变量名。
通过解析tag中的wire_type,来获取到变量值的编码方式,是Varint,还是64-bit, 32-bit, length-delimited方式,
然后,反向解码,就可以读取到变量值;
即,变量类型,变量名称,变量值都获取到了。
protobuf的编码结构,跟常见的TLV结构,略微不同的就是第2部分,当数据类型满足一定的条件时,非必须存在;
从而,减少了传输的数据量,是提高效率的一种方式。
protobuf里源码级别是如何实现的
https://blog.csdn.net/vvvlan/article/details/111471509
wire_type 不同值时的二进制结构
https://blog.csdn.net/u013862108/article/details/115899474?