文章分享

開放、平等、協(xié)作、快速、分享

當(dāng)前位置:首頁(yè)>文章分享

WebSocket協(xié)議—從頂層到底層的實(shí)現(xiàn)原理

摘錄:HCTech 無錫和控電子   時(shí)間:2020-08-07   訪問量:3421

WebSocket協(xié)議—從頂層到底層的實(shí)現(xiàn)原理

時(shí)間:2019-09-05   訪問量:7

從RealTime說起

自從即時(shí)Web的概念提出后,RealTime便成為了web開發(fā)者們津津樂道的話題。實(shí)時(shí)化的web應(yīng)用,憑借其響應(yīng)迅速、無需刷新、節(jié)省網(wǎng)絡(luò)流量的特性,不僅讓開發(fā)者們眼前一亮,更是為用戶帶來絕佳的網(wǎng)絡(luò)體驗(yàn)。

近年來關(guān)于RealTime的實(shí)現(xiàn),主要還是基于Ajax的拉取和Comet的推送。大家都知道Ajax,這是一種借助瀏覽器端JavaScript實(shí)現(xiàn)的異步無刷新請(qǐng)求功能:要客戶端按需向服務(wù)器發(fā)出請(qǐng)求,并異步獲取來自服務(wù)器的響應(yīng),然后按照邏輯更新當(dāng)前頁(yè)面的相應(yīng)內(nèi)容。但是這僅僅是拉取啊,這并不是真正的RealTime:缺少服務(wù)器端的自動(dòng)推送!因此,我們不得不使用另一種略復(fù)雜的技術(shù)Comet,只有當(dāng)這兩者配合起來,這個(gè)web應(yīng)用才勉強(qiáng)算是個(gè)RealTime應(yīng)用!

Hello WebSocket!

WebSocket in Client Chrome

不過隨著HTML5草案的不斷完善,越來越多的現(xiàn)代瀏覽器開始全面支持WebSocket技術(shù)了。至于WebSocket,我想大家或多或少都聽說過。

這個(gè)WebSocket是一種全新的協(xié)議。它將TCP的Socket(套接字)應(yīng)用在了web page上,從而使通信雙方建立起一個(gè)保持在活動(dòng)狀態(tài)連接通道,并且屬于全雙工(雙方同時(shí)進(jìn)行雙向通信)。

其實(shí)是這樣的,WebSocket協(xié)議是借用HTTP協(xié)議的101 switch protocol來達(dá)到協(xié)議轉(zhuǎn)換的,從HTTP協(xié)議切換成WebSocket通信協(xié)議。

再簡(jiǎn)單點(diǎn)來說,它就好像將Ajax和Comet技術(shù)的特點(diǎn)結(jié)合到了一起,只不過性能要高并且使用起來要方便的多(當(dāng)然是之指在客戶端方面。。)

設(shè)計(jì)哲學(xué)

RFC草案中已經(jīng)說明,WebSocket的目的就是為了在基礎(chǔ)上保證傳輸?shù)臄?shù)據(jù)量最少。
這個(gè)協(xié)議是基于Frame而非Stream的,也就是說,數(shù)據(jù)的傳輸不是像傳統(tǒng)的流式讀寫一樣按字節(jié)發(fā)送,而是采用一幀一幀的Frame,并且每個(gè)Frame都定義了嚴(yán)格的數(shù)據(jù)結(jié)構(gòu),因此所有的信息就在這個(gè)Frame載體中。(后面會(huì)詳細(xì)介紹這個(gè)Frame)

特點(diǎn)

  • 基于TCP協(xié)議

  • 具有命名空間

  • 可以和HTTP Server共享同一port

打開連接-握手

下面我先用自然語言描述一下WebSocket的工作原理:
若要實(shí)現(xiàn)WebSocket協(xié)議,首先需要瀏覽器主動(dòng)發(fā)起一個(gè)HTTP請(qǐng)求。

