使用 WebTransport

WebTransport 是一个 API,提供低延迟的双向客户端-服务器消息传递。详细了解其用例,以及如何就未来实现的未来实施提供反馈。

背景

什么是 WebTransport?

WebTransport 是一个使用 HTTP/3 协议作为双向传输的 Web API。它适用于 Web 客户端与 HTTP/3 服务器之间的双向通信。它既支持通过数据报 API 以不可靠的方式发送数据,也支持通过数据流 API 可靠地发送数据。

数据报非常适合用于发送和接收不需要强有力传输保证的数据。单个数据数据包的大小受到底层连接的最大传输单元 (MTU) 的限制,不一定成功传输,如果传输,它们可能会以任意顺序到达。这些特性使数据报 API 非常适合低延迟、尽力而为的数据传输。您可以将数据报视为用户数据报协议 (UDP) 消息,但会进行加密和拥塞。

相比之下,流 API 提供可靠、有序的数据传输。它们非常适合您需要发送或接收一个或多个有序数据流的场景。使用多个 WebTransport 流类似于建立多个 TCP 连接,但由于 HTTP/3 在后台使用轻量级 QUIC 协议,因此可以打开和关闭它们,而不会产生太多开销。

使用场景

此处列出了开发者使用 WebTransport 的可能方法。

  • 通过不可靠、无序的小型消息,以最短延迟时间向服务器发送游戏状态。
  • 以极低的延迟接收服务器推送的媒体流,与其他数据流无关。
  • 在网页打开时接收服务器推送的通知。

我们非常希望能够详细了解您计划如何使用 WebTransport。

浏览器支持

浏览器支持

  • Chrome:97。 <ph type="x-smartling-placeholder">
  • Edge:97。 <ph type="x-smartling-placeholder">
  • Firefox:114。 <ph type="x-smartling-placeholder">
  • Safari:不支持。 <ph type="x-smartling-placeholder">

来源

与不具有通用浏览器支持的所有功能一样,最佳做法是通过功能检测进行防御性编码。

当前状态

步骤 状态
1. 创建铺垫消息 完成
2. 创建规范的初始草稿 完成
3. 收集反馈并迭代设计 完成
4. 源试用 完成
5. 发布 Chromium 97

WebTransport 与其他技术的关系

WebTransport 可以替代 WebSocket 吗?

或许可以。在某些情况下,WebSockets 或 WebTransport 可能是可以使用的有效通信协议。

WebSocket 通信围绕单个可靠的有序消息流建模,这可以满足某些类型的通信需求。如果您需要这些特性,WebTransport 的流 API 也可以提供这些特性。相比之下,WebTransport 的数据报 API 提供低延迟的传送,无法保证可靠性或排序,因此它们不能直接替代 WebSocket。

使用 WebTransport、通过数据报 API 或多个并发 Streams API 实例意味着您不必担心队头阻塞(这是 WebSocket 可能造成的问题)。此外,在建立新连接时有性能优势,因为底层 QUIC 握手比通过 TLS 启动 TCP 速度更快。

WebTransport 是新规范草案的一部分,因此围绕客户端库和服务器库的 WebSocket 生态系统目前更加强大。如果您需要“开箱即用”的功能WebSockets 具有常见的服务器设置,再加上广泛的 Web 客户端支持,是当今更好的选择。

WebTransport 是否与 UDP Socket API 一样?

不可以。WebTransport 不是 UDP Socket API。虽然 WebTransport 使用 HTTP/3,而 HTTP/3“在后台”使用 UDP,WebTransport 在加密和拥塞控制方面有一些要求,因此不仅仅是一个基本的 UDP Socket API。

WebTransport 是 WebRTC 数据通道的替代方案?

是,适用于客户端-服务器连接。WebTransport 与 WebRTC 数据通道具有相同的许多属性,不过底层协议不同。

通常,运行与 HTTP/3 兼容的服务器所需的设置和配置比维护 WebRTC 服务器更少,这涉及了解多种协议(ICEDTLSSCTP),才能获得有效的传输。WebRTC 还包含许多动态部分,可能会导致客户端/服务器协商失败。

WebTransport API 的设计考虑到了 Web 开发者的应用场景,因此感觉更像是编写现代 Web 平台代码,而不是使用 WebRTC 的数据通道接口。与 WebRTC 不同,WebTransport 在 Web Worker 内部受支持,这样您就可以独立于给定 HTML 页面执行客户端-服务器通信。由于 WebTransport 公开了与 Streams 兼容的接口,因此它支持有关背压的优化。

不过,如果您已经拥有满意且可以正常运行的 WebRTC 客户端/服务器设置,那么改用 WebTransport 可能不会获得很多好处。

试试看

尝试使用 WebTransport 的最佳方法是启动兼容的 HTTP/3 服务器。 然后,您可以将此页面与基本 JavaScript 客户端结合使用,尝试客户端/服务器通信。

此外,webtransport.day 上还提供了由社区维护的 echo 服务器。

使用此 API

WebTransport 基于现代网络平台基元(例如 Streams API)而设计。它严重依赖于 promise,并且可与 asyncawait 搭配使用。

Chromium 中的当前 WebTransport 实现支持三种不同的流量:数据报,以及单向和双向数据流。

连接到服务器

您可以通过创建 WebTransport 实例来连接到 HTTP/3 服务器。网址格式应为 https。您需要明确指定端口号。

您应该使用 ready promise 来等待连接建立。此 promise 在设置完成之前不会执行,并且会在 QUIC/TLS 阶段连接失败时拒绝。

closed promise 在连接正常关闭时执行,如果意外关闭,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;

Datagram API

将 WebTransport 实例连接到服务器后,您可以使用该实例发送和接收离散的数据位,称为“数据报”

writeable getter 会返回一个 WritableStream,Web 客户端可以使用它将数据发送到服务器。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

WebTransportSendStream 由 Web 客户端使用 WebTransport 实例的 createUnidirectionalStream() 方法创建,该方法会返回 WebTransportSendStream 的 promise。

使用 WritableStreamDefaultWriterclose() 方法关闭关联的 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}`);
}

同样,使用 WritableStreamDefaultWriterabort() 方法将 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);
}

您可以使用 ReadableStreamDefaultReaderclosed 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.');
}).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
}

WebTransportBidirectionalStreamWebTransportSendStreamWebTransportReceiveStream 的组合。前两部分中的示例说明了如何使用各个属性。

更多示例

WebTransport 草稿规范包含许多额外的内嵌示例,以及所有方法和属性的完整文档。

Chrome 开发者工具中的 WebTransport

很遗憾,Chrome 的开发者工具目前不支持 WebTransport。您可以“加注星标”此 Chrome 问题,接收有关开发者工具界面更新的通知。

聚酯纤维

一个 polyfill(更确切地说是 ponyfill,它以独立模块的形式提供功能),称为 webtransport-ponyfill-websocket 实现了 WebTransport 的某些功能。请仔细阅读 项目的 README,以确定此解决方案是否适合您的用例。

隐私权和安全注意事项

如需权威性指导,请参阅规范草案的相应部分

反馈

Chrome 团队希望了解您对使用此 API 的看法和体验。

有关 API 设计的反馈

API 是否存在尴尬或无法按预期运行的地方?或者,是否有遗漏的环节需要付诸实施?

Web Transport GitHub 代码库中提交问题,或添加您对现有问题的看法。

实施时遇到问题?

您在 Chrome 的实现过程中是否发现了错误?

访问 https://new.crbug.com 提交 bug。添加尽可能多的细节,以及有关重现的简单说明。

打算使用该 API?

您的公开支持有助于 Chrome 确定各项功能的优先级,并向其他浏览器供应商展示支持这些功能的重要性。

一般讨论

对于不属于其他类别的一般问题或问题,您可以使用 web-transport-dev Google 群组

致谢

本文纳入了 WebTransport 说明规范草案相关设计文档中的信息。感谢这两位作者为我们提供基础。

这篇博文的主打图片由 Robin Pierre 在 Un 创立网站制作而成。