尝试使用 WebTransport

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. 启动 未开始

WebTransport 与其他技术的关系 #

WebTransport 可以替代 WebSockets 吗? #

也许可以。在某些用例中,WebSockets 或 WebTransport 可作为可用的有效通信协议。

WebSockets 通信围绕单一、可靠、有序的消息流建模,这对于某些类型的通信需求来说是很好的。如果您需要这些特性,那么 WebTransport 的流 API 也可以提供它们。相比之下,WebTransport 的数据报 API 提供低延迟交付,但不保证可靠性或排序,因此它们不能直接替代 WebSocket

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

WebTransport 属于新草案规范,因此围绕客户端和服务器库的 WebSocket 生态系统目前更加强大。如果您需要具有通用服务器设置和广泛的 Web 客户端支持的“开箱即用”工具,WebSockets 是目前更好的选择。

WebTransport 是否与 UDP 套接字 API 相同? #

不相同。WebTransport 不是 UDP 套接字 API 。虽然 WebTransport 使用 HTTP/3,而后者又在“幕后”使用 UDP,但 WebTransport 对加密和拥塞控制有要求,这使其不仅仅是基本的 UDP 套接字 API。

WebTransport 可以替代 WebRTC 数据通道吗? #

可以,用于客户端-服务器连接。 WebTransport 与 WebRTC 数据通道共享许多相同的属性,尽管底层协议不同。

通常,与维护 WebRTC 服务器相比,运行兼容 HTTP/3 的服务器需要更少的设置和配置,后者涉及了解多种协议(ICE 、 DTLS 和 SCTP)以获得有效的传输。WebRTC 需要更多可能导致客户端/服务器协商失败的移动部分。

WebTransport API 的设计考虑了 Web 开发人员的用例,与使用 WebRTC 的数据通道接口相比,它更像是编写现代 Web 平台代码。与 WebRTC 不同的是,Web Workers 内部支持 WebTransport,它允许您独立于给定的 HTML 页面执行客户端-服务器通信。由于 WebTransport 有一个兼容的接口,因此支持围绕背压的优化。

但是,如果您已经有一个满意的 WebRTC 客户端/服务器设置,那么切换到 WebTransport 可能不会带来很多优势。

试试看 #

试验 WebTransport 的最佳方法是在本地启动兼容的 HTTP/3 服务器。(遗憾的是,目前没有与最新规范兼容的公共参考服务器。)然后,您可以将此页面与基本 JavaScript 客户端一起使用来试验客户端/服务器通信。

此外,可以使用 webtransport.day 来进行尝试,它是一个由社区维护的 WebTransport echo 服务器。

使用 API #

WebTransport 的设计基于现代 Web 平台基本类型(如 Streams API)。它在很大程度上依赖于 promise,并且可以很好地与 async 和 await 配合使用。

WebTransport 初始试用支持三种不同类型的流量:数据报以及单向流和双向流。

连接到服务器 #

您可以通过创建 WebTransport 实例连接到 HTTP/3 服务器。URL 的模式应为 https。您需要直接指定端口号。

您应该使用 ready promise 来等待建立连接。在完成设置之前,不会履行该 promise,如果在 QUIC/TLS 阶段连接失败,被拒绝该 promise。

closed promise 在连接正常关闭时会履行,如果意外关闭,则会被拒绝。

如果服务器由于客户端指示错误(如 URL 的路径无效)而拒绝连接,则会导致 closed 拒绝,而 ready 仍未解析。

const url = 'https://example.com:4999/foo/bar';
const transport = new WebTransport(url);

// 或者,可以选择设置函数以响应
// 连接关闭:
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}.');
});

// 一旦 .ready 满足,就可以使用连接。
await transport.ready;

数据报 API #

一旦您拥有连接到服务器的 WebTransport 实例,您就可以使用它来发送和接收离散的数据位,称为数据报

writeable getter 返回一个 WritableStream,Web 客户端可以使用它向服务器发送数据。readable getter 返回一个 ReadableStream,允许您侦听来自服务器的数据。这两个流本质上都是不可靠的,因此服务器可能收不到您写入的数据,反之亦然。

两种类型的流都使用 Uint8Array 实例进行数据传输。

// 向服务器发送两个数据报。
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);

// 从服务器读取数据报。
const reader = transport.datagrams.readable.getReader();
while (true) {
  const {value, done} = await reader.read();
  if (done) {
    break;
  }
  // 值为 Uint8Array。
  console.log(value);
}

流 API #

连接到服务器后,您还可以使用 WebTransport 通过其 Streams API 发送和接收数据。

所有流的每个块都是一个 Uint8Array。与数据报 API 不同,这些流是可靠的。但是每个流都是独立的,因此不能保证跨流的数据顺序。

SendStream #

SendStream 由 Web 客户端使用 WebTransport 实例的 createSendStream() 方法创建,该方法返回对 SendStream 的 promise。