這個(gè)請(qǐng)求頭包含“Upgrade”字段,內(nèi)容為“websocket”(注:upgrade字段用于改變HTTP協(xié)議版本或換用其他協(xié)議,這里顯然是換用了websocket協(xié)議),還有一個(gè)最重要的字段“Sec-WebSocket-Key”,這是一個(gè)隨機(jī)的經(jīng)過base64編碼的字符串,像密鑰一樣用于服務(wù)器和客戶端的握手過程。一旦服務(wù)器君接收到來自客戶端的upgrade請(qǐng)求,便會(huì)將請(qǐng)求頭中的“Sec-WebSocket-Key”字段提取出來,追加一個(gè)固定的“魔串”:258EAFA5-E914-47DA-95CA-C5AB0DC85B11,并進(jìn)行SHA-1加密,然后再次經(jīng)過base64編碼生成一個(gè)新的key,作為響應(yīng)頭中的“Sec-WebSocket-Accept”字段的內(nèi)容返回給瀏覽器。一旦瀏覽器接收到來自服務(wù)器的響應(yīng),便會(huì)解析響應(yīng)中的“Sec-WebSocket-Accept”字段,與自己加密編碼后的串進(jìn)行匹配,一旦匹配成功,便有建立連接的可能了(因?yàn)檫€依賴許多其他因素)。

這是一個(gè)基本的Client請(qǐng)求頭:(我只寫了關(guān)鍵的幾個(gè)字段)

Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: ************==
Sec-WebSocket-Version: **

Server正確接收后,會(huì)返回一個(gè)響應(yīng)頭:(同樣只有關(guān)鍵的)

Upgrade:websocket
Connnection: Upgrade
Sec-WebSocket-Accept: ******************

這表示雙方握手成功了,之后就是全雙工的通信。

安全性限制

當(dāng)你看完上面一節(jié)后一定會(huì)質(zhì)疑該協(xié)議的保密性和安全性,看上去任何客戶端都能夠很容易的向WS服務(wù)器發(fā)起請(qǐng)求或偽裝截獲數(shù)據(jù)。WebSocket協(xié)議規(guī)定在連接建立時(shí)檢查Upgrade請(qǐng)求中的某些字段(如Origin),對(duì)于不符合要求的請(qǐng)求立即截?cái)?;在通信過程中,也對(duì)Frame中的控制位做了很多限制,以便禁止異常連接。

對(duì)于握手階段的檢查,這種限制僅僅是在瀏覽器中,對(duì)于特殊的客戶端(non-browser,如編碼構(gòu)造正確的請(qǐng)求頭發(fā)送連接請(qǐng)求),這種源模型就失效了。

(后面會(huì)介紹通信過程中的連接關(guān)閉種類與流程。)

除此之外,WebSocket也規(guī)定了加密數(shù)據(jù)傳輸方法,允許使用TLS/SSL對(duì)通信進(jìn)行加密,類似HTTPS。默認(rèn)情況下,ws協(xié)議使用80端口進(jìn)行普通連接,加密的TLS連接默認(rèn)使用443端口。

和TCP、HTTP協(xié)議的關(guān)系

WebSocket是基于TCP的獨(dú)立的協(xié)議。
和HTTP的唯一關(guān)聯(lián)就是HTTP服務(wù)器需要發(fā)送一個(gè)“Upgrade”請(qǐng)求,即101 Switching Protocol到HTTP服務(wù)器,然后由服務(wù)器進(jìn)行協(xié)議轉(zhuǎn)換。

ws的子協(xié)議

客戶端向服務(wù)器發(fā)起握手請(qǐng)求的header中可能帶有“Sec-WebSocket-Protocol”字段,用來指定一個(gè)特定的子協(xié)議,一旦這個(gè)字段有設(shè)置,那么服務(wù)器需要在建立連接的響應(yīng)頭中包含同樣的字段,內(nèi)容就是選擇的子協(xié)議之一。

子協(xié)議的命名應(yīng)該是注冊(cè)過的(有一套規(guī)范)。
為了避免潛在的沖突,建議子協(xié)議的源(發(fā)起者)使用ASCII編碼的域名。
例子:
一個(gè)注冊(cè)過的子協(xié)議叫“chat.xxx.com”,另一個(gè)叫“chat.xxx.org”。這兩個(gè)子協(xié)議都會(huì)被server同時(shí)實(shí)現(xiàn),server會(huì)動(dòng)態(tài)的選擇使用哪個(gè)子協(xié)議(取決于客戶端發(fā)送過來的值)。

Extensions

擴(kuò)展是用來增加ws協(xié)議一些新特性的,這里就不詳細(xì)說了。

建立連接部分代碼

上面說的僅僅是個(gè)概述,重要的是該如何在我們的web應(yīng)用中使用或者說該如何建立一個(gè)基于WebSocket的應(yīng)用呢?

