在项目中使用Protobuf协议实现数据传输(二)

在项目中使用Protobuf协议实现数据传输(二),第1张

上篇已经简单的扒游颂分析了什么是ProtoBuf协议的优缺点、简单的环境配置、项目中的简单使用和一些编写.Proto文件的注意点,下面我们更加深入一下ProtoBuf的语法及磨雹高级使用(非常感谢Carson_Ho大神的博文指导)

作用 :防止不同 .proto 项目间命名 发生冲突

ProtoBuf包春郑的解析过程如下 :

作用 :影响 特定环境下 的处理方式,但不改变整个文件声明的含义

常用Option选项 :(因为使用有限,简单列举常见的)

作用 :真正用于描述 数据结构

在.proto消息模型中主要有 消息对象&字段

组成 :字段 = 字段修饰符 + 字段类型 +字段名 +标识号(主要针对proto2以前版本)

字段 = 字段类型 +字段名 +标识号(主要针对proto3及以后版本)

一个消息对象 可以将 其他消息对象类型 用作字段类型

关于ProtoBuf在项目中的实践的高级用法请关注:下一篇博客:在项目中使用Protobuf协议实现数据传输(三)

protobuf 是Google开源的一款支持跨平台、语言中立的结构化数据描述和高性能序列化协议,此协议完全基于二进制,所以性能要远远高于JSON/XML。由于出色的传输性能所以常见于微服务之间的通讯,其中最为著名的是Google开源的 gRPC 框架。

那么protobuf是如何实现高性能的,又是如何实现数据的编码和解码的呢?

基于128bits的数据存储方式(Base 128 Varints)

Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。

比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息

Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结告改束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,比如 300,会用两个字节来表示:1010 1100 0000 0010。

另外如果从数据大小角度来看,这种表示方式比实现的数据多了一个bit, 所以其实际传输大小就多14%(1/7 = 0.142857143)。

数字1表示方式:0000 0001

对于小的数据比较好理解,正常情况下1的二进制是 0000 0001,使用128bits表示的话,首位结束标识位也是0,所以两者结果是一样的 0 000 0001。

数字 300 表示方式:1010 1100 0000 0010

<figcaption>300</figcaption>

这个有点不太好理解了,这是因为原本用一个字节(8bit)就可以表示,但由于使用128bits表示方法,需要对每个字节的最高位添加一个结束标识位来表示,所以一个字节已经不够用了,需要占用两个字节来表示,其中两个字节最高位都是结束标识位。

如果正向推算的话,历吵我们知道数字300的二进制值 1 0010 1100,用两个字节表示完整值则为

0000 0001 0010 1100 # 二进制

_000 0010 _010 1100 # 二进制每个肢友侍字节的最高位向左移动一个位置,放入结束标识位

0 000 0010 1 010 1100 # 转换为128bits方式,1:结束,0:未结束

1 010 1100 0 000 0010 # 转换为 小端字节序 , 低字节在前,高字节在后

注意这里是先添加结束标识符,然后再转为小端字节序。

消息经过序列化后会成为一个二进制数据流,该流中的数据为一系列的 Key-Value 对。如下图所示:

采用这种 Key-Pair 结构无需使用分隔符来分割不同的 Field。 对于可选的 Field,如果消息中不存在该 field,那么在最终的 Message Buffer 中就没有该 field ,这些特性都有助于节约消息本身的大小。

Key 用来标识具体的 field,在解包的时候,客户端创建一个结构对象,Protocol Buffer 从数据流中读取并反序列化数据,并根据 Key 就可以知道相应的 Value 应该对应于结构体中的哪一个 field。

而Key也是由以下两部分组成

Key 的定义如下:

| 1 | (field_number <<3) | wire_type |

可以看到 Key 由两部分组成。第一部分是 field_number 。第二部分为 wire_type 。表示 Value 的传输类型。

一个字节的低3位表示数据类型,其它位则表示字段序号。

Wire Type 可能的类型如下表所示:

在我们的例子当中,field id 所采用的数据类型为 int32,因此对应的 wire type 为 0。细心的读者或许会看到在 Type 0 所能表示的数据类型中有 int32 和 sint32 这两个非常类似的数据类型。Google Protocol Buffer 区别它们的主要意图也是为了减少 encoding 后的字节数。

每个数据头同样采用128bits方式,一般1个字节就足够了,

本例中字段a 的 序号是1

如上创建了 Test1 的结构并且把 a 的值设为 2,序列化后的二进制数据为

0 000 1000 0 000 0010

Key 部分是 0000 1000

value 部分是 0000 0010, 其中字节最高位是结束标识位,即10进制的2,我们在转换的时候统一将符号位转为0即可。

协议规定数据头的低3位表示wire_type, 其它字段表示字段序号field_number,因此

0000 1000

_000 1000 # 去掉结束标识符位

_000 1000 # 000 表示数据类型, 这里是Varint

_000 1000 # 0001 这四位表示字段序号

https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/

原文: https://blog.haohtml.com/archives/20215


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/12302742.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-24
下一篇 2023-05-24

发表评论

登录后才能评论

评论列表(0条)

保存