中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

從Chrome源碼看WebSocket

2018-07-20    來源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢上線!快速搭建集群,上萬Linux鏡像隨意使用

WebSocket是為了解決雙向通信的問題,因為一方面HTTP的設(shè)計是單向的,只能是一邊發(fā)另一邊收。而另一方面,HTTP等都是建立在TCP連接之上的,HTTP請求完就會把TCP給關(guān)了,而TCP連接本身就是一個長連接嗎,只要連接雙方不斷關(guān)閉連接它就會一直連接態(tài),所以有必要再搞一個WebSocket的東西嗎?

我們可以考慮一下,如果不搞WebSocket怎么實現(xiàn)長連接:

(1)HTTP有一個keep-alive的字段,這個字段的作用是復(fù)用TCP連接,可以讓一個TCP連接用來發(fā)多個http請求,重復(fù)利用,避免新的TCP連接又得三次握手。這個keep-alive的時間服務(wù)器如 Apache 的時間是5s,而 nginx 默認(rèn)是75s,超過這個時間服務(wù)器就會主動把TCP連接關(guān)閉了,因為不關(guān)閉的話會有大量的TCP連接占用系統(tǒng)資源。所以這個keep-alive也不是為了長連接設(shè)計的,只是為了提高h(yuǎn)ttp請求的效率,而http請求上面已經(jīng)提到它是面向單向的,要么是服務(wù)端下發(fā)數(shù)據(jù),要么是客戶端上傳數(shù)據(jù)。

(2)使用HTTP的輪詢,這也是一種很常用的方法,沒有websocket之前,基本上網(wǎng)頁的聊天功能都是這么實現(xiàn)的,每隔幾秒就向服器發(fā)個請求拉取新消息。這個方法的問題就在于它也是需要不斷地建立TCP連接,同時HTTP頭部是很大的,效率低下。

(3)直接和服務(wù)器建立一個TCP連接,保持這個連接不中斷。這個至少在瀏覽器端是做不到的,因為沒有相關(guān)的API。所以就有了WebSocket直接和服務(wù)器建立一個TCP連接。

TCP連接是使用套接字建立的,如果你寫過Linux服務(wù)的話,就知道怎么用系統(tǒng)底層的API(C語言)建立一個TCP連接,它是使用的套接字socket,這個過程大概如下,服務(wù)端使用socket創(chuàng)建一個TCP監(jiān)聽:

// 先創(chuàng)建一個套接字,返回一個句柄,類似于setTimout返回的tId
// AF_INET是指使用IPv4地址,SOCK_STREAM表示建立TCP連接(相對于UDP)
int sockfd = socket(AF_INET, SOCK_STREAM, 0));
// 把這個套接字句柄綁定到一個地址,如localhost:9000
bind(sockfd, servaddr, sizeof(servaddr));
// 開始使用這個套接字監(jiān)聽,最大pending的連接數(shù)為100
listen(sockfd, 100);

客戶端也使用的套接字進(jìn)行連接:

// 客戶端也是創(chuàng)建一個套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0));
// 用這個套接字連接到一個serveraddr
connect(sockfd, servaddr, sizeof(servaddr));
// 向這個套接字發(fā)送數(shù)據(jù)
send(sockfd, sendline, strlen(sendline), 0);
// 關(guān)閉連接
close(sockfd);

也就是說TCP和UDP連接都是使用套接字創(chuàng)建的,所以WebSocket的名字就是這么來的,本質(zhì)上它就是一個套接字,并變成了一個標(biāo)準(zhǔn),瀏覽器器開放了API,讓網(wǎng)頁開發(fā)人員也能直接創(chuàng)建套接字和服務(wù)端進(jìn)行通信,并且這個套接字什么時候要關(guān)閉了由你們?nèi)Q定,而不像http一樣請求完了瀏覽器或者服務(wù)器就自動把TCP的套接字連接關(guān)了。

所以說WebSocket并不是一個什么神奇的東西,它就是一個套接字。同時,WebSocket得借助于現(xiàn)有的網(wǎng)絡(luò)基礎(chǔ),如果它再從頭搞一套建立連接的標(biāo)準(zhǔn)代價就會很大。在它之前能夠和服務(wù)連接的就只有http請求,所以它得借助于http請求來建立一個原生的socket連接,因此才有了協(xié)議轉(zhuǎn)換的那些東西。

瀏覽器建立一個WebSocket連接非常簡單,只需要幾行代碼:

// 創(chuàng)建一個套接字
const socket = new WebSocket('ws://192.168.123.20:9090');
// 連接成功
socket.onopen = function (event) {
    console.log('opened');
    // 發(fā)送數(shù)據(jù)
    socket.send('hello, this is from client');
};

因為瀏覽器已經(jīng)按照文檔實現(xiàn)好了,而要創(chuàng)建一個WebSocket的服務(wù)端應(yīng)該怎么寫呢?這里我們先拋開Chrome源碼,先研究服務(wù)端的實現(xiàn),然后再反過來看瀏覽器客戶端的實現(xiàn)。準(zhǔn)備用Node.js實現(xiàn)一個WebSocket的服務(wù)端,來研究整一個連接建立和接收發(fā)送數(shù)據(jù)的過程是怎么樣的。

WebSocket已經(jīng)在 RFC 6455 里面進(jìn)行了標(biāo)準(zhǔn)化,我們只要按照文檔的規(guī)定進(jìn)行實現(xiàn)就能和瀏覽器進(jìn)行對接,這個文檔的說明比較有趣,特別是第1部分,有興趣的讀者可以看看,并且我們發(fā)現(xiàn)WebSocket的實現(xiàn)非常簡單,讀者如果有時間的話可以先嘗試自己實現(xiàn)一個,然后再回過頭來,對比本文的實現(xiàn)。

1. 連接建立

使用Node.js創(chuàng)建一個hello, world的http服務(wù),如下代碼index.js所示:

let http = require("http");
const hostname = "192.168.123.20"; // 或者是localhost
const port = "9090";
 
// 創(chuàng)建一個http服務(wù)
let server = http.createServer((req, res) => {
    // 收到請求
    console.log("recv request");
    console.log(req.headers);
    // 進(jìn)行響應(yīng),發(fā)送數(shù)據(jù)
    // res.write('hello, world');
    // res.end();
});
 
// 開始監(jiān)聽
server.listen(port, hostname, () => {
    // 啟動成功
    console.log(`Server running at ${hostname}:${port}`);
});

注意到這里沒有任何的出錯和異常處理,被省略了,在實際的代碼里面為了提高程序的穩(wěn)健性需要有異常處理,特別是這種server類的服務(wù),不能讓一個請求就把整個server搞掛了。相關(guān)出錯處理可以參考Node.js的文檔。

保存文件,執(zhí)行node index.js啟動這個服務(wù)。

然后寫一個index.html,請求上面寫的服務(wù):

<!DOCType html>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
<script>
!function() {
    const socket = new WebSocket('ws://192.168.123.20:9090');
    socket.onopen = function (event) {
        console.log('opened');
        socket.send('hello, this is from client');
    };
}();
</script>
</body>
</html>

但是我們發(fā)現(xiàn),Node.js代碼里的請求響應(yīng)回調(diào)函數(shù)并不會執(zhí)行,查了文檔發(fā)現(xiàn)是因為Node.js有另外一個upgrade的事件:

// 協(xié)議升級
server.on("upgrade", (request, socket, head) => {
    console.log(request.headers);
});

因為WebSocket需要先協(xié)議升級,在upgrade里面就能收到升級的請求。把收到的請求頭打印出來,如下所示:

{ host: ‘192.168.123.20:9090’,

connection: ‘ Upgrade ‘,

pragma: ‘no-cache’,

‘cache-control’: ‘no-cache’,

upgrade: ‘websocket’,

origin: ‘http://127.0.0.1:8080’,

‘sec-websocket-version’: ’13’,

‘user-agent’: ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36’,

‘a(chǎn)ccept-encoding’: ‘gzip, deflate’,

‘a(chǎn)ccept-language’: ‘en,zh-CN;q=0.9,zh;q=0.8,zh-TW;q=0.7’,

sec-websocket-key ‘: ‘KR6cP3rhKGrnmIY2iu04Uw==’,

‘sec-websocket-extensions’: ‘permessage-deflate; client_max_window_bits’ }

這是我們建立連接收到的第一個請求,里面有兩個關(guān)鍵的字段,一個是connection: ‘Upgrade’表示它是一個升級協(xié)議請求,另外一個是sec-websocket-key,這是一個用來確認(rèn)對方身份的隨機(jī)的base64字符串,下面將會用到。

我們需要對這個請求進(jìn)行響應(yīng),按照文檔的說明,需要包含以下字段:

server.on("upgrade", (request, socket, head) => {
    let base64Value = '';
    // 第一行是響應(yīng)行(Response line),返回狀態(tài)碼101
    socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' +
        // http響應(yīng)頭部字段用\r\n隔開
        'Upgrade: WebSocket\r\n' +
        'Connection: Upgrade\r\n' +
        // 這是一個給瀏覽器確認(rèn)身份的字符串
        `Sec-WebSocket-Accept: ${base64Value}\r\n` +
        '\r\n');
});

響應(yīng)報文需要按照http規(guī)定的格式,第一行是響應(yīng)行,包含了http的版本號,狀態(tài)碼101,狀態(tài)碼的解釋。每個頭部字段用\r\n隔開,這里面最關(guān)鍵的一個是Sec-WebSocket-Accept,它需要計算一下返回瀏覽器。怎么計算呢?文檔是這么規(guī)定的:

GUID(Globally_Unique_Identifier) = ‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11’
Sec-WebSocket-Accept = base64(sha1(Sec-Websocket-key + GUID))

使用瀏覽器給我的sec-websocket-key值,拼上一個固定的字符串,這個字符串叫全局唯一標(biāo)志符,然后取它的sha1值,再進(jìn)行base64編碼,返回給瀏覽器。如果瀏覽器發(fā)現(xiàn)這個值不對的話,就會拋異常,拒絕下一步的連接操作:

因為它發(fā)現(xiàn)你是一個假的WebSocket服務(wù),起碼不是按照文檔實現(xiàn)的,所以不是同一個世界,沒有共同語言,下面的交流就沒有必要了。

為了計算這個值需要引入一個sha1庫,base64轉(zhuǎn)換可以使用Node.js的Buffer轉(zhuǎn)換,如下代碼所示:

let sha1 = require('sha1');
// 協(xié)議升級
server.on("upgrade", (request, socket, head) => {
    // 取出瀏覽器發(fā)送的key值
    let secKey = request.headers['sec-websocket-key'];
    // RFC 6455規(guī)定的全局標(biāo)志符(GUID)
    const UNIQUE_IDENTIFIER = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
    // 計算sha1和base64值
    let shaValue = sha1(secKey + UNIQUE_IDENTIFIER),
        base64Value = Buffer.from(shaValue, 'hex').toString('base64');
    socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' +
        'Upgrade: WebSocket\r\n' +
        'Connection: Upgrade\r\n' +
        `Sec-WebSocket-Accept: ${base64Value}\r\n` +
        '\r\n');
});

使用上面瀏覽器發(fā)送的key計算得到的accept值為:

RWMSYL3Zmo91ZR+r39JVM2+PxXc=

把這個值發(fā)給瀏覽器,Chrome就不會報剛剛那個檢驗出錯了,確認(rèn)過眼神,遇上對的人。這樣WebSocket連接就建立了,沒錯就是這么簡單。Chrome開發(fā)者工具Network面板里的websocket連接將會從pending狀態(tài)變成101狀態(tài),如果連接關(guān)閉了就會變成200狀態(tài)。

上面瀏覽器的代碼在建立連接完成之后還send了一個數(shù)據(jù)過來:

socket.send('hello, this is from client');

怎么讀取這個數(shù)據(jù)呢?

2. 接收數(shù)據(jù)

數(shù)據(jù)的傳送,文檔規(guī)定了WebSocket數(shù)據(jù)幀格式,長這個樣子:

不要被這個嚇到,一個個拆解來看的話,還是挺簡單的?梢苑殖蓛蓚部分,幀頭字段和有效內(nèi)容或者叫有效負(fù)載(Payload Data),幀頭字段主要的作用是為了解釋這個幀的,如第1位(bit) FIN 如果置為1就表示它是一個結(jié)束幀,如果數(shù)據(jù)比較長就會被拆成幾個幀發(fā)送,F(xiàn)IN為1表示它是當(dāng)前數(shù)據(jù)流的最后一個幀。第4到第7倍的 opcode 是用來做一些指令控制的,如果值為1話就表示Payload Data是文本格式的,2則表示二進(jìn)制內(nèi)容,8表示連接關(guān)閉。第9位到第15位共7位 Payload Len 表示有效負(fù)載的字節(jié)數(shù),7位二進(jìn)制數(shù)最大表示127,如果有效負(fù)載字節(jié)數(shù)大于127的話就需要用到Extended payload length部分。

