关于WebTransport的解释说明

25 年 8 月 24 日 星期日 (已编辑)
1832 字
10 分钟

来源 https://github.com/w3c/webtransport

许多应用程序,如游戏和直播需要一种机制,以尽可能快地发送许多消息,这些消息可能是无序的,可靠性不高,从客户端到服务器或从服务器到客户端。但是,Web 平台缺乏一个轻松实现这种功能的机制。原生应用程序可以使用原始的 UDP 套接字,但由于缺乏加密、拥塞控制和用于发送同意的机制(以防止 DDoS 攻击),这些在 Web 上不可用。

历史上,需要客户端与服务器之间双向数据流的 Web 应用程序可以依赖于 WebSockets [RFC6455],这是一种与 Web 安全模型兼容的基于消息的协议。然而,由于其提供的抽象是单一的、可靠的、有序的消息流,它经常会遭遇队头阻塞(HOLB),这意味着即使消息是独立的,一些消息不再需要,所有消息也必须按顺序发送和接收。这对于依赖部分可靠性和流独立性的延迟敏感应用程序来说适用性不佳。

我们认为需要一个简单的、客户端-服务器、无序/不可靠的 API,以实现最小的延迟。WebTransport 协议通过一个传输对象来提供该功能,抽象掉了具体的底层协议,具备包括可靠的单向和双向流、不可靠的数据报(类似于 QUIC 的能力)在内的灵活能力集。

目标

  • 提供一种与服务器进行低延迟通信的方法,包括对不可靠和无序通信的支持。
  • 提供一个可以用于多种用例的 API,便于在客户端与服务器之间可靠和不可靠、有序和无序地传输数据。
  • 确保与 WebSockets 相同的安全属性(使用 TLS,服务器控制的来源策略)。

非目标

这不是 UDP 套接字 API。我们必须具备加密和拥塞控制的通信。

关键使用场景

  • 通过许多小型、不可依赖的、无序的消息以最低延迟将游戏状态发送到服务器
  • 以最低延迟(无序)接收从服务器推送的媒体
  • 接收从服务器推送的消息(如通知)
  • 通过 HTTP 请求,并通过相同网络连接无序且不可靠地接收推送的媒体

提议的解决方案

  1. 一个通用的传输接口,可以由任何传输提供,但与 QUIC 的能力紧密匹配。
  2. 能够使用 HTTP/3 扩展 的传输接口,以启用 QUIC 提供的传输能力。
  3. 能够使用 HTTP/2 扩展 的传输接口,这在 QUIC 不可用的情况下提供在 TCP 上的相同功能。使用 TCP 提供可靠、按顺序传递,这意味着 HTTP/2 版本可能缺乏一些 QUIC 提供的性能优势。HTTP/3 和 HTTP/2 扩展均提供相同的能力,允许应用程序在大多数情况下构建相同的接口。

示例

使用数据报向服务器发送不可靠的游戏状态

javascript
// 应用程序提供了一种获取已序列化状态的方法,以发送给服务器
function getSerializedGameState() { ... }
const wt = new WebTransport('https://example.com:10001/path');
const writer = wt.datagrams.writable.getWriter();
setInterval(() => {
  const message = getSerializedGameState();
  writer.write(message);
}, 100);

使用单向发送流向服务器发送可靠的游戏状态

javascript
// 应用程序提供了一种获取已序列化状态的方法,以发送给服务器。
function getSerializedGameState() { ... }
const wt = new WebTransport('https://example.com:10001/path');
setInterval(async () => {
  const message = getSerializedGameState();
  const stream = await wt.createUnidirectionalStream();
  const writer = stream.getWriter();
  writer.write(message);
  writer.close();
}, 100);

使用单向接收流接收从服务器推送的媒体

javascript
function getSerializedMediaRequest() { ... }
const wt = new WebTransport('https://example.com:10001/path');
const mediaSource = new MediaSource();
await new Promise(resolve => mediaSource.addEventListener('sourceopen', resolve, {once: true}));
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="opus, vp09.00.10.08"');
const mediaRequest = getSerializedMediaRequest();
const requestStream = await wt.createUnidirectionalStream();
const requestWriter = requestStream.getWriter();
requestWriter.write(mediaRequest);
requestWriter.close();
for await (const receiveStream of wt.incomingUnidirectionalStreams) {
  for await (const buffer of receiveStream) {
    sourceBuffer.appendBuffer(buffer);
  }
  await new Promise(resolve => sourceBuffer.addEventListener('update', resolve, {once: true}));
}

接收来自服务器推送的通知,并作出响应

javascript
function deserializeNotification(serializedNotification) { ... }
function serializeClickedMessage(notification) { ... }
const wt = new WebTransport('https://example.com:10001/path');
for await (const {readable, writable} of wt.incomingBidirectionalStreams) {
  const buffers = []
  for await (const buffer of readable) {
    buffers.push(buffer)
  }
  const notification = new Notification(deserializeNotification(buffers));
  notification.addEventListener('onclick', () => {
    const clickMessage = serializeClickedMessage(notification);
    const writer = writable.getWriter();
    writer.write(clickMessage);
    writer.close();
  });
}

通过使用 HTTP 请求并通过相同网络连接无序且不可靠地接收推送的媒体

javascript
const mediaSource = new MediaSource();
await new Promise(resolve => mediaSource.addEventListener('sourceopen', resolve, {once: true}));
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="opus, vp09.00.10.08"');
const wt = new WebTransport('/video', {allowPooling: true});
await fetch('https://example.com/babyshark');
for await (const datagram of wt.datagrams.readable) {
  sourceBuffer.appendBuffer(datagram);
  await new Promise(resolve => sourceBuffer.addEventListener('update', resolve, {once: true}));
}

通过 HTTP 请求,并通过相同网络连接接收有序且可靠推送的媒体

javascript
const mediaSource = new MediaSource();
await new Promise(resolve => mediaSource.addEventListener('sourceopen', () => resolve(), {once: true}));
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="opus, vp09.00.10.08"');
const wt = new WebTransport('https://example.com/video');
for await (const receiveStream of transport.incomingUnidirectionalStreams) {
  for await (const buffer of receiveStream) {
    sourceBuffer.appendBuffer(buffer);
  }
  await new Promise(resolve => sourceBuffer.addEventListener('update', resolve, {once: true}));
}

详细设计讨论

WebTransport 可以支持多种协议,每个协议提供部分或全部以下能力:

  • 单向流是单方向的无限长字节流,当接收器无法快速读取或者受到网络容量/拥塞限制时,对发送者应用背压。当发送不期望响应的消息时非常有用。可通过在单个流中发送许多消息实现有序和可靠的消息传递。通过每个流发送一个消息实现无序消息传递。

  • 双向流是全双工流。一个双向流本质上是一个单向流对。

  • 数据报是小型、无序、不可靠的消息。它们对于发送消息比流具有更少的 API 复杂性和网络开销。

WebTransport over HTTP/3 是一个建立在 HTTP/3 之上的 WebTransport 协议。目前,WebTransport 只支持这一协议。未来可能支持其他协议,如 WebTransport over HTTP/2。

考虑过的替代设计

WebRTC 数据通道

虽然 WebRTC 数据通道已被用于客户端/服务器通信(例如云游戏应用程序),但这需要服务器端实现多个在服务器上不常见的协议(ICE、DTLS 和 SCTP),且应用程序需要使用为非常不同的用例设计的复杂 API(RTCPeerConnection)。

在 HTTP/3 上分层 WebSockets

[I-D.ietf-quic-http] 以类似于当前在 HTTP/2 [RFC8441] 上分层的方式。这将避免队头阻塞,并提供通过关闭相应的 WebSocket 对象取消流的能力。然而,这种方法有一些缺点,主要来自于每个 WebSocket 在语义上是一个完全独立的实体:

  1. 每个新流都需要 WebSocket 握手以协商所使用的应用协议,这意味着在客户端可以写入之前,每个新流至少需要一个 RTT。

  2. 只有客户端可以发起流。服务器发起的流和其他可用的通信模式(如 QUIC 数据报帧)不可用。

  3. 虽然流通常由用户代理池化,但这并不保证,通常将 WebSocket 映射到端的过程对客户端是透明的。这给系统引入了不可预测的性能属性,并阻止了依赖于流在同一连接上的优化(例如,客户端可能能够为不同的流请求不同的重传优先级,但除非它们都在同一连接上,否则这是不可能的)。

文章标题:关于WebTransport的解释说明

文章作者:shirtiny

文章链接:https://kizamu.anror.com/posts/web-transport-explainer[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。