websocket

websocket在什麼背景下誕生?

短輪詢(Polling)

短輪詢(Polling)的實現思路就是瀏覽器端每隔幾秒鐘向服務器端發送http請求,服務端在收到請求後,不管是否有數據更新,都直接進行響應。在服務端響應完成,就會關閉這個Tcp鏈接,以下圖所示:git

webSocket

示例代碼實現以下:github

function Polling() {
    fetch(url).then(data => {
        // somthing
    }).catch(err => {
        console.log(err);
    });
}
setInterval(polling, 5000);
  • 優勢:能夠看到實現很是簡單,它的兼容性也比較好的只要支持http協議就能夠用這種方式實現。
  • 缺點:可是它的缺點也很明顯就是很是的消耗資源,由於創建Tcp鏈接是很是消耗資源的,服務端響應完成就會關閉這個Tcp鏈接,下一次請求再次創建Tcp鏈接。

長輪詢(Long-Polling)

客戶端發送請求後服務器端不會當即返回數據,服務器端會阻塞請求鏈接不會當即斷開,直到服務器端有數據更新或者是鏈接超時才返回,客戶端纔再次發出請求新建鏈接、如此反覆從而獲取最新數據。大體效果以下:web

webSocket

客戶端的代碼以下:算法

function LongPolling() {
    fetch(url).then(data => {
        LongPolling();
    }).catch(err => {
        LongPolling();
        console.log(err);
    });
}
LongPolling();
  • 優勢: 長輪詢和短輪詢比起來,明顯減小了不少沒必要要的http請求次數,相比之下節約了資源。
  • 缺點:鏈接掛起也會致使資源的浪費。

WebSocket

WebSocket是一種協議,是一種與HTTP 同等的網絡協議,二者都是應用層協議,都基於 TCP 協議。可是 WebSocket 是一種雙向通訊協議,在創建鏈接以後,WebSocket 的 server 與 client 都能主動向對方發送或接收數據。同時,WebSocket在創建鏈接時須要藉助 HTTP 協議,鏈接創建好了以後 client 與 server 之間的雙向通訊就與 HTTP 無關了。npm

相比於短輪詢、長輪詢的每次「請求-應答」都要client 與 server 創建鏈接的模式,WebSocket 是一種長鏈接的模式。就是一旦WebSocket 鏈接創建後,除非client 或者 server 中有一端主動斷開鏈接,不然每次數據傳輸以前都不須要HTTP 那樣請求數據。瀏覽器

另外,短輪詢、長輪詢服務端都是被動的響應,屬於單工通訊。而websocket客戶端、服務端都能主動的向對方發送消息,屬於全雙工通訊。緩存

WebSocket 對象提供了一組 API,用於建立和管理 WebSocket 鏈接,以及經過鏈接發送和接收數據。瀏覽器提供的WebSocket API很簡潔,調用示例以下:安全

var ws = new WebSocket('wss://example.com/socket'); // 建立安全WebSocket 鏈接(wss)
ws.onerror = function (error) { ... } // 錯誤處理
ws.onclose = function () { ... } // 關閉時調用
ws.onopen = function () { ws.send("Connection established. Hello server!");} // 鏈接創建時調用向服務端發送消息
ws.onmessage = function(msg) {  ... }// 接收服務端發送的消息複製代碼

HTTP、WebSocket 等應用層協議,都是基於 TCP 協議來傳輸數據的。咱們能夠把這些高級協議理解成對 TCP 的封裝。既然你們都使用 TCP 協議,那麼你們的鏈接和斷開,都要遵循 TCP 協議中的三次握手和四次握手 ,只是在鏈接以後發送的內容不一樣,或者是斷開的時間不一樣。對於 WebSocket 來講,它必須依賴 HTTP 協議進行一次握手 ,握手成功後,數據就直接從 TCP 通道傳輸,與 HTTP 無關了。服務器

websocket是怎樣握手的?

image.png

  • 瀏覽器、服務器創建TCP鏈接,三次握手。這是通訊的基礎,傳輸控制層,若失敗後續都不執行。
  • TCP鏈接成功後,瀏覽器經過HTTP協議向服務器發送帶有Upgrade頭的HTTP Request消息
    image.png
    Connection:HTTP1.1中規定Upgrade只能應用在直接鏈接中。帶有Upgrade頭的HTTP1.1消息必須含有Connection頭,由於Connection頭的意義就是,任何接收到此消息的人(每每是代理服務器)都要在轉發此消息以前處理掉Connection中指定的域(即不轉發Upgrade域)。
    Upgrade是HTTP1.1中用於定義轉換協議的header域。 若是服務器支持的話,客戶端但願使用已經創建好的HTTP(TCP)鏈接,切換到WebSocket協議。
    Sec-WebSocket-Key是一個Base64encode的值,這個是客戶端隨機生成的,用於服務端的驗證,服務器會使用此字段組裝成另外一個key值放在握手返回信息裏發送客戶端。
    Sec-WebSocket-Version標識了客戶端支持的WebSocket協議的版本列表。
    Sec-WebSocket-Extensions是客戶端用來與服務端協商擴展協議的字段,permessage-deflate表示協商是否使用傳輸數據壓縮,client_max_window_bits表示採用LZ77壓縮算法時,滑動窗口相關的SIZE大小。
    Sec_WebSocket-Protocol是一個用戶定義的字符串,用來區分同URL下,不一樣的服務所須要的協議,標識了客戶端支持的子協議的列表。
  • 服務器收到客戶端的握手請求後,一樣採用HTTP協議回饋。
    image.png
    HTTP的版本爲HTTP1.1,返回碼是101,表示升級到websocket協議
    Connection字段,包含Upgrade
    Upgrade字段,包含websocket
    Sec-WebSocket-Accept字段,詳細介紹一下:websocket

    Sec-WebSocket-Accept字段生成步驟:

    1. 將Sec-WebSocket-Key與協議中已定義的一個GUID 「258EAFA5-E914-47DA-95CA-C5AB0DC85B11」進行拼接。
    2. 將步驟1中生成的字符串進行SHA1編碼。
    3. 將步驟2中生成的字符串進行Base64編碼。

    客戶端經過驗證服務端返回的Sec-WebSocket-Accept的值, 來肯定兩件事情:

    1. 服務端是否理解WebSocket協議, 若是服務端不理解,那麼它就不會返回正確的Sec-WebSocket-Accept,則創建WebSocket鏈接失敗。
    2. 服務端返回的Response是對於客戶端的這次請求的,而不是以前的緩存。 主要是防止有些緩存服務器返回緩存的Response.
  • 至此,握手過程就完成了,此時的TCP鏈接不會釋放。客戶端和服務端能夠互相通訊了。

websocket如何身份認證?

大致上Websocket的身份認證都是發生在握手階段,經過請求中的內容來認證。一個常見的例子是在url中附帶參數。

new WebSocket("ws://localhost:3000?token=xxxxxxxxxxxxxxxxxxxx");

淘寶的直播彈幕也是用這種方式作的身份認證。另外,websocket是採用http協議握手的,能夠用請求中攜帶cookie的方式作身份認證。

以npm的ws模塊實現爲例,其建立Websocket服務器時提供了verifyClient方法。

const wss = new WebSocket.Server({
  host: SystemConfig.WEBSOCKET_server_host,
  port: SystemConfig.WEBSOCKET_server_port,
  // 驗證token識別身份
  verifyClient: (info) => {
    const token = url.parse(info.req.url, true).query.token
    let user
    console.log('[verifyClient] start validate')
    // 若是token過時會爆TokenExpiredError
    if (token) {
      try {
        user = jwt.verify(token, publicKey)
        console.log(`[verifyClient] user ${user.name} logined`)
      } catch (e) {
        console.log('[verifyClient] token expired')
        return false
      }
    }
    // verify token and parse user object
    if (user) {
      info.req.user = user
      return true
    } else {
      info.req.user = {
        name: `遊客${parseInt(Math.random() * 1000000)}`,
        mail: ''
      }
      return true
    }
  }
})

相關的ws源碼位於ws/websocket-server

// ...
  if (this.options.verifyClient) {
    const info = {
      origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
      secure: !!(req.connection.authorized || req.connection.encrypted),
      req
    };

    if (this.options.verifyClient.length === 2) {
      this.options.verifyClient(info, (verified, code, message) => {
        if (!verified) return abortHandshake(socket, code || 401, message);
        this.completeUpgrade(extensions, req, socket, head, cb);
      });
      return;
    }

    if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
  }
  this.completeUpgrade(extensions, req, socket, head, cb);
}

websocket如何斷開重連?

websocket如何處理高併發?

參考:深刻淺出Websocket(一)Websocket協議

相關文章
相關標籤/搜索