第8位的 Mask 如果設(shè)置為1就表示這個幀的有效負(fù)載內(nèi)容被掩碼處理過了,客戶端向服務(wù)端發(fā)送的幀需要進(jìn)行掩碼,而服務(wù)端向客戶端發(fā)送的數(shù)據(jù)幀不需要掩碼。為什么要使用掩碼,這個掩碼計算又是怎么進(jìn)行的呢?掩碼計算很簡單,就是把要發(fā)送的數(shù)據(jù)和另一個數(shù)字異或一下再放到Payload Data, 這個數(shù)字就是上面數(shù)據(jù)幀里的 Masking-key ,它是一個32位的數(shù)字。接收方把Payload Data再和這個數(shù)異或一下就能得到原始的數(shù)據(jù),因為和同一個數(shù)異或兩次等于原本的數(shù),即:

a ^ b ^ b = a

并且每個幀里的Making-key要求都是隨機(jī)的,不可被(代理)服務(wù)所預(yù)測的,為什么要這樣呢?文檔里面是這么說的:

The unpredictability of the masking key is essential to prevent authors of malicious applications from selecting the bytes that appear on the wire

這個解釋有點含糊, Stackoverflow 上有人說是為了避免代理緩存中毒攻擊,具體可參考 Http Cache Poinsing .

所以我們需要從這個幀里面取出掩碼的key值,還原原始的paylod數(shù)據(jù)。

數(shù)據(jù)的發(fā)送和傳輸都要靠socket對象,因為它不是走的http請求,所以在http的響應(yīng)函數(shù)里面是收不到數(shù)據(jù)的,在upgrade事件里面可以拿到這個socket,監(jiān)聽這個socket對象的data事件,就可以得到接收的數(shù)據(jù):

socket.on('data', buffer => {
    console.log('buffer len = ', buffer.length);
    console.log(buffer);
});

返回的數(shù)據(jù)類型是Node.js里的Buffer對象,把這個buffer打印出來:

buffer len = 32  <Buffer 81 9a 4c 3f 64 75 24 5a 08 19 23 13 44 01 24 56 17 55 25 4c 44 13 3e 50 09 55 2f 53 0d 10 22 4b>

這個buffer就是websocket客戶端給我們發(fā)送的數(shù)據(jù)幀了,總共有32個字節(jié),上面的打印是用的16進(jìn)制表示,可以改二進(jìn)制0101表示,和上面那個數(shù)據(jù)幀格式圖一一對照,就能夠解釋這個數(shù)據(jù)幀是什么意思,有什么內(nèi)容。把它打印成原始二進(jìn)制表示:

參照報文格式,如下圖所示:

通過opcode可以知道它是一個文本數(shù)據(jù)的幀,payload len得到文本長度為26個字節(jié),這個剛好等于上面發(fā)送的內(nèi)容長度:

同時掩碼Mask是打開的,掩碼key值存放范圍是[16, 16 + 32],因為這里不需要使用擴(kuò)展字段,所以Masking-key就直接跟在Payload len后面了,再往后就是Payload Data,范圍是[48, 48 + 26 * 8].

這就是一個完整的數(shù)據(jù)幀了,還需要把payload data用掩碼異或一下,還原原始數(shù)據(jù)。在Node.js里面進(jìn)行處理。Node.js里面的Buffer類只能操作字節(jié)級別,如讀取第n個字節(jié)的內(nèi)容,沒辦法直接操作位,如讀取第n位的數(shù)據(jù)。所以額外引入一個庫,網(wǎng)上找了一個BitBuffer,但是它的實現(xiàn)好像有問題,所以自已實現(xiàn)了一個。

如下代碼所示,實現(xiàn)一個能夠讀取任意位的BitBuffer:

class BitBuffer {
    // 構(gòu)造函數(shù)傳一個Buffer對象
    constructor (buffer) {
        this.buffer = buffer;
    }
    // 獲取第offset個位的內(nèi)容
    _getBit (offset) {
        let byteIndex = offset / 8 >> 0,
            byteOffset = offset % 8;
        // readUInt8可以讀取第n個字節(jié)的數(shù)據(jù)
        // 取出這個數(shù)的第m位即可
        let num = this.buffer.readUInt8(byteIndex) & (1 << (7 - byteOffset));
        return num >> (7 - byteOffset);
    }
}

