Protobuf 简介及简单应用

Protobuf 是 protocol buffers 的缩写. 根据官网的说法, protocol buffers 与平台无关, 与语言无关, 实现数据序列化的一种手段. 正如名字一样, protobuf 可以将数据按照规定的协议(protocol)序列化为二进制的数据(buffers). 序列化的数据基本上可以保证类型安全, 并且可以压缩大小. 这篇文章将简单说说关于 protobuf 的优点和问题, 如果有使用的需要可以作为参考

安装和使用

Protobuf 是在 github 上开源的项目, 地址在这里.

因为 protobuf 的编译器是用 C++ 写的, 所以可以通过 C++ 的安装方式来安装. mac 用户的话由于没有 apt-get, 按照安装说明中要使用的命令行工具可以用 homebrew 安装

经过漫长的下载, 编译, 安装, 没有显示错误, 恭喜??, 可以正式开始使用 protobuf 了

首先按照 protobuf 的语法(格式?)来写一个 .proto 文件

// BookInfo.proto
syntax = "proto3";

message BookInfo {
  int64 id = 1;
  string title = 2;
  string author = 3;
}

因为 protobuf 与语言无关, 因此接下来请按照 github 项目页面的教程选择自己使用的语言, 现在 protobuf 官方支持的语言有很多: C++, Java, Python, Objective-C… 选择后根据文档中的方法编译 .proto 文件, 这里我们将 .proto 编译成能够当做 CommonJS 引入的 js 文件

$ protoc --js_out=import_style=commonjs,binary:. BookInfo.proto

其实利用 protobuf 编译器将 .proto 文件编译成各种语言的方法都大同小异:

  1. 指定语言的输出路径 --js-out=. (这里省略了中间的编译选项)
  2. 要编译的文件 BookInfo.proto

然后就能看到当前文件夹中出现了一个 BookInfo_pb.js 的文件, 在项目中引用后就可以直接使用了

const BookInfoModel = require('../protobuf_model/build_model/BookInfo_pb')
let bookInfo = new BookInfoModel.BookInfo()
bookInfo.setId(4)

安装及使用大体就是这样, 根据语言的不同会有一些差别. 接下来将会开始介绍隐藏在 .proto 和 _pb.js 文件背后的东西

先从 JSON 说起

在讲 protobuf 之前先聊聊 JSON, 这个现在非常流行的数据格式. JSON 的格式非常简洁并且可以自解释, 在传输同样的数据量的时候比 XML 更小, 这也就意味着 JSON 可以占用更小的内存, 有更快的传输速度, 后面这点更为关键. 作为服务器和客户端的交互数据, 传输速度更快可以让用户更快得到反馈, 极大提高用户体验.

JSON 是利用符号来作为数据结构的分界, 比如 [] 表示数组, {} 表示字典, 这样就可以直接传输 key-value, 基本上传输的都是最简的需要的数据, 这是它如此轻量的原因. 那么, 有没有办法让 JSON 在传输数据的时候体积更小呢?

// 47 个字符
{
  "id": 123456,
  "title": "A Book",
  "author": "bewils"
}

因为 JSON 的格式不能改变, 否则在解析过程中会直接 crash 掉, 而 value 部分因为都是需要的数据也不能动, 所以理所当然地想到可以对 key 做一些事情

// 37 个字符
{
  "a": 123456,
  "b": "A Book",
  "c": "bewils"
}

通过一个简单的操作就可以将传输的数据压缩 21%, 虽然在使用的时候就会有点麻烦 ???

let id = obj["a"]
let title = obj["b"]

