浏览器中使用Protobuf
Protobuf 作为Google推出的一种二进制结构化数据的格式,可以很好地压缩数据大小,便于传输,同时序列与反序列化的性能很好,所以得到广泛地使用。
但是,前端圈一直以来是以JSON作为数据格式。主要原因如下:
原生支持
JSON格式支持已添加到JavaScript标准中,已经是前端的事实以及规范标准。
易操作性
JSON格式不仅易于阅读,而且可以直接由JavaScript对象序列化而来,同时可以直接反序列化为JavaScript对象,很便捷。
性能良好
JSON格式不管是解析还是序列化的性能都很不错,而且不像XML那样臃肿。
注意 本文并不是为了论证Protobuf比JSON格式优秀,或者说是要在前端弃用JSON。仅仅是为了研究Protobuf在前端使用的可行性,以及如何去使用。
因为前端技术日新月异,很多新技术如WebAssembly, WebRTC,WebSocket, HTTP2等的出现,以及对实时性要求越来越高,使得消息如何传递重新进入话题中心。而Protobuf作为一种优秀的新数据格式标准,很适合消息传递。
关于 Protobuf
对于 Protobuf 是个什么东西,如何编写,网上已经有太多示例了,这里就不再展开描述了。
如果你对 Protobuf 不太熟悉,请参考 Protobuf3 语法指南。
编写 proto 文件
使用protobuf作为传输数据格式时,需要先编写相应proto文件。示例(example.proto)如下:
syntax = "proto3";
message User {
string name = 1;
int32 age = 2;
bool male = 3;
}
message Like {
string sender = 1;
string post = 2;
int64 timestamp = 3;
}
这里写了两个数据模型,分别是User
和Like
模型。
在有了数据模型文件后,需要将其编译成 JavaScript 文件,才能用在前端浏览器中。
编译 proto 文件
进行编译之前,需要先安装相应的 Protobuf 工具。
注意: 这里有两种方式,一种是使用 dcodeIO/Protobuf.js,另一种是使用 Protobuf 官方的 google-protobuf
相对来说,前一种能找到的示例可能更多,后一种很少。甚至连 gRPC 中 Node.js 示例 都提到的是前一种。 但是个人觉得前一种使用方式太过繁琐,甚至有 README 都看不完的感觉。所以这里使用后一种方式。
将 proto 文件编译成代码:
protoc --js_out=import_style=commonjs,binary:. example.proto
经过编译之后,会在当前目录生成一个example_pb.js
的文件。这里生成的是 commonjs 风格的代码,还有一种是 Closure 风格的,但是因为对 Closure 不太熟悉,所以略过了…
使用编译后的文件
因为编译成的 commonjs 风格的代码并不能直接在浏览器中使用,所以需要webpack
之类的工具将代码融合进去。
const messages = require('./example_pb.js')
// 生成一个 User 对象
let user = new messages.User()
user.setName('Protobuf')
user.setAge(3)
user.setMale(true)
// 将 User 对象序列化成二进制字符串,便于发送到服务端
let bytes = user.serializeBinary()
// 通过 HTTP API 或者 WebSocket 接收到发送过来的数据,反序列化成对象
let like = messages.Like.deserializeBinary(data)
let sender = like.getSender()
将最终的代码文件通过webpack
或者browserify
等打包工具打包即可在浏览器中引用并正常使用了。
其它
由于 Protobuf 数据是二进制的,所以对其进行处理(特别是接收数据)时,需要注意数据格式的选取。
比如,在使用 WebSocket 的时候,将其类型改为arraybuffer
可能会解决某些解析成空对象的错误。
let ws = new WebSocket('ws://localhost:8080/ws')
ws.binaryType = 'arraybuffer'
总体来说,在前端使用 protobuf 并不是太成熟,很可能会遇到一些问题,可以在 Protobuf 的 Github issues 页面搜寻一下相关解决方案或提出你的问题。