原理很簡單,先調(diào)Node.js的Buffer的readUInt8讀取第n個字節(jié)的數(shù)據(jù),然后計算一下所要讀取的位數(shù)在這個字節(jié)的第幾位,通過與運(yùn)算,把這個位取出來,更多位運(yùn)算可以參考: 巧用JS位運(yùn)算 。

用這個代碼取出第8位的Mask Flag是否有設(shè)置,如下代碼:

socket.on('data', buffer => {
    let bitBuffer = new BitBuffer(buffer);
    let maskFlag = bitBuffer._getBit(8);
    console.log('maskFlag = ' + maskFlag);
});

打印maskFlag = 1。那么怎么取出連續(xù)的n位呢,如opcode,是從第4位到7位。這個也好辦就是把第4位到第7位分別取出來拼成一個數(shù)就好了:

getBit (offset, len = 1) {
    let result = 0;
    for (let i = 0; i < len; i++) {
        result += this._getBit(offset + i) << (len - i - 1); 
    }   
    return result;
}

這個代碼的效率不是很高,但是容易理解。有個小坑就是JS的位移只支持32位整數(shù)的操作,1 << 31會變成一個負(fù)數(shù),具體不展開討論。用這個函數(shù)取32位的掩碼值就會有問題。

可以利用這個函數(shù)取出opcode和payload len:

socket.on('data', buffer => {
    let bitBuffer = new BitBuffer(buffer);
    let maskFlag = bitBuffer.getBit(8),
        opcode = bitBuffer.getBit(4, 4), 
        payloadLen = bitBuffer.getBit(9, 7);
    console.log('maskFlag = ' + maskFlag);
    console.log('opcode = ' + opcode);
    console.log('payloadLen = ' + payloadLen);
});

打印如下:

maskFlag = 1  opcode = 1  payloadLen = 26

取掩碼值單獨(dú)實現(xiàn)一下,這個掩碼是拆成4個數(shù)使用的,一個字節(jié)表示一個數(shù),借助上面的getBit函數(shù),代碼如下:

getMaskingKey (offset) {
    const BYTE_COUNT = 4;
    let masks = []; 
    for (let i = 0; i < BYTE_COUNT; i++) {
        masks.push(this.getBit(offset + i * 8, 8));
    }   
    return masks;
}

這個例子的掩碼值是從第16位開始,所以offset是16:

let maskKeys = bitBuffer.getMaskingKey(16);
console.log('maskKey = ' + maskKeys);

打印出來的maskKey為:

maskKeys = 76, 63, 100, 117

怎么用這個Mask Key進(jìn)行異或呢,文檔里面是這么規(guī)定的:

j = i MOD 4  transformed-octet-i = original-octet-i XOR masking-key-octet-j

也就是把Payload Data里面的第n,n + 1,n + 2,n + 3個字節(jié)內(nèi)容分別與makKey數(shù)組的第0,1,2,3進(jìn)行異或即可,所以這個實現(xiàn)也比較簡單,如下代碼所示:

getXorString (byteOffset, byteCount, maskingKeys) {
    let text = ''; 
    for (let i = 0; i < byteCount; i++) {
        let j = i % 4;
        // 通過異或得到原始的utf-8編碼
        let transformedByte = this.buffer.readUInt8(byteOffset + i)
                                  ^ maskingKeys[j];
        // 把編碼值轉(zhuǎn)成對應(yīng)的字符
        text += String.fromCharCode(transformedByte);
    }   
    return text;
}

異或操作之后就可以得到編碼值,再借助String.fromCharCode就能得到對應(yīng)的文本,如根據(jù)ASCII表,97就會被還原成字母’a’。

這個例子的payload data的偏移是第6個字節(jié)開始的,這里我們先直接寫死:

let payloadLen = bitBuffer.getBit(9, 7),
    maskKeys = bitBuffer.getMaskingKey(16);
let payloadText = bitBuffer.getXorString(48 / 8, payloadLen, maskKeys);
console.log('payloadText = ' + payloadText);

打印的文本內(nèi)容為:

payloadText = hello, this is from client

到這里,就把接收的數(shù)據(jù)還原出來了。如果想要發(fā)送數(shù)據(jù),就是把讀取的過程逆一下,按照幀格式去拼一個符合規(guī)范的幀發(fā)送給對方,區(qū)別是服務(wù)端的幀數(shù)據(jù)是不需要Mask的,如果你Mask了,Chrome會報一個異常,說數(shù)據(jù)不需要Mask,拒絕解析接收到的數(shù)據(jù)。

