Protobuf-数据编码规则

参考文档:https://developers.google.cn/protocol-buffers/docs/encoding

文章是本人对官方文档的理解,可能理解有误,望指正。^^

1.A Simple Message 简单消息格式

protobuf中的最简单的消息定义:

message Test1 {
  optional int32 a = 1;
}

如果将a赋值150,它的字节流(16进制表示)如下:

08 96 01

转换为二进制表示如下:

    0    8    9    6    0    1
→  0000 1000 1001 0110 0000 0001
标志位 字段编号 字段类型 标志位 低位字段值 标志位 高位字段值
0 0001 000 1 0010110 0 0000001

protobuf都是以8bit(1byte)为一个解析单元。

标志位 0:表示解析单元结束,后一个字节是新的解析单元,1:表示解析单元未结束,后一个字节是这个解析单元的高位部分。

字段编号:protobuf的消息体的字段编号,如上所示,转换为十进制是1

字段类型:protobuf的消息体的字段类型,转换为十进制是0

字段类型对照表

类型值 类型名 使用场景
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimited string, bytes, embedded messages, packed repeated fields
3 Start group groups (deprecated)
4 End group groups (deprecated)
5 32-bit fixed32, sfixed32, float

我们进一步对其解析,剔除标志位,交换字段值的高低位

字段编号 字段类型 高位字段值 低位字段值
0001 000 0000001 0010110
1 0 高低位合并去除左侧0 10010110
1 0 计算十进制 150

这样得到字段编号=1的int32类型值为150

2.Base 128 Varints

varints是整数类型的编码规则,大小是1byte的整数倍

如果是1byte的Varints,能表示0-127的正整数,这里就会奇怪,1byte不是8bit,实际上能表示0-255的正整数,为什么少了一半。

这里就要提到前面讲的标志位,Varints类型的每个字节都有两部分组成,高位的1bit是标志位,剩下的7bit可用用于表示整数。如果高位的1bit是1,下一个byte也被视为Varints的一部分,直至下一个byte的高位1bit是0,Varints的解析单元结束。

注意:如果varints是大于1byte,需要做高低位置换,因为前面表示整数的低位部分,往后,表示整数的高位,总的来说,计算数值时,需要去除标记位后,7bit一组倒转

比如300这个数值,我们看看其二进制

 1010 1100 0000 0010
→ 010 1100  000 0010
→  000 0010 ++ 010 1100 (高低位倒转)
→  100101100
→  256 + 32 + 8 + 4 = 300

3.More Value Types 其他数据类型

3.1有符号整数

前面章节,我们讲到字段类型=0,都用varints的编码表示。但是sint32,sint64会比int32,int64表示负数上面,更节省空间。
就拿int32举例,它表示负数,一般需要5byte,如果采用sint32,其采用ZigZag编码,可以少于5byte,注意,只是可以,不是绝对,特定数值,比如大数值就会达到5byte

原始值 编码值
0 0
-1 1
1 2
-2 3
2147483647 4294967294
-2147483648 4294967295

从表格中可以看出,小数值的正负数,二进制编码,高位将都是0,32位的整型数据可以将高位去除后,进行传输,解码时,在高位补0,凑足32位。减少传输数据的量。

ZigZag编解码公式

之所以可以用这个公式编码,原因在于:
在原始值的二级制结构,正数的最高位都是0,负数的最高位都是1
在编码后的二级制结构,正数的最低位都是1,负数的最低位都是0

通过这个特性,我们可以知道当前数值时正数还是负数,同时采用不同的编解码方式。

n表示数值
正数编码:n<<1
正数解码:n>>>1

负数编码:(n<<1)^~(n&0)
负数解码:(n>>>1)^~(n&0)

举个例子,比如2和-2的编码按照公式计算下

未采用ZigZag的传输

2
→ 0000 0000 0000 0000 0000 0000 0000 0010
→ 10(压缩值)
→ 0000 0010(protobuf传输内容,注意每8bit的最高位是标志位,不是数值部分)

这里传输2只需要1byte

未采用ZigZag的传输

-2
→ 1111 1111 1111 1111 1111 1111 1111 1110 (-2是2取反补码得到的,这是负数在二进制中的表示规则)
→ 1111 1111 1111 1111 1111 1111 1111 1110(压缩值,可以看出无法压缩,高位都是1)
→ 1000 1110 1111 1111 1111 1111 1111 1111 0111 1111  (protobuf传输内容,注意每8bit的最高位是标志位,不是数值部分,注意超过1byte,需要每个字节进行高低位倒转)

这里传输-2需要5byte

采用ZigZag的传输

2
→ 0000 0000 0000 0000 0000 0000 0000 0010
→ 0000 0000 0000 0000 0000 0000 0000 0100(编码)
→ 100(压缩值)
→ 0000 0100(protobuf传输内容,注意每8bit的最高位是标志位,不是数值部分)

