quick-struct让JavaScript明白二进制

1,838 阅读4分钟

quick-struct是我花了一周时间写的二进制加码与编码工具库,目的是想用C语言风格编写数据结构声明,并且最傻瓜化的解码二进制数据,或者编码成二进制数据包。

之前在NPM上搜索了很久,也只找到了一个叫做struct的工具库,声明数据结构非常的繁琐,而且不支持动态长度数组或者字符串的解析!尽管最后花了很大力气动过动态解析数据包长度实现了动态长度的支持,但是代码极其繁琐并且执行速度不高。考虑到这个库的作者是在很多年前开发的,当时对于ArrayBuffer这类新的标准还没有支持,能够写出来解码二进制数据包的工具库已经很了不起了,但是现在新的标准已经大范围的普及了,那么是否可以使用新标准来重写一下呢?说干就干,那么首先就要定下一些目标。

实现目标

  1. 使用类似C语言的struct语法对数据结构进行描述,语法要足够的简单
  2. 能够设定大小端数据读取
  3. 能够将二进制数据包直接解码转换成JavaScript对象
  4. 性能要足够快,毕竟处理二进制的目的就是一个字,快
  5. 支持固定长度的数组声明
  6. 支持固定长度的字符串声明
  7. 字符串类型要能够自动的转化成JavaScript的String类型
  8. 最好支持可变长度的数组声明
  9. 最好还能支持可变长度的字符串声明
  10. 支持多重结构体解析
  11. 支持多重结构题数组解析

惊鸿一瞥

quick-struct最新版已经成功实现了以上除了最后两个目标的其他所有功能,那么就来快速看一下演示代码吧。

// ES Module or Typescript 方式加载
import {qs} from 'quick-struct'

// CommonJS 方式加载
const {qs} = require('quick-struct')

// 声明数据结构
const struct = qs`
    struct {
        u32 id;
         u8 gender;
        u16 name_size;
        u16 address_size;
       char phone[16];
         u8 province;
         u8 city;
       char name[$name_size];
      uchar address[$address_size];
    }
`;

const buffer = ...
const person = struct.decode(buffer).toJson();

上面就是一个个人信息的数据结构声明,这个例子展示了基础数据类型声明,固定长度字符串类型声明和可变长度字符串类型声明。目前在Node.js 16环境中测试通过,理论上在现代浏览器上也是能够正常使用的。

应用技术

模版字符串函数

模版字符串想必是现在大家用的比较多的新标准特性之一了,有了这个之后我们可以写出简洁明了的可变的字符串,然而模版字符串不仅仅如此,它其实还有一个更方便的功能,就是可以作为模版字符串处理函数的参数,使用起来非常的方便,形式如下;

// 模版字符串处理函数
function qs(template_string) {
    ...
}

// 与此函数 qs(`hello ${name}`) 使用效果一样
qs`hello ${name}` 

虽然就是去掉了括号的使用,但是如此一来就可以非常方便的使用字符串模版作为参数输入了,也为我设计的第一个目标提供了一个非常方便的工具,我不需要使用什么构造函数,只需要输入字符串就能够实现数据结构的定义。

因为模版字符串中可以使用任意的字符,这对于编写声明语句就非常的方便了,如果没使用模版字符串的话,那么声明可能就要如下了

const structWithoutTemplateString = [
    "struct {",
    "u8 a;",
    "}"
].join('')

const structWithTemplateString = `
    struct {
       u8 a;
    }
`

虽然最后输出的字符串都是一样的,但是明显使用字符串模版的可读性更高,对于使用者来说更方便易懂。

DataView

DataView是新的JavaScript的API之一,它的作用是按照指定的类型读取或者写入二进制字节数据。

其实不使用DataView也能够使用TpyedArray来读取或者写入字节数据,而且经过我的测试比较,TypedArray具有比DataView更快的处理速度,唯一不足的地方就是TypedArray无法设置大小端!倒不是说TypedArray一定不能处理大小端的格式问题,只是自己手动编写的执行效率可能没有原生函数的更有效,权衡之下最后决定使用DataView来处理二进制字节的读取和写入。

为什么大小端问题那么重要,原因就是如果发送端和接受端的字节大小端不一致,那么将会出现读取数据错误的情况,因此能够设置字节大小端是一项必须的功能,毕竟我也不知道接收的数据究竟来自何种架构的CPU,又或者是何种类型的操作系统。