这个文档会介绍protocol buffer的二进制有线格式(binary wire format)。你并不是需要理解这些后才能在应用里使用protocol buffer,但是当你想知道不同的protocol buffer格式是如何影响编码后的消息体的体积时,这些知识会非常有用。

一个简单的消息

假设有一个非常简单的消息定义:

message Test1 {optional int32 a = 1;
}

在应用中,你创建了一个Test1消息并把a设置为150。然后你把消息序列化到输出流中,如果你能查看编码后的消息,你会看到三个字节:

08 96 01

到目前为止,如此小而且都是数字-但是这是什么意思呢?继续往下看

Varint编码

要理解上面protocol buffer编码的数据,你需要先理解vaintsVarints是一种使用一个或多个字节编码整数的方法。较小的数字使用较少的字节。

除了最后一个字节外,varint编码中的每个字节都设置了最高有效位(most significant bit - msb)–msb为1则表明后面的字节还是属于当前数据的,如果是0那么这是当前数据的最后一个字节数据。每个字节的低7位用于以7位为一组存储数字的二进制补码表示,最低有效组在前,或者叫最低有效字节在前。这表明varint编码后数据的字节是按照小端序排列的。

举例来说,对于数字1-它占用单个字节,所以字节的最高位上是0

0000 0001

对于数字300会有一点复杂,它占用俩个字节

1010 1100 0000 0010

那么是怎么计算出来是300的呢?首先你需要把每个字节的msb去掉,因为它只用来告诉我们是否已经到达数字的最后一个字节(本例的varint占用俩个字节所以第一个字节的msb为1)

 1010 1100 0000 0010
→ 010 1100  000 0010

将两组7位反转,因为你记得,varint存储的数字最低有效组在前。然后,将它们连接起来以获得最终值

000 0010  010 1100 (去掉最高有效位,并反转7位组)
→  000 0010 ++ 010 1100
→  100101100
→  256 + 32 + 8 + 4 = 300

:varint编码理解起来有点难,可以看之前写的varint编码原理解析。

消息的组成

如你所知,一个protocol buffer是一系列键值对。消息的二进制格式只使用消息字段的字段编号作为键--字段名和声明的类型只能在解析端通过引用参考消息类型定义(即.proto文件)才能确定。

当一个消息被编码时,键和值会被连接放入字节流中。当消息被解码时,分析器需要能够跳过未识别的字段。这样,新加入消息的字段就不会破坏不知道他们存在的那些老程序。为此,有线格式消息中每个对的“键”实际上是两个值-.proto文件中的字段编号,加上一种有线类型,该类型仅提供足够的信息来查找随后的值的长度。在大多数语言实现中,这个键称为标签。

可用的有线类型如下:

Type Meaning Used For
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

在消息流中的每个键都是varint,使用(filed_number << 3) | wire_type 获得--也就是说字节的后三位存储的是有线类型。

现在让我们再回到上面的消息示例。你现在知道字节流中的首个字节永远都是一个varint键,在我们的例子中它是08或者下面的二进制(去掉了msb)。

000 1000

通过后三位得出有线类型(0),然后右移三位得到字段编号(1)。现在你知道字段的编号是1对应的值是一个varint。使用前面学到的解码varint的知识,你可以看到下面的两个字节存储着值150。

96 01 = 1001 0110  0000 0001→ 000 0001  ++  001 0110 (去掉最高有效位,并反转7位组)→ 10010110→ 128 + 16 + 4 + 2 = 150

更多值类型

有符号整数

就像你在上一部分看到的那样,protocol buffer中所有与有线类型0关联的类型都会被编码为varint。但是,在编码负数时,带符号的int类型(sint32和sint64)与“标准” int类型(int32和int64)之间存在着巨大区别。如果将int32或int64用作负数的类型,则结果varint总是十个字节长––实际上,它被视为一个非常大的无符号整数。如果使用带符号类型(sint32和sint64)之一,则生成的varint使用ZigZag编码,效率更高

ZigZag编码将有符号数映射到无符号数以便具有较小绝对值的数字(比如-1)也具有较小的varint编码值。这样做的方式是通过正整数和负整数来回“曲折”,将-1编码为1,将1编码为2,将-2编码为3,依此类推,可以在下表中看到:

Signed Original Encoded As
0 0
-1 1
1 2
-2 3
2147483647 4294967294
-2147483648 4294967295

非varint数字

对与非可varint编码的数字来说比较简单--doublefixed64使用有线类型1,这会告诉解析器期望固定的64-bit的数据块。相似地floatfixed32使用有线类型5,这会告诉解析器期望固定的32-bit数据块。这两种情况都是使用小端序排列字节存储数据的。

字符串

有线类型2(长度分隔)表示该值是varint编码的长度值,后跟长度值指定数量的数据字节。

message Test2 {optional string b = 2;
}

设置b的值为"testing"后消息对应的二进制有线格式为

12 07 <font color="red">74 65 73 74 69 6e 67</font>

红色的字节是UTF-8编码后的"testing"

这里的键是0x12→0001 0010→字段号= 2,类型=2(第一个字节的后三位表示有线类型的编号,然后右移三位变成000 0010得到字段号)。值中的varint表示的数据字节长度是7,如你所见我们在它后面找到的七个字节–就是解析器要找的字符串。

内嵌消息

下面是一个拥有内嵌消息的消息定义Test3,内嵌的消息类型是我们上面示例中定义的Test1

message Test3 {optional Test1 c = 3;
}

下面则是内嵌的Test1中的a设置为150,Test3`被编码后的版本

1a 03 <font color="red">08 96 01</font>

如你所见,最后三个字节和我们第一个例子编码后的结果一样(08 96 01),在他们之前是数字3,--内嵌消息会像字符串一样被对对待(有线格式=2)。

可选和可重复元素

如果proto2消息定义具有重复的元素(不带[packed = true]选项),则编码消息具有零个或多个具有相同字段编号的键值对。这些重复的值不必连续出现。它们可能与其他字段交错。解析时,元素之间的顺序会保留下来,尽管其他字段的顺序会丢失。在proto3中,重复字段使用packed编码,可以在下面看到相关编码。

通常,编码消息永远不会有一个以上非重复字段的实例。但是,解析器能处理这种实际情况,对于数字类型和字符串,如果同一字段多次出现,则解析器将接受它看到的最后一个值。对于嵌入式消息字段,解析器将合并同一字段的多个实例,就像使用Message :: MergeFrom方法一样-也就是说,后一个实例中的所有单个标量字段将替换前一个实例中的单个标量字段,可重复字段会被串联到一块。这些规则的作用是,解析两个编码的消息的连接所产生的结果与您分别解析两个消息并合并结果对象的结果完全相同。也就是说:

MyMessage message;
message.ParseFromString(str1 + str2);

等同于

MyMessage message, message2;
message.ParseFromString(str1);
message2.ParseFromString(str2);
message.MergeFrom(message2);

这个特性有时很有用,因为即使您不知道它们的类型,也允许你合并两个消息。

压缩重复字段

proto版本2.1.0引入了压缩重复字段,在proto2中声明为重复字段,并使用特殊的[packed = true]选项。在proto3中,默认情况下压缩标量数字类型的重复字段。这些功能类似于重复的字段,但编码方式不同。包含零元素的压缩重复字段不会出现在编码的消息中。否则,该字段的所有元素都将打包为有线类型为2(定界)的单个键值对。每个元素的编码方式与通常相同,不同之处在于元素之前没有键。

举例来说,你有以下消息类型:

message Test4 {repeated int32 d = 4 [packed=true];
}

现在假设您构造一个Test4,为重复的字段d提供值3、270和86942。然后,消息编码后的形式为:

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)

只能将原始数字类型(使用varint,32位或64位线型的类型)的重复字段声明为“packed”。

字段顺序

字段编号可以在.proto文件中以任何顺序使用。选择使用的顺序对消息的序列化方式没有影响。

序列化消息时,对于如何写入其已知字段或未知字段没有保证的顺序。序列化顺序是一个实现细节,将来任何特定实现的细节都可能更改。因此,protocol buffer解析器必须能够以任何顺序解析字段。

Protobuf编码指南相关推荐

  1. ProtoBuf开发者指南

    转载:http://www.cnblogs.com/foxhengxing/archive/2010/08/10/1796165.html google ProtoBuf开发者指南 ProtoBuf开 ...

  2. Protobuf 语法指南简析(proto3)

    Protobuf 语法指南简析(proto3) 前言 参考官方Language Guide (proto3)文档,主要是参考了官方文档. 本文介绍如何使用 protocol buffer 语法来构造 ...

  3. 《Java编码指南:编写安全可靠程序的75条建议》—— 指南20:使用安全管理器创建一个安全的沙盒...

    本节书摘来异步社区<Java编码指南:编写安全可靠程序的75条建议>一书中的第1章,第1.20节,作者:[美]Fred Long(弗雷德•朗), Dhruv Mohindra(德鲁•莫欣达 ...

  4. 《Java编码指南:编写安全可靠程序的75条建议(英文版)》—— 2.7 修复错误...

    本节书摘来异步社区<Java编码指南:编写安全可靠程序的75条建议(英文版)>一书中的第2章,第2.7节,作者:[美]Fred Long(弗雷德•朗),Dhruv Mohindra(德鲁• ...

  5. ProtoBuf使用指南(C++)

    ProtoBuf使用指南(C++) Created: Mar 12, 2019 6:47 PM Last Edited Time: Mar 22, 2019 1:51 PM 1.安装部署 去官网(gi ...

  6. try catch对异常进行输出到日志、_java安全编码指南之:异常处理

    点击上方的蓝字关注我吧 程序那些事 简介 异常是java程序员无法避免的一个话题,我们会有JVM自己的异常也有应用程序的异常,对于不同的异常,我们的处理原则是不是一样的呢? 一起来看看吧. 异常简介 ...

  7. 如何确定autosar的版本_AUTOSAR编码指南(中文版)

    在汽车应用领域,软件开发变得越来越重要.随着安全.环境以及便利性需求的增长,车辆中应用电子系统的数量也在急速增长.其中有90%的创新应用都是基于软件驱动的电子组件.而这些组件的研发成本占车辆开发成本的 ...

  8. Google Protobuf 开发指南

    为什么80%的码农都做不了架构师?>>>    Google Protobuf开发指南 1.简介 l  它是开源项目:http://code.google.com/p/protobu ...

  9. 《Java编码指南:编写安全可靠程序的75条建议》—— 指南19:对细粒度的安全定义自定义安全权限...

    本节书摘来异步社区<Java编码指南:编写安全可靠程序的75条建议>一书中的第1章,第1.19节,作者:[美]Fred Long(弗雷德•朗), Dhruv Mohindra(德鲁•莫欣达 ...

最新文章

  1. 子元素绝对定位absolute后,自动撑开宽度
  2. 小木木的Python学习笔记
  3. 解决eclipse闪退的办法
  4. python学习日记(匿名函数)
  5. 导出合并小文件_关于微信语音导出,这个方法强烈建议~
  6. python编辑器中文字体倒立的_matplotlib的安装和允许中文及几种字体
  7. 六级词汇打卡第天四天(四)
  8. ~~单调队列(数据结构)(附题目)
  9. halcon三种模板匹配方法
  10. 编译内核是几个常用的命令备忘
  11. 【图像处理】基于matlab GUI数字图像处理【含Matlab源码 652期】
  12. 小米note 卡在android,小米Note手机SIM卡怎么安装?小米Note安装手机SIM卡教程
  13. php7.4中让gd库支持jpeg格式
  14. 使用C语言输出菱形详解
  15. 联想计算机从金丝顿u盘启动,联想笔记本不从U盘启动解决方法
  16. ips细胞再生视网膜研究进展
  17. 记dubbo consumer服务因订阅其他有异常的服务导致超时的问题
  18. 仙剑5手游服务器维护,仙剑奇侠传手游5月27日例行维护与活动公告
  19. Android Studio Module 中的So 文件和 App So文件合并问题
  20. 批处理遍历当前目录和子目录查找指定后缀名的文件并修改后缀名

热门文章

  1. IntellIJ IDEA 配置 Vue 支持
  2. [LintCode] 最长上升子序列
  3. 北大青鸟ASP.NET之总结篇
  4. 微成本搭建企业高效沟通平台
  5. 用户、组织结构、功能菜单、权限分配设计
  6. JACK——BOM Exercise2
  7. 探寻完美 之 JavaScript继承
  8. 我的《野蛮生长》书摘
  9. [转] React风格的企业前端技术
  10. 亚马逊因密码泄露重置部分用户密码