protobuf
Protocol Buffer(protobuf)是 Google 提供的一种数据序列化协议
官方解释:
Protocol Buffers are a language-neutral, platform-neutral,
extensible way of serializing structured data for use in communications protocols,
data storage, and more, originally designed at Google
译:Protocol Buffers 是一种轻便高效并且和语言、平台无关、可扩展的序列化结构数据格式,很适合作为数据存储或者通信交互的格式。
序列化
是将对象的状态信息转换为可以存储或传输的形式的过程,前端接触最多的就是 JSON 序列化的 api;如下
1 | const obj = { message: 'Hello World!' }; |
二进制
1 | // node |
How fast ?
从官方的测试数科院看到一条消息数据,用 protobuf 序列化后的大小是 json 的 10 分之一,只有 xml 格式的 20 分之一,但是性能却是它们的 5~100 倍! 这是飞一样的感觉~~这也就是为什么我们要了解它的原因,万一哪天用到了呢;快的原因这里不去深究,感兴趣的同学可以从文献中参考一二; proto3 中导入 proto2 定义的消息类型,反之亦然。然而,proto2 中的枚举不能直接用在 proto3 语法中(但导入到 proto2 中 proto3 定义的枚举是可用的)。定义
proto3 比 proto2 支持更多语言却更加简洁。去除了一些复杂的语法和特性,更强调约定而弱化语法,所以我们基于 proto3 进行示例;
test.proto 文件如下
其中
syntax = “proto3”;指定 proto 版本,默认为 proto2,并且在 proto 文件中必须是第一行
proto 文件可以相互引用(import),因此 package 可以给文件一个的命名空间 防止消息类型之间的名称冲突
message 消息类型,前端理解成一个 interface 会事半功倍;
消息类型中的定义都遵循 [keywords?] [type] [custom_key] = [tag]
keywords:
- singular 格式正确的消息可以有 0 个或 1 个该字段(但不能多于 1 个)。proto3 语法的默认字段规则;
- repeated 格式正确的消息中该字段可以重复任意次数(包括 0 次)。重复值的顺序将被保留;
- reserved 在使用彻底删除或注释掉某个字段的方式来更新消息类型时,
将来其他用户再更新该消息类型时可能会重用这个字段编号。
后面再加载该 .ptoto 的旧版本时会如数据损坏,隐私漏洞等问题。
防止该问题发生的办法是将需要删除字段的编号(或字段名称,字段名称会导致在 JSON 序列化时产生问题)设置为保留项 reserved;
type:
- 可以是数据中自己定义的 message(理解成 interface 可以被嵌套使用);
- 也可以是 type 类型对应表中的值(和定义 interface 时的类型声明类似);
custom_key: 自己定义的 key 键;
tag: proto 文件中对我们 custom_key 键的描述,或者理解成 react 组件中的唯一 key 值;
tag 用于在消息二进制格式中标识字段,同时要求消息一旦使用字段编号就不应该改变。
另外 1 到 15 的字段编号需要用 1 个字节来编码,编码同时包括 tag 本身的值和以及该 tag 对应的 type。
16 到 2047 的字段编号需要用 2 个字节。
因此应将 1 到 15 的编号用在消息的常用字段上。注意应该为将来可能添加的常用字段预留字段编号。
最小的字段编号为 1,最大的为 2^29 - 1(536,870,911)。
注意不能使用 19000 到 19999 (FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber)的字段编号,因为这些是 protocol buffer 内部保留的
如果使用了这些预留的编号 或者之前用户自己保留的字段(reserved)protocol buffer 编译器会发出警告。
此外 pb 还支持嵌套,枚举,定义 map(map<key_type, value_type> map_field = N
);
type 类型对应表格:
选择一个好的工具
目前流行的是以上三种从使用者的数量和文档完善程度,在 node 端 protobuf.js 能够直接读取 proto 文件等方面考虑
本次的示例是选择 protobuf.js 当然大家感兴趣也能去选择别的工具.
Talk is cheap show me your code!
了解了一些基础的概念之后我们就可以下狠手了!
浏览器中(前端)使用
前端无法处理 proto 类型的文件,因此需要使用 protobuf.js 工具库
安装之后在./node_modules/protobufjs/bin/pbjs 文件目录里提供了 pbjs 命令
不过命令藏得很深 并且没有设置 alias 所以推荐使用 npx 将我们之前的 test.proto 文件进行转化;
npx pbjs -t json _.proto > _.json
将 proto 转化成 json
npx pbjs -t json-module -w commonjs -o _.js _.proto
将 proto 转化成 js
这里推荐转化成 js 因为转成 json 也得用内置的 api(本质还是转成了 js)转化之后才能获得需要的数据;
proto 文件转成的 json 文件
json 文件中会把 proto 文件中的信息根据 proto 的规则进行转化
proto 文件转成的 js 文件
可以看到 js 文件比起 json 文件区别就是$protobuf 进行了一次封装,从而提供给了我们可操作的 api;
获取数据并且使用对应的 proto 进行数据解析
result:
pbMessage:
data:
node 中的使用
有部分资料还是说在 node 环境中也需要像浏览器环境一样把 proto 文件进行处理再使用,
但是 protobufjs 的库能够让我们直接在 node 环境中使用 pb 文件不需要额外的处理;这也是我们选择 protobujs 的理由之一
node 中的使用和浏览器中差异并不大,只是 node 因为 protobufjs 的便携性 可以不用使用转化后的文件,而是直接使用 proto 文件进行处理;
node 输出
到此 node 端简单的用法也就完成了,包含了编码和解码;
结语
选择什么方案都是结合当前自己的需求来判别,protobuf 虽然在资源上的占比传输上都很优秀,个人感觉在当前没有性能上的硬性要求时也不用刻意使用,
毕竟 protobuf 的传输都建立在二进制上,数据上如果有问题调试起来非常困难,并且也需要前后端共同维护一份 proto 定义,维护、沟通的成本也得考虑,
不过维护的处理方式也可以有别的方式(子仓库,动态 pb 等)不过后端传 proto 文件,前端处理处理也就根据麻烦了,因此因地制宜才是上策。
参考文献: