Shiv ENOW large front end
Company official website: CVTE(Guangzhou Shiyuan Stock)
Team: ENOW team of CVTE software Platform Center for Future Education
In this paper, the author
preface
At work, I was in charge of maintaining the company’s Websocket-SDK library. Websocket sends and receives data through ArrayBuffer, but front-end engineers rarely have the opportunity to deal with binary codec, so I had no idea about ArrayBuffer and binary codec scheme at the beginning. I’m going to share my practice with you today.
Through this article, you can learn:
- What are TypedArray/ArrayBuffer classes related to binary codec?
- Why should you use binary encoding?
- Introduction to JavaScript binary codec scheme
- How to make Websocket-SDK compatible with wechat applets
ArrayBuffer
An ArrayBuffer represents a chunk of memory that stores binary data. It cannot be read or written directly, but can only be read or written through views (TypedArray and DataView views) that interpret binary data in a specified format.
TypedArray
The TypedArray view describes an underlying binary data buffer, which can be understood as an interface for JavaScript to manipulate binary data. In fact, there is no global property named TypedArray, and there is no constructor named TypedArray. Instead, there are many different global properties whose values are typed array constructors for a particular element type. The TypedArray view supports nine data types.
The data type | bytes | meaning | The corresponding C language type |
---|---|---|---|
Int8 | 1 | An 8-bit signed integer | signed char |
Uint8 | 1 | An 8-bit unsigned integer | unsigned char |
Uint8C | 1 | 8-bit unsigned integer (automatic filter overflow) | unsigned char |
Int16 | 2 | 16 – bit signed integer | short |
Uint16 | 2 | 16 – bit unsigned integer | unsigned short |
Int32 | 4 | 32 – bit signed integer | int |
Uint32 | 4 | A 32-bit unsigned integer | unsigned int |
Float32 | 4 | 32-bit floating point number | float |
Float64 | 8 | 64-bit floating point number | double |
Why should you use binary encoding
To make the data smaller. JSON is characterized by key-value pairs that store data, but if we agree on which fields the data object contains, then the Key Value is not needed. The resulting packets will no longer contain Key values, reducing their size.
For example, we have a data class called Person that we need to encode. Assume that under ideal conditions, we already have two utility classes: WriteBlock and ReadBlock, which write and read binary data, respectively. (Detailed code implementation is attached at the end of the paper)
// Help us write binary data
class WriteBlock {
// omit details
}
// Implement binary data reading
class ReadBlock {
// omit details
}
class Person {
name: string; / / name
age: number; / / age
gender: string; / / gender
constructor(info) {
this.name = info.name;
this.age = info.age;
this.gender = info.gender;
}
Encode: () = > ArrayBuffer = () = > {
const wb = new WriteBlock();
// Write data in sequence
wb.WriteStringField(1.this.name);
wb.WriteIntField(2.this.age);
wb.WriteStringField(3.this.gender);
return wb.getArrayBuffer();
};
Decode: (
buffer: ArrayBuffer
) = > {
name: string;
age: number;
gender: string;
} = (buffer) = > {
const rb = new ReadBlock(buffer);
// Parse data in sequence according to the agreed protocol
return {
name: rb.ReadStringField(1),
age: rb.ReadIntField(2),
gender: rb.ReadStringField(3),}}; }Copy the code
Introduction to JavaScript binary codec scheme
JavaScripts use UCS-2 encoding, which is similar to UTF-16, and each character is two bytes. So you can encode JavaScript characters as an ArrayBuffer as follows:
function str2ab(str) {
var buf = new ArrayBuffer(str.length*2); // Each character takes two bytes
var bufView = new Uint16Array(buf);
for (var i=0, strLen=str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
Copy the code
Decoding method:
1 / / method
function ab2str(buf) {
return String.fromCharCode.apply(null.new Uint16Array(buf));
}
/ / method 2:
function ab2str(buf) {
var uint8 = new Uint8Array(buffer)
var decoder = new TextDecoder('utf8')
var str = decoder.decode(uint8)
return str
}
Copy the code
How to make Websocket-SDK compatible with wechat applets
In the wechat applet environment, the websocket interface is different from that in the browser environment. The difference is reflected in the different WEBSocket-related API.
Initialize the | websocket API Style | |
---|---|---|
The browser | new WebSocket(url) | websocket.onopen = () => {} |
Wechat applets | wx.connectSocket(object) | websocket.onOpen(function callback) |
With the adapter pattern, we can smooth out differences at the adapter layer and provide a consistent access interface to the upper layer.
First we respectively implement the browser environment and small program environment Websocket control class
// Omit the interface declaration here
export class BrowserWebSocket implements GeneralWebSocket {
private webSocket: WebSocket;
constructor(url: string) {
this.webSocket = new WebSocket(url)
this.webSocket.binaryType = 'arraybuffer'
}
onOpen (cb) {
this.webSocket.onopen = cb
}
onClose (cb) {
this.webSocket.onclose = cb
}
onMessage (cb) {
this.webSocket.onmessage = cb
}
onError (cb) {
this.webSocket.onerror = cb
}
send (data) {
this.webSocket.send(data)
}
getReadyState () {
return this.webSocket.readyState
}
close (data) {
this.webSocket.close()
}
}
export class WeChatWebSocket implements MiniProgramWebSocket {
private webSocket: WechatMiniprogram.SocketTask;
constructor(url) {
this.webSocket = wx.connectSocket({
url,
success: () = > {
console.log('Websocket connection successful')},fail: () = > {
console.log('Websocket connection failed')
},
})
}
close (data?) {
this.webSocket.close(data)
}
onOpen (cb) {
this.webSocket.onOpen(cb)
}
onClose (cb) {
this.webSocket.onClose(cb)
}
onError (cb) {
this.webSocket.onError(cb)
}
onMessage (cb: (evt) = > any) {
this.webSocket.onMessage(cb)
}
send (data: any) {
this.webSocket.send({
data,
success: (info) = > {
console.log('send msg success', info)
},
fail: (info) = > {
console.log('send msg fail:', info)
},
complete: () = > {
console.log('send complete')
},
})
}
getReadyState () {
return (this.webSocket as any).readyState
}
}
Copy the code
Smooth out webSocket differences in the WebsocketAdaptor class:
class WebSocketAdaptor {
private webSocket: WeChatWebSocket | BrowserWebSocket
constructor(env, url) {
if (env === 'wechat') {
this.webSocket = new WeChatWebSocket(url)
} else if (env === 'browser') {
this.webSocket = new BrowserWebSocket(url)
}
}
onOpen (cb) {
this.webSocket.onOpen(cb)
}
onMessage (cb) {
this.webSocket.onMessage(cb)
}
onError (cb) {
this.webSocket.onError(cb)
}
onClose (cb) {
this.webSocket.onClose(cb)
}
send (data) {
this.webSocket.send(data)
}
getReadyState () {
return this.webSocket.getReadyState()
}
close (data?) {
this.webSocket.close(data)
}
}
Copy the code
conclusion
- JavaScript can manipulate binary data through TypedArray views
- Using binary encoding can significantly reduce the size of the data being transmitted
- Through the adapter mode, it is convenient to realize the COMPATIBILITY of SDK to websocket in different environments
Attached is a complete implementation of the binary read and write helper classes
class ReadBlock {
dv: DataView;
pos: number;
size: number;
constructor(buffer: ArrayBuffer) {
this.dv = new DataView(buffer)
this.pos = 0
this.size = buffer.byteLength
}
GetHead: (index: number, type: number) = > boolean = (index, type) = > {
if (this.pos >= this.size) {
return false
}
const head = this.dv.getUint8(this.pos)
if ((head >> 3) != index || (head & 0x07) != type) {
return false
}
this.pos++
return true
}
GetIntValue: () = > number = () = > {
let ret = 0
let val = this.dv.getUint8(this.pos++)
let step = 1
while (val > 127) {
val &= 0x7F
ret += (val * step)
step *= 128
val = this.dv.getUint8(this.pos++)
}
ret += (val * step)
return ret
}
ReadIntField: (index: number) = > number = (index) = > {
if (!this.GetHead(index, 0)) {
return 0
}
const val = this.GetIntValue()
return val
}
ReadStringField: (index: number) = > string = (index) = > {
let str = ' '
if (!this.GetHead(index, 2)) {
return str
}
const len = this.GetIntValue()
for (let i = 0; i < len; i++) {
str += String.fromCharCode(this.dv.getUint8(this.pos++))
}
return str
}
ReadArrayField: (index: number) = > ArrayBuffer = (index) = > {
const arr = []
if (!this.GetHead(index, 2)) {
return new Uint8Array(arr).buffer
}
const len = this.GetIntValue()
for (let i = 0; i < len; i++) {
arr.push(this.dv.getUint8(this.pos++))
}
return new Uint8Array(arr).buffer
}
}
class WriteBlock {
arr: Array<number>;
constructor() {
this.arr = []
}
SetHead: (index: number, type: number) = > void = (index, type) = > {
const val = (index << 3) | type
this.arr.push(val & 0xFF)}SetIntValue: (val: number) = > void = (val) = > {
while (val > 127) {
this.arr.push(val % 128 + 128)
val = Math.floor(val / 128)}this.arr.push(val)
}
WriteIntField: (index: number, val: number) = > void = (index, val) = > {
this.SetHead(index, 0)
this.SetIntValue(val)
}
WriteBooleanField: (index: number, val: boolean) = > void = (index, val) = > {
this.SetHead(index, 0)
this.SetIntValue(+val)
}
WriteStringField: (index: number, str: string) = > void = (index, str) = > {
this.SetHead(index, 2)
this.SetIntValue(str.length)
for (let i = 0; i < str.length; ++i) {
this.arr.push(str.charCodeAt(i))
}
}
WriteArrayBufferField: (index: number, buf: ArrayBuffer) = > void = (index, buf) = > {
this.SetHead(index, 2)
this.SetIntValue(buf.byteLength)
const ui8 = new Uint8Array(buf)
for (let i = 0; i < ui8.length; i++) {
this.arr.push(ui8[i])
}
}
getArrayBuffer: () = > ArrayBuffer = () = > {
return new Uint8Array(this.arr).buffer
}
}
Copy the code