JavaScript進行WebSocket字節流通訊示例


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對象等。


免責聲明!

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



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