基於 protobuf 協議實現高性能的 IM 客戶端

這裏記錄了使用 protobuf 協議與服務端數據交互的相關內容和知識。

涉及到計算機基礎知識,例如字節、buffer 緩衝、大小端等。html


字節 / Byte

1 字節表明了 8 位(bit)二進制,1 位就是 0 或 1,也是計算機最小單位。前端


Uint 與 Int

Int 是帶正負號的整數,Uint 是從 0 開始計的整數。git

Uintx 是指用多少表示的整數,例如 Uint8 就是用 8位(即一個字節) 表示的整數,二進制範圍是 00000000 ~ 11111111,對應的十進制就是 0 ~ 255github

可是人類的數學裏面負數,因此 Int8 就描述了包含負數在內的整數範圍,即十進制的 -128 ~ 127web

更多描述以下所示shell

Uint8 -- (0 to 2^8 - 1)
Int8 -- (-2^7 to +2^7 - 1)

Uint16 -- (0 to 2^16 - 1)
Int16 -- (-2^15 to +2^15 - 1)

Uint32 -- (0 to 2^32 - 1)
Int32 -- (-2^31 to +2^31)

Uint64 -- (0 to 2^64 - 1)
Int64 -- (-2^63 to +2^63 - 1)

ArrayBuffer

ArrayBuffer 對象用來表示通用的、固定長度的原始二進制數據緩衝區。參考MDNwebsocket

// 如下爲建立 12 個字節的 buffer 的例子

const buffer = new ArrayBuffer(12);

上面的操做表明向操做系統申請了 12 字節的二進制緩衝,大概以下分佈socket

| 00000000 | 00000000 | 00000000 | 00000000 | ...(還有8字節)ui

ArrayBuffer 對象並不能直接被操做,須要經過 TypedArray 對象實例或者 DataView 實例做爲橋樑來操做。操作系統

// Uint8Array 的單位爲一字節與 ArrayBuffer 的基本單位吻合
const uint8 = new Uint8Array(buffer);

console.log(uint0) // 輸出 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

uint8[0] = 12; // 此時 buffer 變成 [12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

TypedArray 對象一覽 MDN

類型 大小(字節單位) 描述 Web IDL type
Int8Array 1 8位二進制帶符號整數 -2^7~(2^7) - 1 byte
Uint8Array 1 8位無符號整數 0~(2^8) - 1 octet
Int16Array 2 16位二進制帶符號整數 -2^15~(2^15)-1 short
Uint16Array 2 16位無符號整數 0~(2^16) - 1 unsigned short
Int32Array 4 32位二進制帶符號整數 -2^31~(2^31)-1 long
Uint32Array 4 32位無符號整數 0~(2^32) - 1 unsigned int
Float32Array 4 32位IEEE浮點數 unrestricted float
Float64Array 8 64位IEEE浮點數 unrestricted double

除了 TypedArray,還能夠經過 DataView 來作更細緻的操做

例如咱們須要在特定字節段內寫入對應的數據

| DataLen 4 個字節 | SessionID 8 個字節 | ...

const view = new DataView(buffer);

const DataLen = 100; // buffer 數據總長度
const SessionID = 123456789; // SessionID

// 最後的參數爲大小端排序
view.setUint32(0, DataLen, true);
view.setBigUint64(4, BigInt(SessionID), true);

讀取內容

const view = new DataView(buffer);

// 讀取小端字符順序
const DataLen = view.getUint32(0, true);
const SessionID = view.getBigUint64(4, true);
什麼是大小端
  1. Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
  2. Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。

更多詳情參考維基百科的字節順序


JS 的大數處理

JS 並不能處理 Int64 精度的數,因此在 stage 3 引入了 BigInt API,解決大數精度問題,ChromeFirefox 已經支持,可是 Safari 並不支持,須要用另外的辦法處理。

兼容方式參考 這裏


Protobuf 應用

Google Protocol Buffers 是一種輕便高效的結構化數據存儲格式,能夠用於結構化數據串行化,或者說序列化。

開發時通信雙方或者多方終端都遵循 proto 協議。

而後看看前端如何使用 protobuf

Google 官方的庫對 JS 支持不是太友好,這裏咱們使用 protobuf.js

建立一個 sdk.proto 文件

syntax = "proto3";

package yourPackage;

message LoginReq {
  string UserName = 1;
  string Password = 2;
}
yarn add protobufjs -D

# 使用 protobufjs 提供的 Command line
pbjs ./sdk.proto -t static-module > ./sdk.js

# 生成 ts 聲明文件
pbts -o ./sdk.d.ts ./sdk.js

生成好文件便可使用

import SDK from './sdk';

const { LoginReq } = SDK.yourPackage;

const payload = {
  UserName: 'alex',
  Password: '123'
}

const message = LoginReq.create(payload); // or use .fromObject if conversion is necessary

// encode 信息
const protoBuffer = LoginReq.encode(message).finish();

// 把 protobuf buffer 寫入到上面的 SessionID buffer 信息中

const uint8 = new Uint8Array(buffer);
uint8.set(protoBuffer, offset)

// 使用 websocket 發送 arrayBuffer 數據
const socket = new WebSocket(host)
socket.onopen = () => {
  socket.send(protoBuffer)
}
socket.onmessage = () => {
  // decode operator
}

總結

這裏只是簡單的記錄過程,若是想要更多細節的信息,能夠參考 little-chat 的源碼

相關文章
相關標籤/搜索