使用 WritableStreamDefaultWriter 的 close() 方法关闭关联的 HTTP/3 连接。浏览器尝试在实际关闭关联的连接之前发送所有挂起的数据。

// 向服务器发送两个 Uint8Array。
const stream = await transport.createSendStream();
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() 方法将 QUIC RESET_STREAM 发送到服务器。使用 abort() 时,浏览器可能会丢弃任何尚未发送的挂起的数据。

const ws = await transport.createSendStream();
const writer = ws.getWriter();
writer.write(...);
writer.write(...);
await writer.abort();
// 并非所有数据都已写入。

ReceiveStream #

ReceiveStream 由服务器发起。对于网络客户端,获取 ReceiveStream 的过程分为两步。 首先,它调用 WebTransport 实例的 receiveStreams() 方法,该方法返回一个 ReadableStream。该 ReadableStream 的每个块又是一个 ReceiveStream,可用于读取服务器发送的 Uint8Array 实例。

async function readFrom(receiveStream) {
  const reader = receiveStream.readable.getReader();
  while (true) {
    const {done, value} = await reader.read();
    if (done) {
      break;
    }
    // 值为 Uint8Array
    console.log(value);
  }
}

const rs = transport.receiveStreams();
const reader = rs.getReader();
while (true) {
  const {done, value} = await reader.read();
  if (done) {
    break;
  }
  // 值为 ReceiveStream 的一个实例
  await readFrom(value);
}

您可以使用 ReadableStreamDefaultReader 的 closed promise 来检测流关闭。当使用 FIN 位关闭底层 HTTP/3 连接时,在读取所有数据后会履行 closed promise。当 HTTP/3 连接突然关闭时(例如,由STREAM_RESET 关闭),则 closed promise 会被拒绝。

// 假设一个活动的接收流
const reader = receiveStream.readable.getReader();
reader.closed.then(() => {
  console.log('The receiveStream closed gracefully.');
}).catch(() => {
  console.error('The receiveStream closed abruptly.');
});

BidirectionalStream #

BidirectionalStream 可能由服务器或客户端创建。

Web 客户端可以使用 WebTransport 实例的 {code 0}createBidirectionalStream() 方法创建一个,该方法会返回对 BidirectionalStream 的 promise。

const stream = await transport.createBidirectionalStream();
// stream 是一个 BidirectionalStream
// stream.readable 是一个 ReadableStream
// stream.writable 是一个 WritableStream

您可以使用 WebTransport 实例的 receiveBidirectionalStreams() 方法侦听服务器创建的 BidirectionalStream,该方法返回 ReadableStream{ /code3}。该 <code data-md-type="codespan">ReadableStream 的每个块又是一个 BidirectionalStream

const rs = transport.receiveBidrectionalStreams();
const reader = rs.getReader();
while (true) {
  const {done, value} = await reader.read();
  if (done) {
    break;
  }
  // value 是一个 BidirectionalStream
  // value.readable 是一个 ReadableStream
  // value.writable 是一个 WritableStream
}

BidirectionalStream 只是 SendStream 和 ReceiveStream 的组合。前两个部分中的示例解释了如何使用它们中的每一个。

更多示例 #

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

在初始试用期间启用支持 #

  1. Request a token for your origin.
  2. Add the token to your pages. There are two ways to do that:
    • Add an origin-trial <meta> tag to the head of each page. For example, this may look something like:
      <meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">
    • If you can configure your server, you can also add the token using an Origin-Trial HTTP header. The resulting response header should look something like:
      Origin-Trial: TOKEN_GOES_HERE

Chrome DevTools 中的 WebTransport #

遗憾的是, 初始试用之初,对 WebTransport 的 Chrome DevTools 支持还没有准备好。您可以对此 Chrome 问题添加“星标”,以便在 DevTools 界面上收到有关更新的通知。

隐私和安全注意事项 #

有关权威指南,请参阅草案规范的相应部分

反馈 #

Chrome 团队希望了解您在整个初始试用过程中使用此 API 的想法和体验。

关于 API 设计的反馈 #

API 是否存在不按预期工作的情况?或者是否缺少相关部分而让您无法实现自己的想法?

请在 Web Transport GitHub 存储库上提交问题或将您的想法添加到现有问题中。

实现遇到问题? #

您是否发现 Chrome 实现的缺陷?

在 https://new.crbug.com 中提交缺陷。尽可能包括更多的详细信息以及再现缺陷的简单说明。

计划使用 API? #

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

  • 请确保您已报名初始试用以表明您的兴趣并提供您的域和联系信息。
  • 请向 @ChromiumDev 发送带有 #WebTransport 标签的推文,并提供有关您所在地以及使用方式的详细信息。

交流区 #

您可以使用 web-transport-dev Google Group 解决一般问题或不属于其他类别的问题。

致谢 #

本文含有来自 WebTransport Explainer 、草案规范相关设计文档的信息。感谢各位作者提供的基础文档。

尝试使用 WebTransport
标签: