Bitshares 开发者系列(2)—— Bitsharesjs-ws 库源码解读

View this thread on: d.buzz | hive.blog | peakd.com | ecency.com
·@bts500·
0.000 HBD
Bitshares 开发者系列(2)—— Bitsharesjs-ws 库源码解读
> 官方接口访问库 GitHub : https://github.com/bitshares/bitsharesjs-ws
> 在阅读源码之前,最好先浏览一遍 Bitshares 官方公开的开发文档,虽然文档中对开发细节描述的不是很清楚,但也能对系统的通讯方式有个详细的了解。

### 项目库介绍 
---
`bitsharesjs-ws` 是比特股的 Github 官方账号创建的一个项目,主要功能是提供一个访问比特股 API 节点的工具,与 API 节点通讯访问方式采用的是 WebSocket 方式,代码采用JS编写,可以同时运行于浏览器端与Node环境下。

项目代码结构如下:
```
+ build         * 编译版本的项目文件
+ examples      * 项目库的使用案例      
+ lib           * 项目库源码文件
+ test          * 项目单元测试
+ tools         * 编译工具
```

直接进入 lib 目录,打开 `index.js` 文件阅读源码,你会发现项目库共导出三个如下模块:
```javascript
export {
    Apis,           //  工具库的主要接口,对外公开了 API 的访问方式,与节点服务器的通讯全部依赖此模块的内部实现
    ChainConfig,    //  Bitshares 区块链公开的参数配置项
    Manager         //  与节点服务器之间的连接管理器,负责管理与检查和节点服务器之间的连接状态
}
```

### Apis 模块实现逻辑
---
Apis 模块对应的实现文件是 `ApisInstances.js`,该模块引入了 `ChainWebSocket.js` 、`GrapheneApi.js` 等内部模块,这些内部子模块的作用稍后再作描述,先看Apis模块导出的供外部系统调用的方法:
```javascript
export default {
  /**
   * String   cs              代表要连接的完整钱包节点地址。
   * Boolean  connect         创建成功之后是否直接执行创建的对象的 connect 方法。
   * int      connectTimeout  连接节点时,响应超时的时间。
   * Boolean  enableCrypto    是否进行加密通讯。
   *
   * @return  该方法会返回一个 ApiInstance 的实例对象,全局共享此单例对象。
   */
  instance (cs = "ws://localhost:8090", connect, connectTimeout = 4000, enableCrypto)

  /**
   * Function   callback  设置一个当网络连接状态发生改变的时候的回调函数。
   */
  setRpcConnectionStatusCallback (callback)

  /**
   * 设定当网络连接失败的时候,是否自动重连,会重新设置 autoReconnect 的值。
   */
  setAutoReconnect (auto)

  /**
   * 重置当前的连接,假如当前是连接状态,会直接
   */
  reset (cs = "ws://localhost:8090", connect, connectTimeout = 4000)
  
  /**
   * 获取当前连接的服务器节点的区块链 ID 
   */
  chainId ()
  
  /**
   * 关闭当前的连接  
   */
  close ()
}
```
查看上面导出的方法实现,可以发现 instance 方法会构建一个 ApisInstance 对象的实例,并让外部系统该通过此实例调用对应类别的API接口,关于 ApisInstance 对象的定义如下:
```javascript
Class ApiInstance {
    chain_id    // 当前连接的区块链 id
    String url  // 当前连接的完整钱包节点地址。
    statusCb
    ChainWebSocket ws_rpc // 创建的 ChainWebSocket 对象实例。    
    Promise init_promise
    GrapheneApi _db
    GrapheneApi _net
    GrapheneApi _hist
    GrapheneApi _crypt


    /**
     * 调用 instance() 的时候,第二个参数传递 true,会自动调用此函数连接区块节点,旨在初始化网络连接。
     * 并且初始化 inst 内部的各个关键属性,init_promise, _db, ws_rpc ...
     */
    connect (cs, connectTimeout, enableCrypto = false)
    close()
    db_api()
    network_api()
    history_api()
    crypto_api()
    setRpcConnectionStatusCallback()
}
```
此处方法的功能是与 Apis 模块提供的那些同名方法是相同的,因为 Apis 模块公开的那些方法,也是最终的中转到了此处的方法实现。重点需要注意的就是此对象的内部属性,主要有`ws_rpc`、`init_promise`、`_db`、`_net`、`_hist`、`_crypt`等。  
<br/>
- `ws_rpc` 此属性的值是一个 ChainWebSocket 对象的实例,内部包装了 ReconnectWebSocket 对象,并定义了一套API接口的调用流程。最终由此对象向服务器节点发起 API 调用,并处理返回结果,这样一个完整的访问流程,就是此内部模块定义的。