我直說了,客戶端使用WebSocket簡(jiǎn)直易如反掌,服務(wù)端實(shí)現(xiàn)WebSocket真是難的一B??!尤其是我們現(xiàn)在還沒有學(xué)過計(jì)算機(jī)網(wǎng)絡(luò),對(duì)一些網(wǎng)絡(luò)底層的(如TCP/IP協(xié)議)知識(shí)了解的太少,理解并實(shí)現(xiàn)WebSocket確實(shí)不太容易。所以這次我先把WebSocket用提供一部分接口的高級(jí)語言來實(shí)現(xiàn)。

Node.js的異步I/O模型實(shí)在是太適合這種類型的應(yīng)用了,因此我選擇它作為I/O編程的首選。來看下面的JavaScript代碼~:
Note:以下代碼僅用于闡明原理,不可用于生產(chǎn)環(huán)境!

      var http = require('http');    var crypto = require('crypto');    var MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";    // HTTP服務(wù)器部分
    var server = http.createServer(function (req, res) {      res.end('websocket test\r\n');
    });    // Upgrade請(qǐng)求處理
    server.on('upgrade', callback);    function callback(req, socket) {      // 計(jì)算返回的key
      var resKey = crypto.createHash('sha1')
        .update(req.headers['sec-websocket-key'] + MAGIC_STRING)
        .digest('base64');      // 構(gòu)造響應(yīng)頭
      resHeaders = ([        'HTTP/1.1 101 Switching Protocols',        'Upgrade: websocket',        'Connection: Upgrade',        'Sec-WebSocket-Accept: ' + resKey
      ]).concat('', '').join('\r\n');      // 添加通信數(shù)據(jù)處理
      socket.on('data', function (data) {        // ...
      });      // 響應(yīng)給客戶端
      socket.write(resHeaders);
    }    server.listen(3000);

上面的代碼是等待客戶端與之握手,當(dāng)有客戶端發(fā)出請(qǐng)求時(shí),會(huì)按照“加密-編碼-返回”的流程與之建立通信通道。既然連接已建立,接下來就是雙方的通信了。為了讓大家明白WebSocket的全程使用,在此之前有必要提一下支持WebSocket的底層協(xié)議的實(shí)現(xiàn)。

協(xié)議

協(xié)議這種東西就像某種魔法,賦予了計(jì)算機(jī)之間各種神奇的通信能力,但對(duì)用戶來說卻是透明的。
不過對(duì)于WebSocket協(xié)議,我們可以透過IETF的RFC規(guī)范,看到關(guān)于實(shí)現(xiàn)WebSocket細(xì)節(jié)的每次變更與修正。

Frame

前面已經(jīng)說過了WebSocket在客戶端與服務(wù)端的“Hand-Shaking”實(shí)現(xiàn),所以這里講數(shù)據(jù)傳輸。
WebSocket傳輸?shù)臄?shù)據(jù)都是以Frame(幀)的形式實(shí)現(xiàn)的,就像TCP/UDP協(xié)議中的報(bào)文段Segment。下面就是一個(gè)Frame:(以bit為單位表示)

  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+

按照RFC中的描述:

  • FIN: 1 bit

    表示這是一個(gè)消息的最后的一幀。第一個(gè)幀也可能是最后一個(gè)。  
    %x0 : 還有后續(xù)幀  
    %x1 : 最后一幀
  • RSV1、2、3: 1 bit each

    除非一個(gè)擴(kuò)展經(jīng)過協(xié)商賦予了非零值以某種含義,否則必須為0
    如果沒有定義非零值,并且收到了非零的RSV,則websocket鏈接會(huì)失敗
  • Opcode: 4 bit

    解釋說明 “Payload data” 的用途/功能
    如果收到了未知的opcode,最后會(huì)斷開鏈接
    定義了以下幾個(gè)opcode值:
        %x0 : 代表連續(xù)的幀
        %x1 : text幀
        %x2 : binary幀
        %x3-7 : 為非控制幀而預(yù)留的
        %x8 : 關(guān)閉握手幀
        %x9 : ping幀
    %xA :  pong幀
    %xB-F : 為非控制幀而預(yù)留的
  • Mask: 1 bit

    定義“payload data”是否被添加掩碼
    如果置1, “Masking-key”就會(huì)被賦值
    所有從客戶端發(fā)往服務(wù)器的幀都會(huì)被置1
  • Payload length: 7 bit | 7+16 bit | 7+64 bit

    “payload data” 的長(zhǎng)度如果在0~125 bytes范圍內(nèi),它就是“payload length”,
    如果是126 bytes, 緊隨其后的被表示為16 bits的2 bytes無符號(hào)整型就是“payload length”,
    如果是127 bytes, 緊隨其后的被表示為64 bits的8 bytes無符號(hào)整型就是“payload length”
  • Masking-key: 0 or 4 bytes

    所有從客戶端發(fā)送到服務(wù)器的幀都包含一個(gè)32 bits的掩碼(如果“mask bit”被設(shè)置成1),否則為0 bit。一旦掩碼被設(shè)置,所有接收到的payload data都必須與該值以一種算法做異或運(yùn)算來獲取真實(shí)值。(見下文)
  • Payload data: (x+y) bytes

    它是"Extension data"和"Application data"的總和,一般擴(kuò)展數(shù)據(jù)為空。
  • Extension data: x bytes

    除非擴(kuò)展被定義,否則就是0
    任何擴(kuò)展必須指定其Extension data的長(zhǎng)度
  • Application data: y bytes

    占據(jù)"Extension data"之后的剩余幀的空間