可读性基本为 0, 除了写代码的人没人能看得懂, 估计写代码的人过了 3 个月也不知道自己写了什么鬼东西ヽ(`Д′)?

那要不用个字典来存储对应关系? 然后就能写出可读性好的代码了

const keys = {
  id: "a",
  title: "b",
  author: "c"
}

let id = obj[keys["id"]]
let author = obj[keys["author"]]

通过这种方法, 在前后端各保留一份对应字典, 就可以在传输的过程中缩小体积节省时间, 并且在使用时不会造成困扰. 但是, 等等, keys 的结构好像看起来和 .proto 文件的结构有点像啊? 好像发现了什么不得了的事情.

.proto 文件和编码

实际上 .proto 文件就是一个数据格式的协议文件, 里面规定了数据的结构和类型. 比如开始的 BookInfo, int64 id = 1 这句就是定义了 BookInfo 的属性名为 id, 类型为 int64, tag 为 1. 这样传输的时候只需要传数据, 类型和 tag, 收到后按照 tag 对应的属性名将数据序列化为对象就可以正常使用了

关于编码方法, 官网写了很详细的介绍, 在这里简单介绍(翻译)下:

message 大专栏  Protobuf 简介及简单应用e">Test1 {
  required int32 a = 1;
}

message Test2 {
  required string b = 2;
}

使用编译好的文件来创建 Test1 和 Test2 对象, 将 a 和 b 分别设为 150 和 “testing”, serialize 后打印 buffer 可以看到分别输出了 16 进制的 08 96 0112 07 74 65 73 74 69 6e 67, 这就是 protobuf 将数据编码后的结果

首先看两个数据的前 8 位分别是08(0000 1000)12(0001 0010), protobuf 的编码规则是低 3 位存 type, 高 5 位存 tag. a 字段的类型 int32 是 Varint(type 0), tag 为 1, 因此拼起来是 00001(tag) 000(type) 即 08; b 字段同理拼起来为 00010(tag) 002(type) 即 12

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

因为 protobuf 不使用符号来截断数据, 因此就存在如何分割数据的问题, 在传输数字的时候 protobuf 将每字节的第 1 位用来表示是否结束, 剩下 7 位为数据位. 而且字节间是倒序编码但字节内的 7 位是正序编码.

96 01 = 1001 0110 0000 0001
	=> 001 0110, 000 0001 (第 1 个字节的第 1 位 1 表示数据还没结束, 第 2 个字节的第 1 位 0 表示数据结束)
	=> 000 0001 001 0110 (字节间顺序为倒序, 字节内顺序为正序)
	= 1001 0110 = 150

接下来说说 string 的编码方式, 通过第 1 个字节的 12 可知类型为 Length-delimited(string), tag 为 2, 接下来的第 2 个字节表示这段数据的长度 07, 后面的 7 个字节就是字符的 ASCII 码了

可以看出, protobuf 通过这些编码方式可以在保证数据类型, 数据结构的情况下还能将数据压缩到每个数据只附带 1 到 2 字节的多余数据, 更复杂结构的编码方式请前往官网查看

Protobuf 在 iOS 中的使用

本来是打算在 iOS 的项目中使用 protobuf 才去学习的(因为在 Swift 中使用 protobuf 需要做额外工作所以前面用 js 举例)

在 iOS 中使用 protobuf 有两种方法:

  • 如果使用 Objective-C 的话, 因为 protobuf 支持编译成 objc 所以直接编译后就能使用了
  • 如果使用的是 Swift 的话则有些麻烦, 不过还好苹果亲自动手写了 swift-protobuf 这个库, 虽然配置麻烦但至少比较官方, 维护和反馈都比较及时

swift-protobuf 的安装过程:

  1. clone 下来本地编译, 然后将编译出来的 protoc-gen-swift 放到 PATH 环境变量的目录下, 可以在命令行中输入 echo $PATH 来查看路径, 选一个放进去就行
  2. 在 xcode 项目中用 Cocoapods/Carthage/Swift Package Manager/源代码 的方式引入均可

然后就可以正常使用了, swift-protobuf 提供了很多方便的方法, 除了基础的 serialize/deserialize 外还能与 JSON 相互转换, 简直方便

除此之外 swift-protobuf 在进行转换时还会按照 swift 的代码风格, 比如 python 的后端将 key 定义为 html_url 那么 swift-protobuf 会解析为驼峰命名的 htmlUrl

还有一点, 因为 protobuf 是单纯的数据序列化, 因此会直接转化为 struct 而不是 class

最后总结一下

总体来说 protobuf 是一个很好的数据编码的方案

  1. 可以保证类型安全
  2. 可以极大地压缩数据量, 理论上可以将 JSON 压缩 1~∞ 倍
  3. 因为 protobuf 有默认值这个说法(比如 int 的默认值是 0, stirng 的默认值是 “”, bool 的默认值是 false), 因此如果缺少数据解析过程不会失败, 而且在取值的时候不会有问题, 在 Swift 中特别明显, 所有的字段解析后都不是 optional 的, 也省去了很多 if let id = id 这种判断
  4. 在定下来 API 接口的时候后端写好 .proto 文件就可以直接编译两份直接使用, 开发过程中基本上只需要关心网络连接即可

还有一些问题

Protobuf 在得到上述好处的同时还有一些问题

  1. 分布式的不同步性. 因为需要将 .proto 文件编译后在程序中使用, 如果在分布式的系统(或者最简单的例子 客户端)中, 用户所使用的版本一般不同(客户端一般不会强制更新). 如果新的版本中 .proto 文件进行了修改, 虽然 protobuf 的解析过程不会出现问题(多余字段忽略, 缺失字段用默认值), 但在使用的过程中还是会有一定的影响
  2. 不推荐删除字段, 和第 1 点一样的原因, 而且如果这个字段真的没用过要删除的话新的字段也不能使用该字段的 tag 因为 protobuf 在解析的时候是用 tag 和字段名对应的, 客户端拿到了 tag 还是会对应到旧的字段上
  3. Protobuf 2 中的 required 和 optional 这两个用来标记字段的关键字在 3 中取消了, 理由同 1, 2 因此使用 Protobuf 来保证字段缺失问题只能回去用 2 的版本
  4. Swift 写客户端(其实什么语言都是一样): 因为有默认值的存在所以对于 0, false 等值的用法就要小心, 因为无法保证接收到的到底是确实是这个数据还是丢失了字段
  5. 关于在 Swift 中的使用还有一个理由可以看我的另外一篇Swift4 JSON 解析
  6. Javascript 写前端: 前端因为请求 js 脚本到本地执行所以不存在不同步的问题, 但是为了传 protobuf 还要把解析文件一起传还是很蛋疼的. 而且 JSON 本来就是 Javascript 的内置对象, 操作方便到天际, 根本没可比性

目前想到的最好使用场合

网络游戏开发(强制客户端更新, 大量数据, 对传输速度要求高)

安利一波

黑仪·约会甜到炸/荡漾真可爱/疯狂打 call/?(o)?

原文地址:https://www.cnblogs.com/lijianming180/p/12225820.html

时间: 2024-11-08 16:17:37

Protobuf 简介及简单应用的相关文章

Protobuf(一)——Protobuf简介

Protobuf简介 ? 什么是 Google Protocol Buffer? 假如您在网上搜索,应该会得到类似这样的文字介绍: ? Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件.他们用于 RPC 系统和持续数据存储系统. ? Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,

实时计算,流数据处理系统简介与简单分析

转自:http://www.csdn.net/article/2014-06-12/2820196-Storm 摘要:实时计算一般都是针对海量数据进行的,一般要求为秒级.实时计算主要分为两块:数据的实时入库.数据的实时计算.今天这篇文章详细介绍了实时计算,流数据处理系统简介与简单分析. 编者按:互联网领域的实时计算一般都是针对海量数据进行的,除了像非实时计算的需求(如计算结果准确)以外,实时计算最重要的一个需求是能够实时响应计算结果,一般要求为秒级.实时计算的今天,业界都没有一个准确的定义,什么

特殊权限简介(简单的理解)

特殊权限: suid:运行程序时,相应的进程的属主是程序文件自身的属主,而不是启动者. chmod u+s(u-s) file sgid:运行某个程序时,对应进程的属组是程序文件自身的属组,而不是启动者的所属的基本组.(常常与sticky一起使用,控制公共文件,组人能修改所有文件不能删除别人文件) chmod g+s(g-s) file sticky:在一个公共的目录中,每个人都可以创建文件,删除自己的文件,但不能删除别人的文件 chmod o+t filedir 特殊权限简介(简单的理解),布

Robot Framework-工具简介及简单使用

界面详解 Project.Suite页面 项目结构:整修项目中所有结构显示及相关引用文件的显示,树型结果展示.可选择针对部分TestCase进行执行 引用:添加外部引用包.资源.变量.失败帮助 参数:添加变量.数组 数据:使用TDD时,此处可添加一些测试引用数据 TestCase页面 设置区:主要用来配制此TestCase执行阶段的的操作 Documentation:说明,可添加TestCase的说明.上下文.参数说明.业务等.无内容限制 Setup:执行用例前可进行的一些设置,数据初始化.上下

C#可扩展编程之MEF学习笔记(一):MEF简介及简单的Demo

在文章开始之前,首先简单介绍一下什么是MEF,MEF,全称Managed Extensibility Framework(托管可扩展框架).单从名字我们不难发现:MEF是专门致力于解决扩展性问题的框架,MSDN中对MEF有这样一段说明: Managed Extensibility Framework 或 MEF 是一个用于创建可扩展的轻型应用程序的库. 应用程序开发人员可利用该库发现并使用扩展,而无需进行配置. 扩展开发人员还可以利用该库轻松地封装代码,避免生成脆弱的硬依赖项. 通过 MEF,不

knockout.js的简介和简单使用

1.knockout简介knockout是一个轻量级的UI类库,通过MVVM模式使JavaScript前端UI简单化knockout有四大重要概念:1)声明式绑定:使用简明移读的语法很容易地将模型(model)数据关联到dom元素上.2)UI界面自动刷新:当宁的模型状态(model state)改变时,您的UI将自动更新3)依赖跟踪:为转变和联合数据,在你的模型数据之间隐式建立关系4)模板:为你的模型数据快速编写复杂的可嵌套的UI 2.knockout特性和好处特性:优雅的依赖跟踪声明式绑定灵活

NoSQL初探之人人都爱Redis:(1)Redis简介与简单安装

一.NoSQL的风生水起 1.1 后Web2.0时代的发展要求 随着互联网Web2.0网站的兴起,传统的关系数据库在应付Web2.0网站,特别是超大规模和高并发的SNS类型的Web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题: (1)对数据库高并发读写的需求 网站要根据用户个性化信息来实时生成动态页面和提供动态信息,所以基本上无法使用动态页面静态化技术,因此数据库并发负载非常高,往往要达到每秒上万次读写请求.关系数据库应付上万次SQL查询还勉强顶得住,但是应付上万次SQL写数据请求

webUI框架miniUI,easyUI,extJS,Bootstrap简介及简单部署

本文为大家讲解的是webUI框架miniUI,easyUI,extJS,Bootstrap简介及简单部属,感兴趣的同学参考下 ExtJS是一种主要用于创建前端用户界面,是一个基本与后台技术无关的前端ajax框架. jQuery EasyUI是一组基于jQuery的UI插件集合,而jQuery EasyUI的目标就是帮助web开发者更轻松的打造出功能丰富并且美观的UI界面.开发者不需要编写复杂的javascript,也不需要对css样式有深入的了解,开发者需要了解的只有一些简单的html标签. B

protobuf简介和使用

1.Protocol Buffers简介 Protocol Buffers (ProtocolBuffer/ protobuf )是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储.通信协议等方面.现阶段支持C++.JAVA.Python等三种编程语言. 2.protobuf相比Xml的优点 •更简单 •数据描述文件只需原来的1/10至1/3 •解析速度是原来的20倍至100倍 •减少了二义性 •生成了更容易在编程中使用的数据访问类 3.安装 yum -