我們再從Chrome源碼看Websocket客戶端的實現(xiàn),來補(bǔ)充一些細(xì)節(jié)。

Chrome的websockets代碼是在src/net/websockets,例如Chrome在握手的時候是怎么生成一個隨機(jī)的sec-websocket-key?如下代碼所示:

std::string GenerateHandshakeChallenge() {
  std::string raw_challenge(websockets::kRawChallengeLength, '\0');
  crypto::RandBytes(base::string_as_array(&raw_challenge),
                    raw_challenge.length());
  std::string encoded_challenge;
  base::Base64Encode(raw_challenge, &encoded_challenge);
  return encoded_challenge;
}

它是用的一個crypto::RandBytes生成隨機(jī)字節(jié),而在檢驗sec-websocket-accept也是用的同樣的計算方法:

std::string ComputeSecWebSocketAccept(const std::string& key) {
  std::string accept;
  std::string hash = base::SHA1HashString(key + websockets::kWebSocketGuid);
  base::Base64Encode(hash, &accept);
  return accept;
}

而在使用掩碼計算的時候也是用的一樣的方法:

inline void MaskWebSocketFramePayloadByBytes(
    const WebSocketMaskingKey& masking_key,
    size_t masking_key_offset,
    char* const begin,
    char* const end) {
  for (char* masked = begin; masked != end; ++masked) {
    *masked ^= masking_key.key[masking_key_offset++];
    if (masking_key_offset == WebSocketFrameHeader::kMaskingKeyLength)
      masking_key_offset = 0;
  }
}

其它的還有deflate壓縮、cookie、擴(kuò)展extensions等,本文不再展開討論。

另外還有一個問題,使用一個WebSocket就需要操持一個TCP連接,如果有1000個用戶同時在線,那么服務(wù)端就得保持1000個TCP連接,而一個TCP連接通常需要占用一個獨(dú)立的線程,而線程的開銷是很大的,所以WebSocket對服務(wù)端的壓力特別大?其實也不見得有那么大,因為Linux有一個epoll的服務(wù)模型,它是一個事件驅(qū)動機(jī)制的,能夠讓一個核支持并發(fā)的很多個連接。

最后一個問題,由于連接是一直操持的,如果連接雙方有一方異常退出了,沒有發(fā)送一個關(guān)閉連接的包通知對方,那么對方就會傻傻地操持著這個沒用的連接,所以WebSocket又引入了一個ping/pong的消息幀,幀頭里的opcode為0x9就表示是一個ping幀,0x10表示pong的響應(yīng)幀。所以可以讓客戶端不斷地ping,如每隔30秒就ping一次,服務(wù)收到了ping就知道當(dāng)前客戶端還活著,給一個pong的響應(yīng),如果服務(wù)端太久沒收到ping了如1分鐘,那么就認(rèn)為這個客戶端已經(jīng)走了直接關(guān)閉連接。而客戶端如果沒收到pong響應(yīng)那么就認(rèn)為當(dāng)前連接已經(jīng)斷了,需要重連。瀏覽器JS的API沒有開放ping/pong,需要自已實現(xiàn)一個消息類型。

本篇主要討論了WebSocket存在的意義,給瀏覽器開放一個socket的API,并進(jìn)行標(biāo)準(zhǔn)化,除了瀏覽器,APP等也都可以按照這個標(biāo)準(zhǔn)實現(xiàn),彌補(bǔ)了HTTP單向傳輸?shù)娜秉c。還討論了WebSocket報文幀的格式,以及怎么用Node.js讀取這個報文幀,客戶端會把它發(fā)送的內(nèi)容進(jìn)行掩碼處理,服務(wù)端收到的也需要進(jìn)行掩碼還原。我們發(fā)現(xiàn)Chrome客戶端的實現(xiàn)有很多地方是類似的。

怎么保證WebSocket傳輸?shù)姆(wěn)定性可能又是另外一個話題了,包括出錯重連機(jī)制,跨中美地區(qū)的可能需要使用專線等。

Post Views: 8

 

來自:https://www.yinchengli.com/2018/05/27/chrome-websocket/

 

標(biāo)簽: linux 代碼 服務(wù)器 開發(fā)者 時間服務(wù)器 通信 網(wǎng)絡(luò)

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:JavaScript 的 this 原理

下一篇:正則表達(dá)式基礎(chǔ)知識