注意:這些數(shù)據(jù)都是以二進(jìn)制形式表示的,而非ascii編碼字符串

構(gòu)造Frame

Frame的結(jié)構(gòu)已經(jīng)清楚了,我們就構(gòu)造一個(gè)Frame。
在構(gòu)造時(shí),我們可以把Frame分成兩段:控制位數(shù)據(jù)位。其中控制位就是Frame的前兩字節(jié),包含F(xiàn)IN、Opcode等與該Frame的元信息。

Note:網(wǎng)絡(luò)中使用大端次序(Big endian)表示大于一字節(jié)的數(shù)據(jù),稱之為網(wǎng)絡(luò)字節(jié)序。
Node.js中提供了Buffer對(duì)象,專門用來彌補(bǔ)JavaScript在處理字節(jié)數(shù)據(jù)上的不足,這里正好可以用它來完成這個(gè)任務(wù):

  // 控制位: FIN, Opcode, MASK, Payload_len
  var preBytes = [], 
      payBytes = new Buffer('test websocket'), 
      mask = 0;
      masking_key = Buffer.randomByte(4);  var dataLength = payBytes.length;  // 構(gòu)建Frame的第一字節(jié)
  preBytes.push((frame['FIN'] << 7) + frame['Opcode']);  // 處理不同長(zhǎng)度的dataLength,構(gòu)建Frame的第二字節(jié)(或第2~第8字節(jié))
  // 注意這里以大端字節(jié)序構(gòu)建dataLength > 126的dataLenght
  if (dataLength < 126) {    preBytes.push((frame['MASK'] << 7) + dataLength);
  } else if (dataLength < 65536) {    preBytes.push(
      (frame['MASK'] << 7) + 126, 
      (dataLength & 0xFF00) >> 8,
      dataLength & 0xFF
    );
  } else {    preBytes.push(
      (frame['MASK'] << 7) + 127,      0, 0, 0, 0,
      (dataLength & 0xFF000000) >> 24,
      (dataLength & 0xFF0000) >> 16,
      (dataLength & 0xFF00) >> 8,
      dataLength & 0xFF
    );
  }

  preBytes = new Buffer(preBytes);  // 如果有掩碼,就對(duì)數(shù)據(jù)進(jìn)行加密,并構(gòu)建之后的控制位
  if (mask) {
    preBytes = Buffer.concat([preBytes, masking_key]);    for (var i = 0; i < dataLength; i++) 
      payBytes[i] ^= masking_key[i % 4];
  }  // 生成一個(gè)Frame
  var frame = Buffer.concat([preBytes, payBytes]);

按照這種格式,就定義好了一個(gè)幀,客戶端或者服務(wù)器就可以用這個(gè)幀來互傳數(shù)據(jù)了。既然數(shù)據(jù)已經(jīng)接收,接下來看看如何處理這些數(shù)據(jù)。

Masking

規(guī)范里解釋了Masking-key掩碼的作用了:就是當(dāng)mask字段的值為1時(shí),payload-data字段的數(shù)據(jù)需要經(jīng)這個(gè)掩碼進(jìn)行解密。

在處理數(shù)據(jù)之前,我們要清楚一件事:服務(wù)器推送到客戶端的消息中,mask字段是0,也就是說Masking-key為空。這樣的話,數(shù)據(jù)的解析就不涉及到掩碼,直接使用就行。

