應用程式可透過施加回壓,避免淹沒在 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 應用程式。
- 同樣地,錄影和其他應用程式會在瀏覽器中產生大量資料,這些資料都需要上傳至伺服器。有了回壓機制,用戶端就能停止產生資料,而不會在記憶體中累積資料。
目前狀態
步驟 | 狀態 |
---|---|
1. 建立說明 | 完成 |
2. 建立規格初稿 | 進行中 |
3. 收集意見回饋並重複設計 | 進行中 |
4. 來源試用 | 完成 |
5. 啟動 | 尚未開始 |
如何使用 WebSocketStream API
WebSocketStream API 是基於 Promise 的 API,因此在現代 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 連線
WebSocketStream 可使用 AbortController
關閉。因此,請將 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 回報錯誤。請務必盡可能提供詳細資訊,並在「Component」方塊中輸入 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 實作。