編寫 WebSocket 客戶端應用程式

在本指南中,我們將介紹一個基於 WebSocket 的 ping 應用程式的實現。在此應用程式中,客戶端每秒向伺服器傳送一個“ping”訊息,伺服器響應一個“pong”訊息。客戶端偵聽“pong”訊息並記錄它們,同時跟蹤已經進行了多少條訊息交換。

雖然這是一個非常簡單的應用程式,但它涵蓋了編寫 WebSocket 客戶端所涉及的基本要點。

您可以在 https://github.com/mdn/dom-examples/tree/main/websockets 找到完整的示例。伺服器端是用 Deno 編寫的,因此如果您想在本地執行示例,需要先安裝它。

建立 WebSocket 物件

要使用 WebSocket 協議進行通訊,您需要建立一個 WebSocket 物件。建立此物件後,它將立即開始嘗試連線到指定的伺服器。

js
const wsUri = "ws://127.0.0.1/";
const websocket = new WebSocket(wsUri);

WebSocket 建構函式接受一個必需引數——要連線的 WebSocket 伺服器的 URL。在本例中,由於我們將在本地執行伺服器,因此我們使用 localhost 地址。

注意: 在此示例中,我們使用的是 ws 協議進行連線,因為我們在示例中連線到 localhost。在實際應用程式中,網頁應使用 HTTPS 提供服務,WebSocket 連線應使用 wss 作為協議。

建構函式還接受另一個可選引數 protocols,該引數允許單個伺服器實現多個子協議。我們在示例中不使用此功能。

如果目標不允許訪問,建構函式將丟擲 SecurityError。當您嘗試使用不安全的連線時,可能會發生這種情況(大多數 使用者代理 現在都要求所有 WebSocket 連線都使用安全連結,除非它們位於同一裝置上或可能在同一網路上)。

偵聽 open 事件

建立 WebSocket 例項會啟動與伺服器建立連線的過程。一旦連線建立,將觸發 open 事件,之後該套接字就可以傳輸資料了。

在下面的示例程式碼中,當觸發 open 事件時,我們使用 Window.setInterval() API 每秒向伺服器傳送一個“ping”訊息。

js
websocket.addEventListener("open", () => {
  log("CONNECTED");
  pingInterval = setInterval(() => {
    log(`SENT: ping: ${counter}`);
    websocket.send("ping");
  }, 1000);
});

偵聽錯誤

如果在建立連線時或建立連線後的任何時間發生錯誤,將觸發 error 事件。

我們的應用程式在發生錯誤時不會執行任何特殊操作,但我們會記錄錯誤。

js
websocket.addEventListener("error", (e) => {
  log(`ERROR`);
});

發生錯誤後,連線將關閉,並將觸發 close 事件。

傳送訊息

我們已經看到,一旦連線建立,我們就可以使用 send() 方法將訊息傳送到伺服器。

js
websocket.addEventListener("open", () => {
  log("CONNECTED");
  pingInterval = setInterval(() => {
    log(`SENT: ping: ${counter}`);
    websocket.send("ping");
  }, 1000);
});

在我們的示例中,我們傳送文字,但您也可以將二進位制資料作為 BlobArrayBufferTypedArrayDataView 傳送。

一種常見的方法是使用 JSON 將序列化的 JavaScript 物件作為文字傳送。例如,我們的客戶端可以傳送一個包含已交換訊息數量的序列化物件,而不是僅僅傳送“ping”文字訊息。

js
const message = {
  iteration: counter,
  content: "ping",
};
websocket.send(JSON.stringify(message));

send() 方法是非同步的:它在返回給呼叫者之前不會等待資料傳輸完成。它只是將資料新增到其內部緩衝區並開始傳輸過程。WebSocket.bufferedAmount 屬性表示尚未傳輸的位元組數。請注意,WebSocket 協議使用 UTF-8 對文字進行編碼,因此 bufferedAmount 是根據任何已緩衝文字資料的 UTF-8 編碼計算的。

接收訊息

要從伺服器接收訊息,我們偵聽 message 事件。

我們的訊息事件處理程式會記錄接收到的訊息,並增加已發生的已交換訊息數量的計數。

js
websocket.addEventListener("message", (e) => {
  log(`RECEIVED: ${e.data}: ${counter}`);
  counter++;
});

伺服器還可以傳送二進位制資料,這些資料將作為 BlobArrayBuffer 提供給客戶端,具體取決於 WebSocket.binaryType 屬性的值。

正如我們在傳送訊息時所見,伺服器也可以傳送 JSON 字串,客戶端可以將其解析為物件。

js
websocket.addEventListener("message", (e) => {
  const message = JSON.parse(e.data);
  log(`RECEIVED: ${message.iteration}: ${message.content}`);
  counter++;
});

處理斷開連線

當連線關閉時(無論是客戶端還是伺服器關閉它,還是因為發生了錯誤),將觸發 close 事件。

我們的應用程式偵聽 close 事件,並在觸發時清理 interval 計時器。

js
websocket.addEventListener("close", () => {
  log("DISCONNECTED");
  clearInterval(pingInterval);
});

使用 bfcache

後退/前進快取(或 bfcache)可以更快地在使用者最近訪問的頁面之間進行後退和前進導航。它透過儲存頁面的完整快照(包括 JavaScript 堆)來實現這一點。

當頁面被新增到 bfcache 或從 bfcache 恢復時,瀏覽器會暫停然後恢復 JavaScript 執行。這意味著,根據頁面正在做什麼,瀏覽器不一定總能安全地使用 bfcache。如果瀏覽器確定不安全,則不會將頁面新增到 bfcache,使用者也無法獲得它帶來的效能優勢。

不同的瀏覽器使用不同的標準將頁面新增到 bfcache,並且開啟的 WebSocket 連線可能會阻止瀏覽器將您的頁面新增到 bfcache。這意味著,當用戶不再使用您的頁面時,關閉連線是一種好的做法。用於此目的的最佳事件是 pagehide 事件。

我們在示例應用程式中執行此操作。

js
window.addEventListener("pagehide", () => {
  if (websocket) {
    log("CLOSING");
    websocket.close();
    websocket = null;
    window.clearInterval(pingInterval);
  }
});

相反,透過偵聽 pageshow 事件,您可以在頁面從 bfcache 恢復時無縫地重新啟動連線。由於 pageshow 事件在頁面載入時也會觸發,因此它也可以用於在頁面首次載入時啟動 WebSocket 連線。

js
let websocket = null;

window.addEventListener("pageshow", () => {
  log("OPENING");

  websocket = new WebSocket(wsUri);

  websocket.addEventListener("open", () => {
    log("CONNECTED");
    pingInterval = setInterval(() => {
      log(`SENT: ping: ${counter}`);
      websocket.send("ping");
    }, 1000);
  });

  websocket.addEventListener("close", () => {
    log("DISCONNECTED");
    clearInterval(pingInterval);
  });

  websocket.addEventListener("message", (e) => {
    log(`RECEIVED: ${e.data}: ${counter}`);
    counter++;
  });

  websocket.addEventListener("error", (e) => {
    log(`ERROR: ${e.data}`);
  });
});

如果您執行我們的示例,請嘗試導航到另一個頁面,然後再返回示例。在 Chrome 中,您應該會看到示例重新啟動了連線,並保持了其原始上下文:例如,它會記住已交換訊息的計數。

有關 bfcache 相容性和 WebSockets API 的更多背景資訊,請參閱 web.dev 關於 bfcache 的文章

在支援它的瀏覽器上,您可以使用 Performance API 的 notRestoredReasons 屬性來獲取頁面未新增到 bfcache 的原因。

安全注意事項

不應在混合內容環境中(即,不應從使用 HTTPS 載入的頁面開啟非安全 WebSocket 連線,反之亦然)使用 WebSockets。大多數瀏覽器現在只允許安全的 WebSocket 連線,並且不再支援在不安全的環境中使用它們。