但是我們前面提到過,如果消息是從客戶端發(fā)送到服務(wù)器,那么mask一定是1,Masking-key一定是一個(gè)32bit的值。下面我們來看看數(shù)據(jù)是如何解析的:

當(dāng)消息到達(dá)服務(wù)器后,服務(wù)器程序就開始以字節(jié)為單位逐步讀取這個(gè)幀,當(dāng)讀取到payload-data時(shí),首先將數(shù)據(jù)按byte依次與Masking-key中的4個(gè)byte按照如下算法做異或:

      //假設(shè)我們發(fā)送的"Payload data"以變量`data`表示,字節(jié)(byte)數(shù)為len;
      //masking_key為4byte的mask掩碼組成的數(shù)組
    //offset:跳過的字節(jié)數(shù)

    for (var i = 0; i < len; i++) {        var j = i % 4;
        data[offset + i] ^= masking_key[j];
    }

上面的JavaScript代碼給出了掩碼Masking-key是如何解密Payload-data的:先對(duì)i取模來獲得要使用的masking-key的索引,然后用data[offset + i]masking_key[j]做異或,從而得到真實(shí)的byte數(shù)據(jù)。

控制幀

控制幀用來說明WebSocket的狀態(tài)信息,用來控制分片、連接的關(guān)閉等等。所有的控制幀必須有一個(gè)小于等于125字節(jié)的payload,并且control Frames不允許被分片。Opcode0x0(持續(xù)的幀),0x8(關(guān)閉連接),0x9(Ping幀)和0xA(Pong幀)代表控制幀。

一般Ping Frame用來對(duì)一個(gè)有超時(shí)機(jī)制的套接字keepalive或者驗(yàn)證對(duì)方是否有響應(yīng)。Pong Frame就是對(duì)Ping的回應(yīng)。

數(shù)據(jù)幀

前面我們總是談到“控制幀”和“非控制幀”,想必大家已經(jīng)看出來一些門路。其實(shí)數(shù)據(jù)幀就是非控制幀。因?yàn)檫@個(gè)幀并不是用來提供協(xié)議連接狀態(tài)信息的。數(shù)據(jù)幀由最高符號(hào)位是0的Opcode確定,現(xiàn)在可用的幾個(gè)數(shù)據(jù)幀的Opcode是0x1(utf-8文本)、0x2(二進(jìn)制數(shù)據(jù))。

分片(Fragment)

理論上來說,每個(gè)幀(Frame)的大小是沒有限制的,因?yàn)閜ayload-data在整個(gè)幀的最后。但是發(fā)送的數(shù)據(jù)有不能太大,否則 WebSocket 很可能無法高效的利用網(wǎng)絡(luò)帶寬。那如果我們想傳點(diǎn)大數(shù)據(jù)該怎么辦呢?WebSocket協(xié)議給我們提供了一個(gè)方法:分片,將原本一個(gè)大的幀拆分成數(shù)個(gè)小的幀。下面是把一個(gè)大的Frame分片的圖示:

  編號(hào):      0  1  ....  n-2 n-1
  分片:     |——|——|......|——|——|
  FIN:      0  0  ....   0  1
  Opcode:   !0 0  ....   0  0

由圖可知,第一個(gè)分片的FIN為0,Opcode為非0值(0x1或0x2),最后一個(gè)分片的FIN為1,Opcode為0。中間分片的FINOpcode二者均為0。

Note1:消息的分片必須由發(fā)送者按給定的順序發(fā)送給接收者。

Note2:控制幀禁止分片

Note3:接受者不必按順序緩存整個(gè)frame來處理

關(guān)閉連接

