互联网 · 2017年6月25日 0

ProtoBuf.js 使用技巧

Protocol Buffers

Protocol buffers 是一个用来序列化结构化数据的技术,支持多种语言诸如 C++、Java 以及 Python 语言,可以使用该技术来持久化数据或者序列化成网络传输的数据。相比较一些其他的 XML 技术而言,该技术的一个明显特点就是更加节省空间(以二进制流存储)、速度更快以及更加灵活。

具体参见 Google 开发文档:https://developers.google.com/protocol-buffers/docs/overview

ProtoBuf.js

上面抄的内容不是本文重点,重点是 Google 没有推出官方的 JavaScript 库,在神奇的同性交友社区 Github 上,我找到了一个纯绿色无公害的 ProtoBuf.js 库。它是一个由纯 JavaScript 实现构建在 ByteBuffer.js 之上的 .proto 文件解析库,包含 Message 构建、数据序列化和反序列化等功能。

Google Protocol Buffers 传输的数据是二进制格式,JavaScript 天生不具备处理二进制数据的能力,所以要依赖 ByteBuffer.js ,ByteBuffer 和 ProtoBuf 都是由同一个团队 dcode.io 出品,ByteBuffer 可以单独使用,兼容 IE8+。

.proto 文件

.proto 文件是 Protocol Buffers 的结构化数据定义,结构化数据被称为 Message,具体的就不解释了,可以看最末的两篇参考文章。

ProtoBuf.js 可以解析 .proto,构建 Message 对象,实现数据的序列化和反序列化,对于 .proto 不了解可以看官方文档:https://developers.google.com/protocol-buffers/docs/proto3

下面举几个例子简单说明:

.proto文件初始化和构建 Message,例子参见:https://github.com/dcodeIO/ProtoBuf.js/blob/master/examples/websocket/www/index.html

1
2
var builder = ProtoBuf.loadProtoFile("./example.proto");
var Message = builder.build("Message");

对于声明了 package 的.proto,只需在构建时把包名带上就行。

1
2
var builder = ProtoBuf.loadProtoFile("./example.proto");
var Message = builder.build("com.xxx.Message");

使用loadProtoFile()会让.proto文件明文暴露,所以可以使用 ProtoBuf.js 提供的工具将.proto转义成 jsonjs,参见:https://github.com/dcodeIO/ProtoBuf.js/wiki/pbjs

安装 node 模块:

1
npm install -g protobufjs

example.proto 为例,在终端执行:

1
pbjs src/address_book.proto -t js

会输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var _root = dcodeIO.ProtoBuf.newBuilder({})['import']({
    "package": null,
    "messages": [
        {
            "name": "Message",
            "fields": [
                {
                    "rule": "required",
                    "type": "string",
                    "name": "text",
                    "id": 1
                }
            ]
        }
    ]
}).build();

在实际应用时,通常一个 .proto 文件里面会有很多个 Message 类型,所以会将输出结果保存为一个 builder,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var builder = dcodeIO.ProtoBuf.newBuilder({})['import']({
    "package": null,
    "messages": [
        {
            "name": "Message",
            "fields": [
                {
                    "rule": "required",
                    "type": "string",
                    "name": "text",
                    "id": 1
                }
            ]
        }
    ]
});

存成 builder 就可以根据需求构建 Message 类型对象,

1
2
3
4
var Message = builder.build("Message");
var msg = new Message({
  text: 'message from maxzhang.'
});

序列化和反序列化

在 Web 端使用 Protocol Buffers 时,无论发送还是接收的数据都应当是二进制格式,二进制数据可以直接使用 Message 类型对象解析,

1
2
3
4
5
6
7
8
9
10
11
12
// 序列化
function encode(jsonData) {
  var Message = builder.build("Message");
  var msg = new Message(jsonData);
  return msg.toArrayBuffer();
}

// 反序列化
function decodeMessage(data) {
  var msg = builder.build("Message").decode(data);
  return msg;
}

decode() 返回一个 Message 实例对象(可以等同于 JSON Object),实例中的属性便是 .proto 文件中声明的变量与类型,

.proto 文件中声明数据类型需要遵循 Protocol Buffers 数据类型 规则,如下表:
Protocol Buffers 数据类型

由于上图不包括 JavaScript 对应的数据类型,所以我自己补充了一个数据类型对应关系(每种数据类型我并没有一一验证使用过,可能有误,欢迎指正):

.proto Type JavaScript Type
double Long
float float
int32 int
int64 Long
uint32 int
uint64 Long
sint32 int
sint64 Long
fixed32 int
fixed64 Long
sfixed32 int
sfixed64 Long
bool boolean
string string
bytes ByteBuffer

ByteBuffer.js

bytes 类型是二进制格式数据,需要使用 ByteBuffer.js 处理,ByteBuffer 可以直接操作二进制数据,例子:

1
2
3
4
5
6
var ByteBuffer = require("bytebuffer");

var bb = new ByteBuffer()
            .writeIString("Hello world!")
            .flip();
console.log(bb.readIString() + " from ByteBuffer.js");

ByteBuffer 可以直接写入或读取任意一种类型的值,值得长度为 8bits – 64bits,特殊的按位写入需要使用 JavaScript 位移操作符,比如:

1
2
3
4
5
6
// 写入数据格式 len of id(4bits) + id(12bits)
var bb = new ByteBuffer(16);
var id = 1;
bb.writeInt8(String(id).length << 4);
bb.writeInt8(id);
bb.flip();

更多 ByteBuffer 接口参见API:https://github.com/dcodeIO/ByteBuffer.js/wiki/API

Message API 中的 toArrayBuffer() toBuffer() 等方法底层实际调用的是 ByteBuffer 的接口,与 ByteBuffer 不同的是“Message 对象是按照 JSON 的方式修改值,调用 toArrayBuffer() 接口序列化数据,调用 decode() 接口反序列数据”。

Long.js

由于 JavaScript 精度问题,所以 int64uint64 等类型数据会被转换成 Long.js 对象实例,Long 并不太复杂,与 bignumber.js 类似,具体参考 Long.js API.

WebSocket

关于 WebSocket 提供一个简单的例子
实际应用与例子差不多,就是做两件基础的事:

  • 连接 WebSocket,从 socket 通道拿到二进制数据,反序列化解析成 Message 对象。
  • 实例化 Message 对象,然后序列化成二进制数据,发送给服务端。

参考文章

 

转自:http://www.maxzhang.com/2015/09/ProtoBuf-js使用技巧/

Share this: