WebTransport 是一种 API,可提供低延迟、双向的客户端-服务器消息传递。详细了解其使用场景,以及如何针对未来的实现提供反馈。
背景
什么是 WebTransport?
WebTransport 是一种 Web API,它使用 HTTP/3 协议作为双向传输。它旨在实现 Web 客户端与 HTTP/3 服务器之间的双向通信。它支持通过其数据报 API 不可靠地发送数据,也支持通过其流 API 可靠地发送数据。
数据报非常适合发送和接收不需要强交付保证的数据。单个数据包的大小受底层连接的最大传输单元 (MTU) 的限制,并且可能成功传输,也可能无法成功传输;即使成功传输,到达顺序也可能不确定。这些特性使得数据报 API 非常适合低延迟、尽力而为的数据传输。您可以将数据报视为用户数据报协议 (UDP) 消息,但经过加密并受拥塞控制。
相比之下,流式传输 API 可提供可靠的有序数据传输。它们非常适合需要发送或接收一个或多个有序数据流的场景。使用多个 WebTransport 流类似于建立多个 TCP 连接,但由于 HTTP/3 在底层使用更轻量级的 QUIC 协议,因此可以打开和关闭这些流,而不会产生过多的开销。
使用场景
以下列举了一些开发者可能会使用 WebTransport 的方式。
- 以最小延迟时间通过小型、不可靠的无序消息定期向服务器发送游戏状态。
- 接收从服务器推送的媒体流,延迟时间最短,不受其他数据流的影响。
- 在网页打开时接收服务器推送的通知。
我们很想详细了解您计划如何使用 WebTransport。
浏览器支持
与所有未获得浏览器普遍支持的功能一样,通过功能检测进行防御性编码是一种最佳实践。
当前状态
步骤 | 状态 |
---|---|
1. 创建说明 | 完成 |
2. 创建规范的初始草稿 | 完成 |
3. 收集反馈并迭代设计 | 完成 |
4. 源试用 | 完成 |
5. 发布 | Chromium 97 |
WebTransport 与其他技术的关系
WebTransport 是否会取代 WebSocket?
或许可以。在某些使用情形下,WebSockets 或 WebTransport 可能是有效的通信协议。
WebSocket 通信是围绕单个可靠的有序消息流建模的,这对于某些类型的通信需求来说是合适的。如果您需要这些特性,WebTransport 的 Streams API 也可以提供。相比之下,WebTransport 的数据报 API 提供低延迟的传送,但不保证可靠性或顺序,因此不能直接替代 WebSocket。
通过数据报 API 或多个并发的 Streams API 实例使用 WebTransport,意味着您不必担心 head-of-line blocking,而这可能是 WebSockets 的一个问题。此外,在建立新连接时,由于底层 QUIC 握手比启动基于 TLS 的 TCP 更快,因此可以提高性能。
WebTransport 属于新的规范草稿,因此目前围绕客户端和服务器库的 WebSocket 生态系统更加强大。如果您需要一种能够与常见服务器设置“开箱即用”且具有广泛 Web 客户端支持的技术,那么 WebSocket 目前是更好的选择。
WebTransport 与 UDP 套接字 API 是否相同?
否。WebTransport 不是 UDP 套接字 API。虽然 WebTransport 使用 HTTP/3,而 HTTP/3 又在“底层”使用 UDP,但 WebTransport 在加密和拥塞控制方面有要求,因此它不仅仅是一个基本的 UDP 套接字 API。
WebTransport 是否是 WebRTC 数据通道的替代方案?
可以,对于客户端-服务器连接。WebTransport 与 WebRTC 数据通道有许多相同的属性,但底层协议不同。
一般来说,运行与 HTTP/3 兼容的服务器所需的设置和配置比维护 WebRTC 服务器要少,后者需要了解多种协议(ICE、DTLS 和 SCTP)才能获得可用的传输。WebRTC 涉及许多可能会导致客户端/服务器协商失败的活动部件。
WebTransport API 在设计时考虑到了 Web 开发者的使用情形,与使用 WebRTC 的数据通道接口相比,它更像是编写现代 Web 平台代码。与 WebRTC 不同,WebTransport 在 Web Worker 中受支持,这使您可以独立于给定的 HTML 网页执行客户端-服务器通信。由于 WebTransport 公开了符合 Streams 标准的接口,因此它支持围绕反压力的优化。
不过,如果您已经拥有一个运行正常的 WebRTC 客户端/服务器设置,并且对此感到满意,那么改用 WebTransport 可能不会带来太多优势。
试试看
试验 WebTransport 的最佳方式是启动兼容的 HTTP/3 服务器。然后,您可以将此网页与基本 JavaScript 客户端搭配使用,以尝试客户端/服务器通信。
此外,还有一个由社区维护的回显服务器,网址为 webtransport.day。
使用此 API
WebTransport 基于现代 Web 平台原语(例如 Streams API)设计。它高度依赖于 Promise,并且可与 async
和 await
搭配使用。
Chromium 中当前的 WebTransport 实现支持三种不同的流量类型:数据报,以及单向和双向数据流。
连接到服务器
您可以通过创建 WebTransport
实例来连接到 HTTP/3 服务器。网址的架构应为 https
。您需要明确指定端口号。
您应使用 ready
promise 等待连接建立。在设置完成之前,此 promise 不会兑现;如果连接在 QUIC/TLS 阶段失败,此 promise 将被拒绝。
当连接正常关闭时,closed
promise 会兑现;如果关闭是意外的,则会拒绝。
如果服务器因客户端指示错误(例如网址的路径无效)而拒绝连接,则会导致 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.t>hen(() = {
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;
数据报 API
获得已连接到服务器的 WebTransport 实例后,您可以使用该实例发送和接收离散的数据位(称为数据报)。
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
。与数据报 API 不同,这些流是可靠的。不过,每个数据流都是独立的,因此无法保证数据流之间的数据顺序。
WebTransportSendStream
Web 客户端使用 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: ${erro
r}`);
}
同样,使用 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
由服务器发起。对于 Web 客户端,获取 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
promise 来检测流关闭。当底层 HTTP/3 流通过 FIN 位关闭时,在读取所有数据后,closed
promise 会得到兑现。当 HTTP/3 流突然关闭(例如,通过 RESET_STREAM
关闭)时,closed
promise 会被拒绝。
// Assume an active receiveStream
const reader = receiveStream.readable.getReader();
reader.closed.then(() => {
console.log('The receiveStream closed gracefully.');
}).ca>tch(() = {
console.error('The receiveStream closed abrup
tly.');
});
WebTransportBidirectionalStream
WebTransportBidirectionalStream
可以由服务器或客户端创建。
Web 客户端可以使用 WebTransport
实例的 createBidirectionalStream()
方法创建一个,该方法会返回一个针对 WebTransportBidirectionalStream
的 promise。
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 问题加注星标,以便在开发者工具界面有更新时收到通知。
Polyfill
我们提供了一个名为 webtransport-ponyfill-websocket
的 Polyfill(或者更确切地说,是一个提供功能的可用作独立模块的 Ponyfill),它实现了 WebTransport 的部分功能。请仔细阅读项目 README
中的限制,以确定此解决方案是否适用于您的使用情形。
隐私权和安全注意事项
如需权威指导,请参阅规范草稿的相应部分。
反馈
Chrome 团队希望了解您对使用此 API 的想法和体验。
有关 API 设计的反馈
该 API 是否存在不便之处或无法按预期运行?或者,是否缺少实现您的想法所需的要素?
在 Web Transport GitHub 代码库中提交问题,或在现有问题中添加您的想法。
实现存在问题?
您是否发现 Chrome 的实现存在 bug?
请前往 https://new.crbug.com 提交 bug。请尽可能详细地说明问题,并提供简单的重现说明。
打算使用 API?
您的公开支持有助于 Chrome 确定功能优先级,并向其他浏览器供应商展示支持这些功能的重要性。
- 使用
#WebTransport
主题标签向 @ChromiumDev 发送推文,详细说明您在何处以及如何使用该功能。
一般性讨论
对于不属于其他任何类别的常规问题或问题,您可以使用 web-transport-dev Google 群组。
致谢
本文纳入了 WebTransport 说明、规范草稿和相关设计文档中的信息。感谢相关作者提供这一基础。
此博文中的主打图片由 Unsplash 上的 Robin Pierre 拍摄。