WebTransport 是一種 API,可提供低延遲、雙向的用戶端-伺服器訊息傳遞服務。進一步瞭解這項功能的用途,以及如何針對未來的導入方式提供意見回饋。
背景
什麼是 WebTransport?
WebTransport 是一種網路 API,會使用 HTTP/3 通訊協定做為雙向傳輸。這項通訊協定適用於網路用戶端和 HTTP/3 伺服器之間的雙向通訊。它支援透過資料包 API 和串流 API 傳送資料,前者不穩定,後者則可靠。
Datagram 非常適合用於傳送和接收不需要強大傳送保證的資料。個別資料封包的大小受限於基礎連線的最大傳輸單位 (MTU),可能會或可能不會順利傳送,如果傳送成功,可能會以任意順序抵達。這些特性讓資料包 API 非常適合用於低延遲的盡力資料傳輸。您可以將資料元視為用戶資料元協定 (UDP) 訊息,但經過加密和擁塞控制。
相較之下,串流 API 提供可靠的資料傳輸順序。在需要傳送或接收一或多個有序資料串流的情況下,非常適合使用這類函式。使用多個 WebTransport 串流,就好比建立多個 TCP 連線,但由於 HTTP/3 會在底層使用較輕量化的 QUIC 通訊協定,因此可以開啟和關閉這些連線,而不會造成太多額外負擔。
用途
以下列出開發人員可能會使用 WebTransport 的幾種方式。
- 透過小型、不可靠且不按順序傳送的訊息,以定期間隔傳送遊戲狀態,並將延遲時間降到最低。
- 接收伺服器推送的媒體串流,延遲時間最短,不受其他資料串流影響。
- 在開啟網頁時,接收伺服器推送的通知。
我們想進一步瞭解您打算如何使用 WebTransport。
瀏覽器支援
對於所有不受通用瀏覽器支援的功能,透過功能偵測進行防禦式編碼是最佳做法。
目前狀態
WebTransport 與其他技術的關係
WebTransport 是否可取代 WebSocket?
不一定。在某些用途中,WebSockets 或 WebTransport 可能都是可用的通訊協定。
WebSocket 通訊是以單一可靠的訊息串流為基礎,並按照順序排列,這類通訊方式可滿足某些通訊需求。如果您需要這些特性,WebTransport 的串流 API 也可以提供。相較之下,WebTransport 的資料包 API 可提供低延遲的傳送服務,但無法保證可靠性或順序,因此無法直接取代 WebSocket。
透過資料包 API 或多個並行 Streams API 例項使用 WebTransport,表示您不必擔心 頭阻塞問題,這可能是 WebSocket 的問題。此外,建立新連線時,由於基礎的 QUIC 握手比透過 TLS 啟動 TCP 更快,因此可提升效能。
WebTransport 是新規格草稿的一部分,因此目前的用戶端和伺服器程式庫 WebSocket 生態系統更加健全。如果您需要「開箱即用」的解決方案,且可搭配常見的伺服器設定使用,並支援廣泛的網路用戶端,WebSockets 是目前較佳的選擇。
WebTransport 與 UDP Socket API 是否相同?
否。WebTransport 並非 UDP Socket API。雖然 WebTransport 使用 HTTP/3,而 HTTP/3 又會「在幕後」使用 UDP,但 WebTransport 有加密和壅塞控制方面的需求,因此不只是基本的 UDP 網路介面 API。
WebTransport 是否是 WebRTC 資料管道的替代方案?
是的,用戶端與伺服器連線。WebTransport 與 WebRTC 資料管道共用許多相同的屬性,但基礎通訊協定不同。
一般來說,執行與 HTTP/3 相容的伺服器所需的設定和配置,比維護 WebRTC 伺服器所需的設定和配置少,因為後者需要瞭解多個通訊協定 (ICE、DTLS 和 SCTP),才能取得可運作的傳輸機制。WebRTC 包含許多可導致用戶端/伺服器協商失敗的動態元素。
WebTransport API 的設計著重於網頁程式開發人員的使用情境,因此使用者應該會覺得這更像是編寫新式網頁平台程式碼,而不是使用 WebRTC 的資料通道介面。與 WebRTC 不同,WebTransport 支援 Web Workers,可讓您不必使用特定 HTML 網頁,即可執行用戶端-伺服器通訊。由於 WebTransport 會公開符合 Streams 的介面,因此可支援回壓方面的最佳化。
不過,如果您已經有運作良好的 WebRTC 用戶端/伺服器設定,轉換至 WebTransport 可能不會帶來太多優勢。
立即試用
如要嘗試 WebTransport,最好的方法是啟動相容的 HTTP/3 伺服器。接著,您可以使用這個頁面搭配基本 JavaScript 用戶端,試用用戶端/伺服器通訊。
此外,您也可以前往 webtransport.day 使用社群維護的回音伺服器。
使用 API
WebTransport 是根據新型網站平台基本元素 (例如 Streams API) 設計而成。它極度仰賴承諾,且可與 async
和 await
搭配使用。
Chromium 目前的 WebTransport 實作支援三種不同類型的流量:資料包,以及單向和雙向串流。
連線至伺服器
您可以建立 WebTransport
執行個體,連線至 HTTP/3 伺服器。網址的通訊協定應為 https
。您必須明確指定通訊埠編號。
您應使用 ready
Promise 等待連線建立。只有在設定完成後,這個應許才會履行,如果在 QUIC/TLS 階段連線失敗,則會遭到拒絕。
當連線正常關閉時,closed
應許會完成,如果關閉方式非預期,則會遭到拒絕。
如果伺服器因用戶端指示錯誤 (例如網址路徑無效) 而拒絕連線,就會導致 closed
拒絕,而 ready
仍未解決。
const url = 'https://example.com:4999/foo/bar';
const transport = new WebTransport(url);
// Optionally, set up functions to respond to
// the connection closing:
transport.closed.then(() => {
console.log(`The HTTP/3 connection to ${url} closed gracefully.`);
}).catch((error) => {
console.error(`The HTTP/3 connection to ${url} closed due to ${error}.`);
});
// Once .ready fulfills, the connection can be used.
await transport.ready;
Datagram API
有了連線至伺服器的 WebTransport 例項後,您就可以使用該例項傳送及接收個別位元組的資料,也就是所謂的 datagrams。
writeable
getter 會傳回 WritableStream
,網路用戶端可使用該值將資料傳送至伺服器。readable
getter 會傳回 ReadableStream
,讓您監聽伺服器的資料。這兩個串流本質上都不可靠,因此伺服器可能不會收到您寫入的資料,反之亦然。
這兩種串流都會使用 Uint8Array
例項進行資料傳輸。
// Send two datagrams to the server.
const writer = transport.datagrams.writable.getWriter();
const data1 = new Uint8Array([65, 66, 67]);
const data2 = new Uint8Array([68, 69, 70]);
writer.write(data1);
writer.write(data2);
// Read datagrams from the server.
const reader = transport.datagrams.readable.getReader();
while (true) {
const {value, done} = await reader.read();
if (done) {
break;
}
// value is a Uint8Array.
console.log(value);
}
Streams API
連線至伺服器後,您也可以使用 WebTransport 透過其 Streams API 傳送及接收資料。
所有串流的每個區塊都是 Uint8Array
。與 Datagram API 不同,這些串流可靠。不過,每個串流都是獨立的,因此無法保證串流間的資料順序。
WebTransportSendStream
網路用戶端會使用 WebTransport
例項的 createUnidirectionalStream()
方法建立 WebTransportSendStream
,並傳回 WebTransportSendStream
的 promise。
使用 WritableStreamDefaultWriter
的 close()
方法關閉相關聯的 HTTP/3 連線。瀏覽器會在實際關閉相關聯連線之前,嘗試傳送所有待處理資料。
// Send two Uint8Arrays to the server.
const stream = await transport.createUnidirectionalStream();
const writer = stream.writable.getWriter();
const data1 = new Uint8Array([65, 66, 67]);
const data2 = new Uint8Array([68, 69, 70]);
writer.write(data1);
writer.write(data2);
try {
await writer.close();
console.log('All data has been sent.');
} catch (error) {
console.error(`An error occurred: ${error}`);
}
同樣地,請使用 WritableStreamDefaultWriter
的 abort()
方法,將 RESET\_STREAM
傳送至伺服器。使用 abort()
時,瀏覽器可能會捨棄所有尚未傳送的待處理資料。
const ws = await transport.createUnidirectionalStream();
const writer = ws.getWriter();
writer.write(...);
writer.write(...);
await writer.abort();
// Not all the data may have been written.
WebTransportReceiveStream
伺服器會啟動 WebTransportReceiveStream
。取得 WebTransportReceiveStream
的程序分為兩個步驟,首先,它會呼叫 WebTransport
例項的 incomingUnidirectionalStreams
屬性,該屬性會傳回 ReadableStream
。ReadableStream
的每個區塊都是 WebTransportReceiveStream
,可用來讀取伺服器傳送的 Uint8Array
例項。
async function readFrom(receiveStream) {
const reader = receiveStream.readable.getReader();
while (true) {
const {done, value} = await reader.read();
if (done) {
break;
}
// value is a Uint8Array
console.log(value);
}
}
const rs = transport.incomingUnidirectionalStreams;
const reader = rs.getReader();
while (true) {
const {done, value} = await reader.read();
if (done) {
break;
}
// value is an instance of WebTransportReceiveStream
await readFrom(value);
}
您可以使用 ReadableStreamDefaultReader
的 closed
應許,偵測串流關閉。當底層 HTTP/3 連線使用 FIN 位元關閉時,系統會在讀取所有資料後履行 closed
應許。如果 HTTP/3 連線突然關閉 (例如由 RESET\_STREAM
關閉),closed
應許就會遭到拒絕。
// Assume an active receiveStream
const reader = receiveStream.readable.getReader();
reader.closed.then(() => {
console.log('The receiveStream closed gracefully.');
}).catch(() => {
console.error('The receiveStream closed abruptly.');
});
WebTransportBidirectionalStream
WebTransportBidirectionalStream
可能由伺服器或用戶端建立。
網路用戶端可以使用 WebTransport
例項的 createBidirectionalStream()
方法建立一個,該方法會傳回 WebTransportBidirectionalStream
的承諾。
const stream = await transport.createBidirectionalStream();
// stream is a WebTransportBidirectionalStream
// stream.readable is a ReadableStream
// stream.writable is a WritableStream
您可以監聽伺服器使用 WebTransport
例項的 incomingBidirectionalStreams
屬性建立的 WebTransportBidirectionalStream
,該屬性會傳回 ReadableStream
。ReadableStream
的每個部分都是 WebTransportBidirectionalStream
。
const rs = transport.incomingBidirectionalStreams;
const reader = rs.getReader();
while (true) {
const {done, value} = await reader.read();
if (done) {
break;
}
// value is a WebTransportBidirectionalStream
// value.readable is a ReadableStream
// value.writable is a WritableStream
}
WebTransportBidirectionalStream
只是 WebTransportSendStream
和 WebTransportReceiveStream
的組合。前兩節的範例說明瞭如何使用每個方法。
其他示例
WebTransport 草稿規格包含許多額外的內嵌範例,以及所有方法和屬性的完整文件。
Chrome 開發人員工具中的 WebTransport
很抱歉,Chrome 的開發人員工具目前不支援 WebTransport。你可以「加星」這個 Chrome 問題,以便在開發人員工具介面上收到更新通知。
聚酯纖維
您可以使用名為 webtransport-ponyfill-websocket
的 polyfill (或稱為 ponyfill,可提供可用作獨立模組的功能),實作 WebTransport 的部分功能。請仔細閱讀專案 README
中的限制條件,判斷這個解決方案是否適用於您的用途。
隱私權和安全性考量
如需權威指南,請參閱規格草稿的相應章節。
意見回饋
Chrome 團隊希望聽聽你對這個 API 的想法和使用體驗。
針對 API 設計提供意見回饋
API 是否有任何不便之處,或無法正常運作?或者,您是否缺少實現想法的必要元素?
在 Web Transport GitHub 存放區中回報問題,或在現有問題中加入您的想法。
導入時發生問題?
你是否發現 Chrome 實作項目有錯誤?
請前往 https://new.crbug.com 提交錯誤。請盡可能提供詳細資訊,並附上重現問題的簡單操作說明。
打算使用 API 嗎?
你的公開支持有助於 Chrome 優先處理功能,並向其他瀏覽器供應商顯示支援這些功能的重要性。
- 使用標籤
#WebTransport
傳送推文給 @ChromiumDev,並詳細說明你使用該功能的位置和方式。
一般討論
如果您有一般問題或問題不屬於其他類別,可以使用 web-transport-dev Google 群組。
特別銘謝
本文參考了 WebTransport 說明文件、規格草稿和相關設計文件中的資訊。感謝各位作者提供基礎資訊。
這篇文章的主頁橫幅圖片出自 Unsplash 上的 Robin Pierre。