正常的連接關(guān)閉流程

  1. 發(fā)送關(guān)閉連接請(qǐng)求(Close Handshake)
    即發(fā)送Close Frame(Opcode為0x8)。一旦一端發(fā)送/接收了一個(gè)Close Frame,就開始了Close Handshake,并且連接狀態(tài)變?yōu)?code style="box-sizing: border-box; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 12px; color: rgb(232, 62, 140); word-break: break-word; padding: 0.2em 0.4em; margin: 0px; background-color: rgba(27, 31, 35, 0.0470588); border-radius: 3px;">Closing。
    Close Frame中如果包含Payload data,則data的前2字節(jié)必須為兩字節(jié)的無符號(hào)整形,(同樣遵循網(wǎng)絡(luò)字節(jié)序:BE)用于表示狀態(tài)碼,如果2byte之后仍有內(nèi)容,則應(yīng)包含utf-8編碼的關(guān)閉理由。
    如果一端在之前未發(fā)送過Close Frame,則當(dāng)他收到一個(gè)Close Frame時(shí),必須回復(fù)一個(gè)Close Frame。但如果它正在發(fā)送數(shù)據(jù),則可以推遲到當(dāng)前數(shù)據(jù)發(fā)送完,再發(fā)送Close Frame。比如Close Frame在分片發(fā)送時(shí)到達(dá),則要等到所有剩余分片發(fā)送完之后,才可以作出回復(fù)。

  2. 關(guān)閉WebSocket連接
    當(dāng)一端已經(jīng)收到Close Frame,并已發(fā)送了Close Frame時(shí),就可以關(guān)閉連接了,close handshake過程結(jié)束。這時(shí)丟棄所有已經(jīng)接收到的末尾字節(jié)。

  3. 關(guān)閉TCP連接
    當(dāng)?shù)讓覶CP連接關(guān)閉時(shí),連接狀態(tài)變?yōu)?code style="box-sizing: border-box; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 12px; color: rgb(232, 62, 140); word-break: break-word; padding: 0.2em 0.4em; margin: 0px; background-color: rgba(27, 31, 35, 0.0470588); border-radius: 3px;">Closed。

clean closed

如果TCP連接在Close handshake完成之后關(guān)閉,就表示W(wǎng)ebSocket連接已經(jīng)clean closed(徹底關(guān)閉)了。
如果WebSocket連接并未成功建立,狀態(tài)也為連接已關(guān)閉,但并不是clean closed。

正常關(guān)閉

正常關(guān)閉過程屬于clean close,應(yīng)當(dāng)包含close handshake

通常來講,應(yīng)該由服務(wù)器關(guān)閉底層TCP連接,而客戶端應(yīng)該等待服務(wù)器關(guān)閉連接,除非等待超時(shí)的話,那么自己關(guān)閉底層TCP連接。

服務(wù)器可以隨時(shí)關(guān)閉WebSocket連接,而客戶端不可以主動(dòng)斷開連接。

異常關(guān)閉

  1. 由于某種算法或規(guī)定,一端直接關(guān)閉連接。(特指在open handshake(打開連接)階段)

  2. 底層連接丟失導(dǎo)致的連接中斷。

連接失敗

由于某種算法或規(guī)范要求指定連接失敗。這時(shí),客戶端和服務(wù)器必須關(guān)閉WebSocket連接。當(dāng)一端得知連接失敗時(shí),不準(zhǔn)再處理數(shù)據(jù),包括響應(yīng)close frame。

從異常關(guān)閉中恢復(fù)

為了防止海量客戶端同時(shí)發(fā)起重連請(qǐng)求(reconnect),客戶端應(yīng)該推遲一個(gè)隨機(jī)時(shí)間后重新連接,可以選擇回退算法來實(shí)現(xiàn),比如截?cái)喽M(jìn)制指數(shù)退避算法。

關(guān)于補(bǔ)充

這兩篇blog里主要用自然語言講了WebSocket的實(shí)現(xiàn)。代碼的細(xì)節(jié)操作(例如:處理數(shù)據(jù)、安全處理等)并沒有給出,因?yàn)楹诵膶?shí)現(xiàn)原理已經(jīng)闡明。

因?yàn)榻趯懥艘粋€(gè)比較完整的WebSocket庫(kù)RocketEngine,在編碼過程中發(fā)現(xiàn)了好多需要注意的問題,特此加以補(bǔ)充和修正,增加了部分章節(jié),改正了一些不精確的說法,同時(shí)將兩篇日志合并。

如需詳細(xì)學(xué)習(xí),請(qǐng)戳=> RocketEngine(附詳細(xì)注釋與wiki)


上一篇:nginx反向代理webSocket配置

下一篇:Nginx代理webSocket時(shí)60s自動(dòng)斷開, 怎么保持長(zhǎng)連接

在線咨詢

點(diǎn)擊這里給我發(fā)消息 售前咨詢專員

點(diǎn)擊這里給我發(fā)消息 售后服務(wù)專員

在線咨詢

免費(fèi)通話

24小時(shí)免費(fèi)咨詢

請(qǐng)輸入您的聯(lián)系電話,座機(jī)請(qǐng)加區(qū)號(hào)

免費(fèi)通話

微信掃一掃

微信聯(lián)系
返回頂部