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

<td class="code">
  <div class="top-box hide">
  </div>
  
  ```

var builder = ProtoBuf.loadProtoFile("./example.proto"); var Message = builder.build(“Message”);

    </td>
  </tr>
</table></figure> 

对于声明了 package`.proto`,只需在构建时把包名带上就行。<figure class="highlight javascript"> 

<table>
  <tr>
    <td class="gutter">
      ```
<span class="line">1</span>
<span class="line">2</span>
</td>

<td class="code">
  <div class="top-box hide">
  </div>
  
  ```

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

    </td>
  </tr>
</table></figure> 

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

安装 node 模块:<figure class="highlight bash"> 

<table>
  <tr>
    <td class="gutter">
      ```
<span class="line">1</span>
</td>

<td class="code">
  ```

npm install -g protobufjs

    </td>
  </tr>
</table></figure> 

以 [example.proto](https://github.com/dcodeIO/ProtoBuf.js/blob/master/examples/websocket/www/example.proto) 为例,在终端执行:<figure class="highlight bash"> 

<table>
  <tr>
    <td class="gutter">
      ```
<span class="line">1</span>
</td>

<td class="code">
  ```

pbjs src/address_book.proto -t js

    </td>
  </tr>
</table></figure> 

会输出:<figure class="highlight javascript"> 

<table>
  <tr>
    <td class="gutter">
      <div class="top-box hide">
      </div>
      
      ```
<span class="line">1</span>
<span class="line">2</span>
<span class="line">3</span>
<span class="line">4</span>
<span class="line">5</span>
<span class="line">6</span>
<span class="line">7</span>
<span class="line">8</span>
<span class="line">9</span>
<span class="line">10</span>
<span class="line">11</span>
<span class="line">12</span>
<span class="line">13</span>
<span class="line">14</span>
<span class="line">15</span>
<span class="line">16</span>
</td>

<td class="code">
  <div class="top-box hide">
  </div>
  
  ```

var _root = dcodeIO.ProtoBuf.newBuilder({})[‘import’]({ “package”: null, “messages”: [ { “name”: “Message”, “fields”: [ { “rule”: “required”, “type”: “string”, “name”: “text”, “id”: 1 } ] } ] }).build();

    </td>
  </tr>
</table></figure> 

在实际应用时,通常一个 `.proto` 文件里面会有很多个 Message 类型,所以会将输出结果保存为一个 builder,<figure class="highlight javascript"> 

<table>
  <tr>
    <td class="gutter">
      <div class="top-box hide">
      </div>
      
      ```
<span class="line">1</span>
<span class="line">2</span>
<span class="line">3</span>
<span class="line">4</span>
<span class="line">5</span>
<span class="line">6</span>
<span class="line">7</span>
<span class="line">8</span>
<span class="line">9</span>
<span class="line">10</span>
<span class="line">11</span>
<span class="line">12</span>
<span class="line">13</span>
<span class="line">14</span>
<span class="line">15</span>
<span class="line">16</span>
</td>

<td class="code">
  <div class="top-box hide">
  </div>
  
  ```

var builder = dcodeIO.ProtoBuf.newBuilder({})[‘import’]({ “package”: null, “messages”: [ { “name”: “Message”, “fields”: [ { “rule”: “required”, “type”: “string”, “name”: “text”, “id”: 1 } ] } ] });

    </td>
  </tr>
</table></figure> 

存成 builder 就可以根据需求构建 Message 类型对象,<figure class="highlight javascript"> 

<table>
  <tr>
    <td class="gutter">
      ```
<span class="line">1</span>
<span class="line">2</span>
<span class="line">3</span>
<span class="line">4</span>
</td>

<td class="code">
  <div class="top-box hide">
  </div>
  
  ```

var Message = builder.build(“Message”); var msg = new Message({ text: ‘message from maxzhang.’ });

    </td>
  </tr>
</table></figure> 

## 序列化和反序列化 {#序列化和反序列化}

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

<table>
  <tr>
    <td class="gutter">
      ```
