淺淺的聊一下 WebSocket

貪婪的君子發表於2022-03-15

第一次看到 ws://wss:// 時候,感覺好高階啊,還有這種協議。

Websocket 歷史

WebSocket是在2008年6月誕生的1。經由IEFT標準化後,2009年chrome 4第一個提供了該標準支援,並預設啟用。於2011年由IEFT標準化為RFC 6455

現在的瀏覽器均已支援該標準。

Websocket 出現的背景

思考一下我們經常遇到的一種需求場景,要求在某個網頁下,網頁的內容可以實時更新。

這種情況下,最大眾化的方式就是輪詢介面了,即通過定時器,定時請求介面,獲取到最新的資訊後,將內容更新到頁面中,如下:

setInterval(() => {
  queryAPI().then(() => update());
}, 1000);

但是我們知道,這種定時器的延時並不是很精確,而且加上介面的請求時延,實際時間可能不止程式碼中所預先設定的時間長度,所以這種實時更新是偽實時更新。

除此之外,還有一點可能會經常遇到,即,我們更新資訊並總是要更新整個頁面上所有可以看到的資訊,我們更關注一些經常變化的資訊,比如狀態,狀態的資訊可能大小隻有幾個位元組,但是我們輪詢介面拿到的資訊卻是這個頁面的所有資訊,大小自然不只幾個位元組,但是除狀態以外的資訊都可以視作是冗餘的。

狀態

我們實際只需要一個欄位,而且即使後端提供只返回狀態的介面,但實際在一個請求中還要計算ip報文頭的大小,依舊是很佔用頻寬的。

輪詢這種解決方案目前依舊是非常流行,最新的輪詢技術是Comet,這種技術雖然可以實現雙向通訊,但仍然需要反覆發出請求。而且在Comet中普遍採用的HTTP長連線也會消耗伺服器資源2。

Websocket通訊模式

瞭解網路的都知道,資料傳輸分為單工、半雙工、全雙工三種工作模式。

Websocket是基於TCP的,使用全雙工通訊模式的協議,他使得客戶端和服務端之間的資料交換變得更簡單。而且,作為一個工作在全雙工模式下的協議,服務端可以在建立連線後隨時向客戶端推送訊息。

由於協議是基於TCP的,所以websocket也是需要建連和關閉連線的,但要注意的是,一般在websocket的握手通常指的是:客戶端傳送一個http請求到服務端,服務端響應後標誌這個連結建立起來。而不是指tcp的三次握手。

另外在RFC 6455 1.1節「Background」中介紹:WebSocket通過HTTP埠的80和443進行工作,並支援HTTP代理和中介。

原文:it is designed to work over HTTP ports 80 and 443 as well as to support HTTP proxies and intermediaries

為了實現和HTTP的相容性,WebSocket握手使用HTTP的Upgrade頭將HTTP協議轉換成Websocket協議。

作為一種協議,websocket自然也是有其用於協議控制的頭部資訊的,但是相對於HTTP請求每次都要帶上完整的頭部資訊,傳輸資料時,websocket資料包的頭部資訊就相對較小,從而降低了控制開銷。

相對於前文所提到的輪詢介面,websocket可以做到服務端直接向客戶端傳輸資料,省去了客戶端發起請求的步驟,同時沒有間隔時間,只要服務端內容變化,就可以告知客戶端,實時性上有了很大的提高。

Websocket 使用

WebSocket使用十分簡單,只需要關注以下API:

  1. WebSocket(url[, protocol]) 建構函式

使用 new WebSocket(xxx) 建立物件時,會同時建立與伺服器的連線

ws

  1. WebSocket.onopen , WebSocket.onclose

分別對應連線成功、失敗時的回撥,這裡可以做一些初始化、銷燬的工作

  1. WebSocket.onmessage

實際處理資料是用的該函式

在資料處理完成後,需要移除回撥函式,不然可能會影響到其他地方的處理

const ws = new WebSocket('ws://sdf.com');
function handleData(evt) {
  // handle server data.
}
ws.onmessage = handleData;
ws.addEventListener('message', handleData);
  1. WebSocket.send

主動向服務端傳送訊息,可以通過send和onmessage進行資料互動,如:

ws.send('list');
​
ws.onmessage = evt => {
  const data = evt.data;
  if (data === 'hello') {
    console.log('world');
    return ;
  }
  try {
    const obj = JSON.parse(data);
    switch (obj.type) {
      case 'list':
        // do something.
    }
  } catch (ex) {}
}
  1. WebSocket.close

關閉連線

Node服務端的實現,這個就參考相關的庫吧,比較複雜。

衍生知識

http協議至今,主要經歷了三個版本。

  • http1.0 短連線,單工通訊

    • http/1.0預設的模型是短連線,每個HTTP請求都由他自己獨立完成,下圖左1,可以看到每一個http請求都對應了一個建立連線關閉連線的階段,每一個請求都有TCP握手和揮手的階段。
    • 在這個模型下,想要做到實時更新頁面資料,只能考慮輪詢。
  • http1.1 支援長連結,半雙工通訊

    • 1.0之後的版本,1.1會讓某個連線保持一定的時間,在這段時間裡重複傳送一系列請求(下圖左2),就是保活。
  • http2.0 支援多路複用,全雙工通訊

Compares the performance of the three HTTP/1.x connection models: short-lived connections, persistent connections, and HTTP pipelining.

參考文獻

[1]  [whatwg] TCPConnection feedback

[2]  wiki

[3]  RFC 6455

[4]  Websocket教程

[5]  HTTP1.x連線管理

相關文章