折騰一下WebSocket的ArrayBuffer傳輸方式


前言

  之前寫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))


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM