代碼在 github.com/tsingson/fa…javascript
爲某個開源項目增長 websocket 對接, 寫了這個示例html
代碼中 javascript 對 flatbuffers 的序列化/反序列化, 查了一天資料, 嗯哼, 最終完成了. 看代碼吧.........java
序列化 serialized / 反序列化 un-serialized , 通常是指對像轉成二進制序列( 或叫二進制數組), 以及逆向從二進制轉成對像的過程, 通常用在幾個地方python
flatbuffers 是 google 員工在開發遊戲過程當中, 仿 protobuffers 寫的一個高性能序列化/反序列化庫, 經過 IDL (接口描述語言) 定義, 經過 flatc 編譯爲多種語言的接口對象序列化/反序列化的強類型庫, 支持 c++ / c / java / python / rust / js / typescript / go / swift.........c++
fastbuffers 的介紹, 參見 於德志 的文章 halfrost.com/flatbuffers…, 幾篇文章寫得很細緻,精確,完整git
PS: 於德志 的技術專題介紹文章,語言簡練易懂, 配圖簡單明瞭,很是值得一讀github
提及來, 個人英文閱讀能力還能夠, 但不得不說, 訪問 於德志 的 halfrost.com/tag/protoco… 仍是很愉悅輕鬆. 謝謝了!golang
flatbuffers 的特色, 我的看法:web
因此, 在一次序列化, 而屢次反序列化的狀況下, 以及對反序列化要求速度很是快的狀況, 能夠考慮選擇 flatbuffers , 想一想 google 員工爲遊戲而開發 flatbuffers 這一典型場景吧typescript
在如下場景中, 我使用了( 或正在計劃使用) flatbuffers:
以前, flatbuffers 在序列化時代碼很讓人着急, 但2019年12月的一個改進, 讓 flatbuffers 序列化時代碼簡化很多
flatc --gen-object-api ./*.fbs
複製代碼
以上參數的添加, 讓 flatbuffers 序列化簡單以下:
// --------------- 這是 fbs 文件中的 IDL
table LoginRequest{
msgID:int=1;
username:string;
password:string;
}
// -------------- 這是 flatc 編譯後的 go 代碼
type LoginRequestT struct {
MsgID int32
Username string
Password string
}
func LoginRequestPack(builder *flatbuffers.Builder, t *LoginRequestT) flatbuffers.UOffsetT {
if t == nil {
return 0
}
usernameOffset := builder.CreateString(t.Username)
passwordOffset := builder.CreateString(t.Password)
LoginRequestStart(builder)
LoginRequestAddMsgID(builder, t.MsgID)
LoginRequestAddUsername(builder, usernameOffset)
LoginRequestAddPassword(builder, passwordOffset)
return LoginRequestEnd(builder)
}
// ----------- 這是我作的簡單封裝
func (a *LoginRequestT) Byte() []byte {
b := flatbuffers.NewBuilder(0)
b.Finish(LoginRequestPack(b, a))
return b.FinishedBytes()
}
//---------------- 這裏是序列化
l := &LoginRequestT{
MsgID: 1,
Username: "1",
Password: "1",
}
b := l.Byte() // ------------- 變量 b 是序列化後的二進制數組
複製代碼
示例代碼使用瞭如下開源庫
xone.fbs 示例來自 www.cnblogs.com/sevenstar/p…, 感謝!!
namespace xone.genflat;
table LoginRequest{
msgID:int=1;
username:string;
password:string;
}
table LoginResponse{
msgID:int=2;
uid:string;
}
//root_type非必須。
//root_type LoginRequest;
//root_type LoginRespons
複製代碼
生成 javascript
flatc -s --gen-mutable ./*.fbs
複製代碼
生成 golang
flatc --go --gen-object-api --gen-all --gen-compare --raw-binary ./*.fbs
複製代碼
./cmd/wsserver/main.go ----- websocket server
./cmd/wsclient/main.go ----- websocket client
./ws/... ------------------- websocket go code for websocket handler and websocket client
./jsclient/ws.js ---------- javascript client code , please check-out package.json for depends
複製代碼
請注意代碼註釋中的--------- 特別注意這一行
// ------------ ./jsclient/index.js
const flatbuffers = require('./flatbuffers').flatbuffers;
const xone = require('./xone_generated').xone; //Generated by `flatc`.
//-------------------------------------------
// serialized
//-------------------------------------------
let b = new flatbuffers.Builder(1);
let username = b.createString("zlssssssssssssh");
let password = b.createString("xxxxxxxxxxxxxxxxxxx");
xone.genflat.LoginRequest.startLoginRequest(b);
xone.genflat.LoginRequest.addUsername(b, username);
xone.genflat.LoginRequest.addPassword(b, password);
xone.genflat.LoginRequest.addMsgID(b, 5);
let req = xone.genflat.LoginRequest.endLoginRequest(b);
b.finish(req); //建立結束時記得調用這個finish方法。
let uint8Array = b.asUint8Array(); // ------------- 特別注意這一行
console.log(uint8Array);
// console.log(b.dataBuffer() );
//-------------------------------------------
// un-serialized
//-------------------------------------------
let bb = new flatbuffers.ByteBuffer(uint8Array); //-------------- 特別注意這一行
let lgg = xone.genflat.LoginRequest.getRootAsLoginRequest(bb);
console.log("username: ", lgg.username());
console.log("password", lgg.password());
console.log("msgID: ", lgg.msgID());
複製代碼
// ------ ./apis/genflat/model.go
func (a *LoginRequestT) Byte() []byte {
b := flatbuffers.NewBuilder(0)
b.Finish(LoginRequestPack(b, a))
return b.FinishedBytes()
}
func ByteLoginRequestT(b []byte) *LoginRequestT {
return GetRootAsLoginRequest(b, 0).UnPack()
}
// ------- ./apis/genflat/model_test.go
func TestLoginRequestT_Byte(t *testing.T) {
as := assert.New(t)
// serialized
l := &LoginRequestT{
MsgID: 1,
Username: "1",
Password: "1",
}
b := l.Byte()
// un-serialized
c := ByteLoginRequestT(b)
if l.MsgID > 0 {
fmt.Println(" id > ", c.MsgID, " u > ", c.Username, " pw > ", c.Password)
}
as.Equal(l.Password, c.Password)
}
複製代碼
ws.onmessage = (event) => {
//-------------------------------------------------------------------
// read from websocket and un-serialized via flatbuffers
//--------------------------------------------------------------------
let aa = str2ab(event.data);
let bb = new flatbuffers.ByteBuffer(aa);
let lgg = xone.genflat.LoginRequest.getRootAsLoginRequest(bb);
let pw = lgg.password();
if (typeof pw === 'string') {
console.log("----------------------------------------------");
console.log("username: ", lgg.username());
console.log("password", lgg.password());
console.log("msgID: ", lgg.msgID());
} else {
console.log("=================================");
console.log(event.data);
}
// console.log(`Roundtrip time: ${Date.now() }` , ab2str(d ));
setTimeout(function timeout() {
//-------------------------------------------------------------------
// serialized via flatbuffers and send to websocket
//--------------------------------------------------------------------
let b = new flatbuffers.Builder(1);
let username = b.createString("zlssssssssssssh");
let password = b.createString("xxxxxxxxxxxxxxxxxxx");
xone.genflat.LoginRequest.startLoginRequest(b);
xone.genflat.LoginRequest.addUsername(b, username);
xone.genflat.LoginRequest.addPassword(b, password);
xone.genflat.LoginRequest.addMsgID(b, 5);
let req = xone.genflat.LoginRequest.endLoginRequest(b);
b.finish(req); //建立結束時記得調用這個finish方法。
let uint8Array = b.asUint8Array();
ws.send(uint8Array);
}, 500);
};
function str2ab(str) {
let array = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) {
array[i] = str.charCodeAt(i);
}
return array
}
複製代碼
MIT
code by tsingson