前言
之前寫WebSocket
都是基於文字傳輸的,後來準備升級專案,於是打算嘗試一下arraybuffer
傳輸方式,由於是第一次使用javascript
處理字串轉arraybuffer
,不過真的是一把辛酸淚啊,特此記錄。
專案背景
還是之前寫的(基於Tio通訊框架的SpringBootLayIM專案)[https://github.com/fanpan26/SpringBootLayIM].
開發過程還原
- 1.要將客戶端的傳輸方式改為
arraybuffer
。
var ws = new WebSocket(tool.options.server + '?access_token=' + token);
ws.binaryType = 'arraybuffer';
- 2.我們就要用到框架中的
IWsMsgHandler.onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext)
方法接收客戶端的訊息
/**
* 位元組傳輸
* */
public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
System.out.println("接收到的訊息為:"+new String(bytes));
return null;
}
- 3.在
ws.send
之前要先將傳送的內容轉換。
//
var buff = new TextEncoder().encode(str);
很好,這三個步驟做完之後,我們試一下。可以看到,列印結果正常
接收到的訊息為:{"username":"雍正王","avatar":"/static/images/demo/huangshang.jpg","id":203328,"type":"friend","content":"22"}
到此為止呢,還是沒有任何問題的。按照我之前的思路就是將訊息反序列化成物件例項,然後進行相應的邏輯操作。不過呢,後來突發奇想,本來我傳遞過來的訊息就是想原封不動的傳送給對方,也就是說程式不需要執行反序列化的過程。那可不可以這樣,如下圖:
沒錯就是這樣子,然後我列印了一下new TextEncoder().encode(str);
返回的內容如下:
可以看到,它是一個 Uint8Array
,於是我就看看能否重新構造一個陣列,用虛擬碼實現就是醬紫:
var newBuff = new ArrayBuffer(4+1+bodyArray.buffer.byteLength)
再查資料發現ArrayBuffer
得依賴DataView
來進行賦值操作,那不就簡單啦。先把接收人ID(UInt32)和 訊息型別 (UInt8) 寫入,如下:
var view = new DataView(dataBuff.buffer);
view.setInt32(0, targetId);
view.setInt8(4, 1);
那剩下的body怎麼寫進去呢?按照我想的,應該是有個 setContent(arr,offset:number)
方法,可是查了一下,沒有。於是乎再查文件,原來Unit8Array
有個遍歷的方法,那就用這個方法試試吧。
//從索引第五個開始寫
dataBuff.forEach(function (value, index) {
view.setInt8(5+index,value);
});
為了驗證我這個思路(嗚嗚,這個思路還是研究了好久,浪費了很多時間。。。其實中間由於不熟悉DataView和ArrayBuffer導致做了很多嘗試的工作,而且都失敗了,要麼就是報錯,要麼就是覆蓋了訊息體)的正確性,我們將後臺程式碼改一下:
byte[] targetIdBytes = Arrays.copyOf(bytes,4);
byte[] contents = Arrays.copyOfRange(bytes,5,bytes.length);
System.out.println("訊息體:"+new String(contents));
int targetId = ConvertUtil.byteArrayToInt(targetIdBytes);
System.out.println("接收人:"+targetId);
System.out.println("訊息型別"+bytes[4]);
執行正常。
可是,總覺得在遍歷複製一遍有點繁瑣。那既然setInt32,setUInt8
這些方法是覆蓋陣列裡的值的,那我可不可以這樣寫呢?使用佔位符的方式,也就是說,在不影響訊息體的情況下,在轉化成byte
陣列之後,前五位是佔位資料,後邊才是正確的訊息,那麼我重寫前五位的內容也不會受到什麼影響了,而且不用遍歷賦值了。說幹就幹,改成程式碼如下:
var str = placeholder + JSON.stringify(d);
var buff = new TextEncoder().encode(str);
return buff;
問題就在於這個placeholder
的值是什麼呢?其實我們使用小寫字母代替就可以了。比如 'abcde',看一下轉換的結果:
不出我所料,那這樣的話,就沒問題啦,直接覆蓋前五位就可以了。客戶端完整程式碼如下:
//根據layim提供的data資料,進行解析
var mine = data.mine,
to = data.to,
id = mine.id,
group = to.type === 'group';
if (group) {
id = to.id;
}
//構造訊息
var msg = {
username: mine.username
, avatar: mine.avatar
, id: id
, type: to.type
, content: mine.content
}, targetId = to.id
var dataBuff = this.encode(msg);
var view1 = new DataView(dataBuff.buffer);
view1.setInt32(0, targetId);
view1.setInt8(4, group ? msgType.chatGroup : msgType.chatFriend);
return view1.buffer;
執行一下,結果沒問題,大功告成!
總結
從問題的發出到解決,雖然從部落格上來看沒有什麼難度,但是自己在做的時候,搜了很多資料,嘗試了很多次也沒有結果,原因是自己自動腦補了一些物件的操作API,基礎知識還是很重要的啊,滾回去學習。雖然最終的思路不一定是最好的,或者還有一些其他的問題,但是多嘗試一下還是有很多意外的收穫。那麼問題來了。反序列化和陣列的拷貝哪個效率更高一些呢?
//這個好還是
byte[] targetIdBytes = Arrays.copyOf(bytes,4);
byte[] contents = Arrays.copyOfRange(bytes,5,bytes.length);
//這個好呢?
Json.toBean(new String(contents))