- `init_promise` 一个Promise对象,因为网络接口调用、WebSocket连接等操作都是异步响应的,所以此处定义了一个 Promise 对象来给外部系统使用,当与服务器节点之间的连接全部初始化完成之后,此异步对象就会变为完成状态。

- `_db` `_net` `_hist` `_crypt` 这些属性都分别是一个 GrapheneApi 对象的实例,用于区分不同类别的API调用,因为 Bitshares 系统为不同的 API 接口设置有不同的 API 访问令牌,所以单独为每个 API 类型都创建了一个实例,以便外部系统调用不同类型的 API 的时候,不用反复设置 API 令牌标识。


关于 ChainWebSocket 与 ReconnectWebSocket 应该进一步解释下它们的作用,首先说一下 ReconnectWebSocket, ReconnectWebSocket 实现了在浏览器环境下的断线重连机制,因为整个模块是使用 WbeSocket 与服务节点进行通信连接的,所以可能会偶尔的发生 WebSocket 断开连接的问题。

因为当断开连接以后,可能就会导致订阅通知与API调用接口不能正常使用,这就需要外部系统自己再定义一套复杂的连接状态检查规则,来进行断线之后的连接与初始化操作。所以索性就在模块内部实现了一个 ReconnectWebSocket 模块,实现一套 Bitshares 系统专用的断线重连的制度,来保证与服务器节点连接的稳定性,减少外部系统的复杂程度,让外部系统能专心于业务逻辑的开发。

具体是如何定义重连制度的,可以自己查看 ReconnectWebSocket 模块的源码,这里不再详细介绍,但其实官方引用的 ReconnectWebSocket 在实现的时候也有一个问题,ReconnectWebSocket 仅仅是包装了 WebSocket 对象,并在内部依赖 document 与 window 对象,所以导致断线重连的制度在Nodejs的环境中是不可用的,只有在浏览器的环境下才会有断线重连保护。

ChainWebSocket 是建立在可靠的 ReconnectWebSocket 连接之上的,然后针对 Bitshares 系统的特性,构建了一个API的访问流程,和订阅通知的处理流程。外部系统的所有API操作操作都会经由 ChainWebSocket 处理并通过 WebSocket 发送到服务器节点,然后在响应结果发生以后,再由 ChainWebSocket 解析并转交给外外部系统处理。

由于在 Nodejs 的环境中是不存在 WebSocket 对象的,所以引入了一个开源的第三方 WebSocket 的实现 —— `ws`:
```javascript
if (typeof WebSocket === "undefined" && !process.env.browser) {
    WebSocketClient = require("ws");
} else if (typeof(WebSocket) !== "undefined" && typeof document !== "undefined") {
    WebSocketClient = require("ReconnectingWebSocket")
} else {
    WebSocketClient = WebSocket;
}
```
那么 Apis 模块的功能就已经全部介绍完了,梳理一下你的 API 访问流程应该是这样的:  
<br/>
> 通过 Apis.instance 连接操作,告诉模块要到的服务器节点,并且配置是否开启断线重连制度。

> 然后通过返回的 instance 对象属性 init_promise 来指定连接成功之后的下一步操作。

> 假如你在连接成功之后通过 instance.__db 属性调用了 `get_account` API,那么你的请求会经由 GrapheneApi 打上对应的API令牌标识,再投递给 ChainWebSocket 发送请求,当响应结果到来的时候,又会回调你的处理函数来完成一个完整的API请求流程。

### Manager 模块实现逻辑
---
辅助 Apis 模块实现 WebSocket 连接的管理,更详细的功能不再描述,可以自行查看源码。

### Manager 模块实现逻辑
---
定义了一些 Bitshares 链上的一些属性配置,我目前对 Bitshares 系统的其他细节还不了解,就不作解读说明了,等以后学习了更多的知识,再补充这些内容供大家参考。

### 总结
---
`bitsharesjs-ws` 针对 Bitshares 系统,定义了一套 API 调用的流程,并拓展了 WebSocket 的功能来维持一个稳定的连接通道,让外部系统能够专心于业务 API 的开发,但在其模块职责领域上,还应该有很多的可拓展空间,在此感谢此模块开发者为我们提供了一个好用的工具库,并节省了我们宝贵的时间。
👍 , , , , , ,