<span class="line">1</span>
<span class="line">2</span>
<span class="line">3</span>
<span class="line">4</span>
<span class="line">5</span>
<span class="line">6</span>
<span class="line">7</span>
<span class="line">8</span>
<span class="line">9</span>
<span class="line">10</span>
<span class="line">11</span>
<span class="line">12</span>
</td>

<td class="code">
  <div class="top-box hide">
  </div>
  
  ```

// 序列化 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; }

    </td>
  </tr>
</table></figure> 

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

在 `.proto` 文件中声明数据类型需要遵循 [Protocol Buffers 数据类型](https://developers.google.com/protocol-buffers/docs/proto3#scalar) 规则,如下表:  
![Protocol Buffers 数据类型](http://www.maxzhang.com/2015/09/ProtoBuf-js%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7/protobuf-types.jpg) 

由于上图不包括 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 {#ByteBuffer-js}

bytes 类型是二进制格式数据,需要使用 [ByteBuffer.js](https://github.com/dcodeIO/ByteBuffer.js) 处理,ByteBuffer 可以直接操作二进制数据,例子:<figure class="highlight javascript"> 

<table>
  <tr>
    <td class="gutter">
      ```
<span class="line">1</span>
<span class="line">2</span>
<span class="line">3</span>
<span class="line">4</span>
<span class="line">5</span>
<span class="line">6</span>
</td>

<td class="code">
  <div class="top-box hide">
  </div>
  
  ```

var ByteBuffer = require(“bytebuffer”);

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

    </td>
  </tr>
</table></figure> 

ByteBuffer 可以直接写入或读取任意一种类型的值,值得长度为 8bits &#8211; 64bits,特殊的按位写入需要使用 JavaScript 位移操作符,比如:<figure class="highlight javascript"> 

<table>
  <tr>
    <td class="gutter">
      ```
<span class="line">1</span>
<span class="line">2</span>
<span class="line">3</span>
<span class="line">4</span>
<span class="line">5</span>
<span class="line">6</span>
</td>

<td class="code">
  <div class="top-box hide">
  </div>
  
  ```

// 写入数据格式 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();

    </td>
  </tr>
</table></figure> 

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

在 [Message API](https://htmlpreview.github.io/?https://raw.githubusercontent.com/dcodeIO/ProtoBuf.js/master/docs/ProtoBuf.Builder.Message.html) 中的 `toArrayBuffer()` `toBuffer()` 等方法底层实际调用的是 ByteBuffer 的接口,与 ByteBuffer 不同的是“Message 对象是按照 JSON 的方式修改值,调用 `toArrayBuffer()` 接口序列化数据,调用 `decode()` 接口反序列数据”。

### Long.js {#Long-js}

由于 JavaScript 精度问题,所以 `int64``uint64` 等类型数据会被转换成 [Long.js](https://github.com/dcodeIO/Long.js) 对象实例,Long 并不太复杂,与 [bignumber.js](https://github.com/MikeMcl/bignumber.js) 类似,具体参考 [Long.js API](https://github.com/dcodeIO/Long.js#api).

## WebSocket {#WebSocket}

关于 WebSocket 提供一个简单的[例子](https://github.com/dcodeIO/ProtoBuf.js/blob/master/examples/websocket/www/index.html),  
实际应用与例子差不多,就是做两件基础的事:

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

## 参考文章 {#参考文章}

  * [http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/](http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/)
  * [http://www.cnblogs.com/royenhome/archive/2010/10/29/1864860.html](http://www.cnblogs.com/royenhome/archive/2010/10/29/1864860.html)

&nbsp;


  转自:[http://www.maxzhang.com/2015/09/ProtoBuf-js使用技巧/](http://www.maxzhang.com/2015/09/ProtoBuf-js%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7/)

💬 评论

``` 1 2 ```