1. 程式人生 > >JavaScript進行WebSocket字節流通訊示例

JavaScript進行WebSocket字節流通訊示例

ons 其中 示例 取消 介紹 大於 utf-8 connect 約定

websocket進行通訊時,可以選擇采用字符串或者字節流的傳輸模式。但在發送與接收時,需要考慮數據的分包,即分成一個個請求與響應消息。無論是采用哪種傳輸模式,都不免要遇到這個問題。

采用字符串傳輸時,接收端可以將每次接收到的字符串拼接到一起,再檢測是否出現了某一特定子串,比如連續兩個換行,即可將一個長的字符串分隔成一個個的請求或響應消息。這種處理方式比較簡單且有效。但這裏,介紹另一種模式,即傳輸字節流。

首先考慮下分包的問題,一般分為消息頭與消息體。出於簡單目的,消息頭裏只存放一個消息體的長度,消息體為字節數組。

確定了數據包格式接下來可以寫實現代碼了,首先是連接:

var socket;
var
uri = "ws://" + window.location.host + "/push"; // 示例地址 function Connect(uri) { socket = new WebSocket(uri); socket.binaryType = "arraybuffer"; socket.onopen = function (e) { console.log("已連接至服務器"); }; socket.onclose = function (e) { console.log("鏈接已關閉"); }; socket
.onmessage = function (e) { doReceive(e.data); }; socket.onerror = function (e) { console.log("出現錯誤"); }; }

這裏將socket變量定義為公共的,因為後續的發送方法會用到這個變量。默認JavaScript裏的WebSocket傳輸是采用字符串模式的,采用UTF-8編碼,通過將binaryType屬性設置為arraybuffer來使用字節流傳輸。

當發生onmessage事件時代表接收到數據,保存在參數e.data裏。每一次接收都可能接收到一個完整的消息或部分消息,我們通過一個doReceive方法來進行消息數據包的拆分。代碼如下:

var receive = [];
var length = 0;
function doReceive(buffer) {
    receive = receive.concat(Array.from(new Uint8Array(buffer)));
    if (receive.length < 4) {
        return;
    }
    length = new DataView(new Uint8Array(receive).buffer).getUint32(0);
    if (receive.length < length + 4) {
        return;
    }
    var bytes = receive.slice(4, length + 4);
    doSomething(bytes);

    receive = receive.slice(length + 4);
};

其中receive作為接收緩沖區,每次接收到數據時先將其存到該緩沖區裏。之後檢查其長度是否大於等於4字節,即上文定義的消息頭長度。若滿足條件,則將其作為Uint32值讀取,代表消息體長度。之後檢查緩沖區是否大於消息頭加消息體長度,若滿足則讀取消息體,之後得到的bytes字節數組即為完整的消息體。最後,用剩余字節重置緩沖區以備下一次讀取。

其中buffer參數為ArrayBuffer類型,其代表原始的字節數組,本身是無意義的。JavaScript通過一個個視圖來解釋這些字節。Uint8Array即是其中一種視圖,它將ArrayBuffer中的字節作為8位無符號整數來對待,正好一字節對應一個uint8整數。類似的還有Uint16Array,它將ArrayBuffer中的字節作為16位無符號整數來對待,則每兩位對應一個uint16整數。

而DataView是JavaScript API提供的一種視圖,他將ArrayBuffer中的數據作為網絡流對待,它采用大端編碼,可以用它來讀取或寫入數據。這裏我們用它讀取了一個Uint32的值。

接下來是發送方法,代碼如下:

function doSend(bytes) {
    var buffer = new ArrayBuffer(bytes.length + 4);
    var view = new DataView(buffer);
    view.setUint32(0, bytes.length);
    for (var i = 0; i < bytes.length; i++) {
        view.setUint8(i + 4, bytes[i]);
    }
    socket.send(view);
};

其中參數bytes是已經編碼過的字節數組,這裏通過DataView視圖將其存儲到ArrayBuffer對象裏,以備發送。按照上文約定,需先設置Uint32型的消息體長度,再設置消息體。

最後,本文按約定的消息格式來進行請求與響應消息的傳輸,消息體為不定長度的字節序列。其本身是無意義的。我們可以通過API提供的Uint16Array、Uint32Array等視圖將其作為整數值序列,也可以自我實現其內容的解釋方式。

比如參考這裏將其作為采用UTF-8編碼的字符串。之後可再將字符串打印或反序列化為JSON對象等。

JavaScript進行WebSocket字節流通訊示例