这里传输2只需要1byte

采用ZigZag的传输

-2
→ 1111 1111 1111 1111 1111 1111 1111 1110 (-2是2取反补码得到的,这是负数在二进制中的表示规则)
→ 0000 0000 0000 0000 0000 0000 0000 0011(编码)
→ 11(压缩值)
→ 0000 0011 (protobuf传输内容,注意每8bit的最高位是标志位,不是数值部分)

这里传输-2需要1byte

从如上两个的对比,可以看出负数在ZigZag编码的传输中,可以节省空间。具备更高的传输效率。但是,如果对大数值的正负数,压缩的空间就很小了。

3.2Non-varint Numbers 非varint型数值

fixed64, sfixed64, double,fixed32, sfixed32, float都是Non-varint Numbers,
可以从字面看出,fixed64, sfixed64, double大小是64bit(也就是8byte),fixed32, sfixed32, float大小是32bit(也就是4byte),
但是在实际的传输过程中可能超过,因为有标志位存在。

这块的部分没有找到详细的资料说明,只是说编码采用标志位+高低位逆序编码(就是varint编码规则),没太懂!!!!!!!!!!

Strings 字符型

字符串都用length-delimited长度限定的编码格式,编码中,有一个部分采用varint类型表示长度。

message Test2 {
  optional string b = 2;
}

b="testing"

具体编码:

字段编号&字段类型 字段长度 字段值
12 07 74 65 73 74 69 6e 67

0x12 → field number = 2, type = 2

0x07→ 7个字节

Embedded Messages 嵌套消息

如下是我们说的嵌套消息结构,Test3的c字段是Test1类型,一样,我们把a设置为150

message Test1 {
  optional int32 a = 1;
}
message Test3 {
  optional Test1 c = 3;
}

如下是实际编码,我们来解析下,
前文提到 Test1.a=150,它的编码是08 96 01,你会发现下面的编码后半部分正好是一样的。

 1a 03 08 96 01

剩下的1a 03是Test3.c的编码,我们解析下

1a 03
→ 0001 1010 0000 0011
→ 0(标志位)0011(字段编号)010(字段类型)0(标志位)0000011(字段长度)
→ 3(字段编号)2(字段类型)3(字段长度)

对照字段类型表,我们可以确认嵌套消息的类型采用length-delimited长度限定类型,字段值按照长度截取,解析它的时候,再按照子消息格式,再次解码。

3.3Packed Repeated Fields 列表字段的压缩

在proto2版本中,repeated默认采用[packed=false],不进行压缩。
在proto3版本中,对于数值类型(指字段类型是0,1,5)默认采用 [packed=true],在grpc报文中,它是(字段编号+字段类型+元素个数+元素1+元素2....)结构,具体如下所示:

message Test4 {
  repeated int32 d = 4 [packed=true];
}
22        // key (field number 4, wire type 2)
06        // payload size (6 bytes)
03        // first element (varint 3)
8E 02     // second element (varint 270)
9E A7 05  // third element (varint 86942)

剩下的Length-delimited类型,就无法采用这种压缩方式,它们的grpc报文组织结构是这样子(字段编号+字段类型+长度限定值1+字符串1+字段编号+字段类型+长度限定值2+字符串2。。。。。)
从报文可以看出,字段编号和字段类型都是重复要素,需要占用一定的字节。

注意:这边有一个官方对于protobuf解码器的要求,比如你传递的grpc的报文,列表类型数据,采用packed=false,不进行压缩,但是接收者的protobuf定义的又是配置了packed=true,这时候,解码器需要兼容这种情况,对报文做正确解析。

这边额外对packed=false的编码grpc报文特征做下说明:

  1. 所有数组元素可能不是连续的,中间可能穿插其他字段的报文
    2.所有数组元素的顺序是可保持的,解码后,数组元素的展示顺序将和编码前一致。
  2. 数组元素,将是多对key-value的格式,在网络传递。

原文地址:https://www.cnblogs.com/lowezheng/p/11778052.html

时间: 2024-10-23 09:05:19

Protobuf-数据编码规则的相关文章

住建部 能耗数据编码规则开发实践

本文根据住建部<分项能耗数据采集技术导则>阐述"能耗数据编码规则",以便于开发人员在进行相关项目设计时,能够迅速上手,进行项目开发. 能耗数据编码示意图: 编码:  01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 X  X  X  X  X  X  X  X  X  X  X  X  X  X  X 行                建 建       分    分 一 二 政                筑 筑       类

golang Protobuf学习

protobuf是一种高效的数据传输格式(Google's data interchange format),且与语言无关,protobuf和json是基于http服务中最常见的两种数据格式.今天来学习基于golang的protobuf相关内容. google protocol buffer: https://developers.google.com/protocol-buffers/golang 官方提供的protobuf支持插件:https://github.com/golang/prot

智能家居系统-软件协议

3. 家庭网关的软件平台 随着嵌入式电子系统越来越复杂,系统软件的稳定性对系统的稳定运行显得愈发重要,在一些功能复杂的系统中,软件的工作量已经超过硬件开发.此时,嵌入式操作系统的作用凸显出来,操作系统可以把开发人员从无尽的软件编码工作中解放出来,只专注于应用开发,而系统资源的管理交与操作系统来完成,这种方式极大地提高了开发效率,缩短了开发周期.同时,基于操作系统的开发可以极大地提高系统的健壮性,各个任务并发执行,各自独立,即时有一个任务程序跑飞,但并不会造成系统的崩溃.另一方面,现代处理器的功能

displayport-2

上一章讲述了display-port的硬件连接,今天来说说协议层 图中可以看到,最底层是物理层,上层是连接服务层,提供的服务包括同步数据传输服务,aux链接服务,aux设备数据传输服务,在设备端也一样,但是两者完成的功能不同,主机端服务主要用于完成数据打包,填充,数据的多路分离(也就是将一个数据包分在几个lane上传输)以及数据编码,在设备端方向完成解包,去填充,多路数据合成,反编码,以及从数据包中恢复时钟. 在应用层,要完成视频流的管理,解码等操作和硬件连接的管理 在终端中,在数据流之外还需要

ICE概述

网络通信引擎(Internet Communications Engine, Ice)是由ZeroC的分布式系统开发专家实现的一种高性能.面向对象的中间件平台.它号称标准统一,开源,跨平台,跨语言,分布式,安全,服务透明,负载均衡,面向对象,性能优越,防火墙穿透,通讯屏蔽.因此相比CORBA,DCOM,SOAP,J2EE等的中间件技术,自然是集众多优点于一身,而却没有他们的缺点. Ice提供了完善的分布式系统解决方案,适合所有的异构网络环境:客户端和服务器端可以用不同的程序语言来实现,可以运行在

http,soap and rest

http://www.cnblogs.com/hyhnet/archive/2016/06/28/5624422.html http是标准超文本传输协议.使用对参数进行编码并将参数作为键值对传递,还使用关联的请求语义.每个协议都包含一系列HTTP请求标头及其他一些信息,定义客户端向服务器请求哪些内容,服务器用一系列HTTP响应标头和所请求的数据进行响应.HTTP-GET 使用 MIME 类型application/x-www-form-urlencoded(将追加到处理请求的服务器的 URL 中

转-浅谈HTTP-GET 、 HTTP-POST 和SOAP

原文链接:浅谈HTTP-GET . HTTP-POST 和SOAP 1.HTTP-GET 和 HTTP-POST HTTP-GET和HTTP-POST是标准协议,他们使用HTTP(超文本传输协议)谓词(谓词是指条件表达式的求值返回真或假的过程.)对参数金星编码并将参数作为名称/值对传递,还使用关联的请求语义.每个协议都包含一系列HTTP请求标头,HTTP请求标头及其他一些信息定义客户端向服务器请求哪些内容,哪个服务器用一系列HTTP响应标头和所请求的数据进行响应. HTTP-GET 使用 MIM

WebService发布协议--SOAP和REST的区别

HTTP是标准超文本传输协议.使用对参数进行编码并将参数作为键值对传递,还使用关联的请求语义.每个协议都包含一系列HTTP请求标头及其他一些信息,定义客户端向服务器请求哪些内容,服务器用一系列HTTP响应标头和所请求的数据进行响应.HTTP-GET 使用 MIME 类型application/x-www-form-urlencoded(将追加到处理请求的服务器的 URL 中)以 URL 编码文本的形式传递其参数. URL 编码是一种字符编码形式,可确保传递的参数中包含一致性文本,例如将空格编码为

Protobuf最佳实践(2)-- 命名规则

上篇文章介绍了一种比较合理的目录结构,本文来讨论一下各种命名规则. 文件 Protobuf文件使用SnakeCase规则命名(小写字母+下划线),以.proto为后缀.比如:player_info.proto.protoc会根据目标语言的命名规则来生成相应的目标文件.如果目标语言是Java的话,会生成PlayerInfo.java.如果目标语言是C++的话,会生成player_info.pb.h和player_info.pb.cc. 消息 消息的命名规则和Java的类名规则一致:首字母大写的Ca

Google protobuf proto文件编写规则

转载自: http://blog.csdn.net/yi_ya/article/details/40404231 1. 简单介绍 protobuf文件:就是定义你要的消息(类似java中的类)和消息中的各个字段及其数据类型(类似java类中的成员变量和他的数据类型) 2. Protobuf消息定义 消息由至少一个字段组合而成,类似于C语言中的结构.每个字段都有一定的格式. 字段格式:限定修饰符① | 数据类型② | 字段名称③ | = | 字段编码值④ | [字段默认值⑤] 1)限定修饰符包含