運用背壓,防止應用程式因 WebSocket 訊息而變得不堪負荷,或運用背壓來使 WebSocket 伺服器充斥訊息。
背景
WebSocket API 提供 WebSocket 通訊協定的 JavaScript 介面,方便您在使用者瀏覽器和伺服器之間開啟雙向互動式通訊工作階段。 透過這個 API,您可以傳送訊息至伺服器及接收事件導向的回應,而不用輪詢伺服器來回覆。
Streams API
Streams API 可讓 JavaScript 以程式輔助方式存取透過網路接收的資料區塊串流,並視需要加以處理。串流的一個重要概念就是「背壓」。這是單一串流或管道鏈條控管讀取或寫入速度的程序。如果串流本身或管道鏈結中的串流仍處於忙碌狀態,且尚未準備好接受更多區塊,便會透過鏈結反向傳送信號,並視情況減慢傳遞速度。
目前使用的 WebSocket API 問題
無法對收到的訊息套用背壓
使用目前的 WebSocket API 回應訊息時,系統會在 WebSocket.onmessage
中回應訊息;收到伺服器傳來的訊息時,就會呼叫 EventHandler
。
假設您有一個應用程式,需要在收到新訊息時執行大量資料處理作業。您可能會設定類似下方程式的流程,而且由於您 await
了 process()
呼叫的結果,因此應該沒問題,對吧?
// A heavy data crunching operation.
const process = async (data) => {
return new Promise((resolve) => {
window.setTimeout(() => {
console.log('WebSocket message processed:', data);
return resolve('done');
}, 1000);
});
};
webSocket.onmessage = async (event) => {
const data = event.data;
// Await the result of the processing step in the message handler.
await process(data);
};
答錯了!目前的 WebSocket API 問題在於無法套用回壓。當訊息送達的速度比 process()
方法可處理訊息的速度還快時,轉譯程序會透過緩衝處理這些訊息來填滿記憶體,在 CPU 使用率 100% 或兩者皆有時無回應。
為已傳送的郵件套用背壓並非符合人體工學
您可以將回壓套用至已傳送的訊息,但這會涉及輪詢 WebSocket.bufferedAmount
屬性,效率不彰且不符合人體工學。這個唯讀屬性會傳回使用 WebSocket.send()
呼叫排入佇列,但尚未傳送至網路的資料位元組數。所有排隊的資料都已傳送後,這個值會重設為零,但如果您持續呼叫 WebSocket.send()
,這個值會持續增加。
什麼是 WebSocketStream API?
WebSocketStream API 會將串流與 WebSocket API 整合,處理不存在或不符合人體工學的回壓問題。也就是說,您可以「免費」套用回壓,不必額外付費。
WebSocketStream API 的建議用途
以下列舉可使用這個 API 的網站:
- 需要保留互動功能 (尤其是影片和螢幕分享功能) 的高頻寬 WebSocket 應用程式。
- 同樣地,錄影和其他應用程式會在瀏覽器中產生大量資料,而這些資料需要上傳至伺服器。有了回壓機制,用戶端就能停止產生資料,而不會在記憶體中累積資料。
目前狀態
如何使用 WebSocketStream API
WebSocketStream API 是以 Promise 為基礎,因此在現代 JavaScript 世界中,處理這項 API 會顯得相當自然。首先請建構新的 WebSocketStream
,並向其傳送 WebSocket 伺服器的網址。接下來,您等待連線變成 opened
,這會產生 ReadableStream
和/或 WritableStream
。
呼叫 ReadableStream.getReader()
方法後,您最終會取得 ReadableStreamDefaultReader
,您可以從中read()
資料,直到串流完成,也就是傳回 {value: undefined, done: true}
形式的物件為止。
因此,呼叫 WritableStream.getWriter()
方法後,您最終會取得 WritableStreamDefaultWriter
,然後可用於 write()
資料。
const wss = new WebSocketStream(WSS_URL);
const {readable, writable} = await wss.opened;
const reader = readable.getReader();
const writer = writable.getWriter();
while (true) {
const {value, done} = await reader.read();
if (done) {
break;
}
const result = await process(value);
await writer.write(result);
}
背壓
我們承諾會提供背壓功能嗎?
您可以「免費」取得,不需要採取額外步驟。如果 process()
需要額外時間,則系統會在管道就緒後才使用下一個訊息。同樣地,只有在安全無虞的情況下,WritableStreamDefaultWriter.write()
步驟才會繼續執行。
進階範例
WebSocketStream 的第二個引數是選項包,允許日後擴充。唯一選項是 protocols
,運作方式與 WebSocket 建構函式的第二個引數相同:
const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;
所選的 protocol
和潛在 extensions
都屬於透過 WebSocketStream.opened
承諾提供的字典。與即時連線有關的所有資訊均由這個承諾提供,因為如果連線失敗,這並不相關。
const {readable, writable, protocol, extensions} = await chatWSS.opened;
關閉的 WebSocketStream 連線相關資訊
從 WebSocket API 中的 WebSocket.onclose
和 WebSocket.onerror
事件取得資訊,現在可透過 WebSocketStream.closed
承諾取得。在發生不潔關閉事件時,承諾會遭到拒絕,否則會解析為伺服器傳送的程式碼和原因。
CloseEvent
狀態碼清單會說明所有可能的狀態碼及其含義。
const {code, reason} = await chatWSS.closed;
關閉 WebSocketStream 連線
您可以使用 AbortController
關閉 WebSocketStream。因此,請將 AbortSignal
傳遞至 WebSocketStream
建構函式。
const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);
您也可以使用 WebSocketStream.close()
方法,但這項方法的主要用途是允許您指定傳送至伺服器的代碼和原因。
wss.close({code: 4000, reason: 'Game over'});
漸進增強與互通性
Chrome 目前是唯一實作 WebSocketStream API 的瀏覽器。為了與傳統版 WebSocket API 互通,我們不允許對收到的訊息套用背壓。您可以對已傳送的訊息套用背壓,但這涉及輪詢 WebSocket.bufferedAmount
屬性,這效率低落且沒有人因工程。
特徵偵測
如要檢查是否支援 WebSocketStream API,請使用:
if ('WebSocketStream' in window) {
// `WebSocketStream` is supported!
}
示範
在支援的瀏覽器上,您可以在嵌入式 iframe 中查看 WebSocketStream API 的實際運作情形,也可以直接在 Glitch 上查看。
意見回饋
Chrome 團隊希望瞭解你使用 WebSocketStream API 的體驗。
請說明 API 設計
API 是否有任何部分無法正常運作?或者,您是否缺少實作想法所需的方法或屬性?您對安全性模型有疑問或意見嗎?在對應的 GitHub 存放區上提出規格問題,或將您的想法新增至現有問題。
回報導入問題
你是否發現 Chrome 實作項目有錯誤?或者實作方式與規格不同?前往 new.crbug.com 回報錯誤。請盡可能提供最多細節、重製操作說明,並在「Components」(元件) 方塊中輸入 Blink>Network>WebSockets
。Glitch 則是分享可輕鬆快速重現保護殼的工具。
顯示對 API 的支援
您是否打算使用 WebSocketStream API?你的公開支持有助於 Chrome 團隊決定功能的優先順序,並向其他瀏覽器供應商顯示支援這些功能的重要性。
使用主題標記 #WebSocketStream
發送推文給 @ChromiumDev,告訴我們你在何處使用這項功能,以及使用方式。
實用連結
- 公開說明
- WebSocketStream API 示範 | WebSocketStream API 示範來源
- 追蹤錯誤
- ChromeStatus.com 項目
- Blink 元件:
Blink>Network>WebSockets
特別銘謝
WebSocketStream API 由 Adam Rice 和 Yutaka Hirano 實作。