使用 WebTransport

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

背景

什么是 WebTransport?

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

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

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

用例

这里仅列出了开发者使用 WebTransport 的可能方式。

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

我们想详细了解您计划如何使用 WebTransport。

浏览器支持

浏览器支持

  • 97
  • 97
  • 114
  • x

来源

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

当前状态

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

WebTransport 与其他技术的关系

WebTransport 是否可以替代 WebSocket?

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

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

通过数据报 API 或多个并发 Streams API 实例使用 WebTransport 时,不必担心出现队名屏蔽问题,而队长阻塞可能是 WebSocket 的问题。此外,建立新连接还会带来性能优势,因为底层 QUIC 握手比通过 TLS 启动 TCP 更快。

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

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 服务器需要了解多种协议(ICEDTLSSCTP)以获取有效的传输。WebRTC 涉及更多变数,可能会导致客户端/服务器协商失败。

WebTransport API 在设计时充分考虑了 Web 开发者的用例,应该更类似于编写现代 Web 平台代码,而不是使用 WebRTC 的数据通道接口。与 WebRTC 不同Web Workers 内部支持 WebTransport,使用它,您可以独立于指定的 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 会在连接正常关闭时执行,如果意外关闭,则会拒绝。

如果服务器因客户端指示错误(例如网址的路径无效)拒绝连接,则会导致 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,网络客户端可以使用它向服务器发送数据。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

Web 客户端使用 WebTransport 实例的 createUnidirectionalStream() 方法创建 WebTransportSendStream,该方法会返回 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
}

WebTransportBidirectionalStream 只是 WebTransportSendStreamWebTransportReceiveStream 的组合。前两部分中的示例介绍了如何使用它们。

更多示例

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

Chrome 开发者工具中的 WebTransport

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

聚酯纤维

可以使用一个名为 webtransport-ponyfill-websocket 的 polyfill(更确切地说,是将功能作为可使用的独立模块提供功能的 ponyfill),用于实现 WebTransport 的部分功能。请仔细阅读项目 README 中的限制条件,以确定此解决方案是否适用于您的用例。

隐私权和安全注意事项

请参阅规范草案中的相应部分,获取权威指南。

反馈

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

有关 API 设计的反馈

这个 API 是否存在异常之处或无法按预期运行?或者是否遗漏了某些需要实施的地方?

网络传输 GitHub 代码库中提交问题,或将您的想法添加到现有问题中。

实施方面有问题?

您是否发现了 Chrome 实现方面的错误?

请通过 https://new.crbug.com 提交 bug。请提供尽可能详细的信息以及重现错误的简单说明。

打算使用该 API?

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

  • 您可以使用 # 标签 #WebTransport 发送一条推文,并详细说明您在什么位置、如何使用该推文。

一般性讨论

您可以在 web-transport-dev Google 网上论坛中提出一般问题或不属于其他某个类别的问题。

致谢

本文纳入了 WebTransport 说明规范草稿相关设计文档中的信息。感谢各位作者为我们提供这方面的支持。

本博文中的主打图片由 Robin Pierre 在 Unsplash 发布。