WebTransport 是一个 API,提供低延迟、双向的客户端-服务器消息传递。详细了解其用例,以及如何就未来的实现方式提供反馈。
背景
什么是 WebTransport?
WebTransport 是一个 Web API,它使用 HTTP/3 协议作为双向传输协议。它适用于 Web 客户端与 HTTP/3 服务器之间的双向通信。它支持通过其数据报 API 不可靠地发送数据,以及通过其流 API 可靠地发送数据。
数据报非常适合发送和接收不需要强有力的传送保证的数据。单个数据包的大小受底层连接的最大传输单元 (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 生态系统目前更加稳健。如果您需要的是可与常见服务器设置搭配使用且广泛受 Web 客户端支持的“开箱即用”解决方案,那么目前 WebSocket 是更好的选择。
WebTransport 是否与 UDP Socket API 相同?
不可以。WebTransport 不是 UDP Socket API。虽然 WebTransport 使用 HTTP/3,而 HTTP/3 反过来又“在后台”使用 UDP,但 WebTransport 对加密和拥塞控制有要求,这使其不只是一个基本的 UDP Socket API。
WebTransport 是否是 WebRTC 数据通道的替代方案?
是,对于客户端-服务器连接。WebTransport 与 WebRTC 数据通道有许多相同的属性,但底层协议不同。
通常,与维护 WebRTC 服务器相比,运行与 HTTP/3 兼容的服务器需要更少的设置和配置,因为维护 WebRTC 服务器需要了解多种协议(ICE、DTLS 和 SCTP),才能获得可用的传输。WebRTC 涉及更多可导致客户端/服务器协商失败的移动部分。
WebTransport API 的设计充分考虑了 Web 开发者的用例,使用起来应该更像是在编写现代 Web 平台代码,而不是使用 WebRTC 的数据通道接口。与 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 阶段失败,则会被拒绝。
当连接正常关闭时,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.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;
数据报 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
。与数据报 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: ${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
由服务器发起。对于 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
承诺会被拒绝。
// 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
可以由服务器或客户端创建。
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 的 DevTools 目前不支持 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 提供。