WebTransport 即将到来,允许浏览器进行类似 UDP 的交换。
WebTransport is almost here to allow UDP-like exchange in the browser

原始链接: https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API

## HTTP/3 & WebTransport 摘要 HTTP/3基于Google的QUIC协议(通过UDP),旨在改进传统HTTP/2(基于TCP)的局限性。主要优势包括消除队头阻塞——单个数据包丢失会阻止所有流——以及通过简化的安全性(与QUIC集成,减少往返次数)和传输效率来提高性能。HTTP/3还通过使用在网络变化中持久存在的连接ID,更优雅地处理网络转换。 WebTransport是一个利用HTTP/3的API,提供可靠和*不可靠*的数据传输。不可靠传输(通过数据报)非常适合速度至关重要且偶尔数据丢失可接受的场景,例如游戏状态更新。可靠流保证顺序和交付,适用于聊天等应用。 WebTransport连接通过HTTPS在指定端口建立。数据交换通过流进行——单向(单向)、双向(双向)和数据报——利用`ReadableStream`和`WritableStream` API发送和接收`Uint8Array`数据。该API可以访问服务器发起的传入流,从而实现灵活的通信模式。错误处理使用`WebTransportError`对象提供详细信息。

## WebTransport:一种新的浏览器传输层 一种新的Web API,WebTransport,即将发布,在浏览器内提供类似UDP的功能。虽然前景可观,但讨论的重点在于它是否可能*取代*WebSocket,这是一种广泛使用的可靠双向通信协议。一些开发者担心WebTransport不适合所有WebSocket用例,并希望WebSocket仍然可用。 一个主要问题是WebTransport需要HTTPS,限制了它在本地环境中的使用,在这些环境中安全连接不切实际。尽管如此,支持者认为,鉴于WebTransport依赖于HTTP/3等现代协议,TLS的开销很小。 WebTransport与WebSocket有很大不同,它提供多个流(可靠和不可靠),而不是单个双向流。它的设计更加灵活,但可能需要更高的复杂度。服务器端实现正在出现(Go和Rust中有库),但采用情况与各种语言中对HTTP/3的支持有关。 对话还涉及更广泛的Web API演进,以及对现有技术(如WebRTC)的更简单、更现代替代方案的需求。
相关文章

原文

HTTP/3 has been in progress since 2018. It is based on Google's QUIC protocol (which is itself based on UDP), and fixes several issues around the classic TCP protocol, on which HTTP and WebSockets are based.

These include:

Head-of-line blocking

HTTP/2 allows multiplexing, so a single connection can stream multiple resources simultaneously. However, if a single resource fails, all other resources on that connection are held up until any missing packets are retransmitted. With QUIC, only the failing resource is affected.

Faster performance

QUIC is more performant than TCP in many ways. QUIC can handle security features by itself, rather than handing responsibility off to other protocols like TLS — meaning fewer round trips. And streams provide better transport efficiency than the older packet mechanism. That can make a significant difference, especially on high-latency networks.

Better network transitions

QUIC uses a unique connection ID to handle the source and destination of each request — to ensure that packets are delivered correctly. This ID can persist between different networks, meaning that, for example, a download can continue without getting interrupted if you switch from Wi-Fi to a mobile network. HTTP/2, on the other hand, uses IP addresses as identifiers, so network transitions can be problematic.

Unreliable transport

HTTP/3 supports unreliable data transmission via datagrams.

The WebTransport API provides low-level access to two-way communication via HTTP/3, taking advantage of the above benefits, and supporting both reliable and unreliable data transmission.

Initial connection

To open a connection to an HTTP/3 server, you pass its URL to the WebTransport() constructor. Note that the scheme needs to be HTTPS, and the port number needs to be explicitly specified. Once the WebTransport.ready promise fulfills, you can start using the connection.

Also note that you can respond to the connection closing by waiting for the WebTransport.closed promise to fulfill. Errors returned by WebTransport operations are of type WebTransportError, and contain additional data on top of the standard DOMException set.

const url = "https://example.com:4999/wt";

async function initTransport(url) {
  // Initialize transport connection
  const transport = new WebTransport(url);

  // The connection can be used once ready fulfills
  await transport.ready;

  // …
}

// …

async function closeTransport(transport) {
  // Respond to connection closing
  try {
    await transport.closed;
    console.log(`The HTTP/3 connection to ${url} closed gracefully.`);
  } catch (error) {
    console.error(`The HTTP/3 connection to ${url} closed due to ${error}.`);
  }
}

Unreliable transmission via datagrams

"Unreliable" means that transmission of data is not guaranteed, nor is arrival in a specific order. This is fine in some situations and provides very fast delivery. For example, you might want to transmit regular game state updates where each message supersedes the last one that arrives, and order is not important.

Unreliable data transmission is handled via the WebTransport.datagrams property — this returns a WebTransportDatagramDuplexStream object containing everything you need to send datagrams to the server, and receive them back.

The WebTransportDatagramDuplexStream.writable property returns a WritableStream object that you can write data to using a writer, for transmission 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);

The WebTransportDatagramDuplexStream.readable property returns a ReadableStream object that you can use to receive data from the server:

async function readData() {
  const reader = transport.datagrams.readable.getReader();
  while (true) {
    const { value, done } = await reader.read();
    if (done) {
      break;
    }
    // value is a Uint8Array.
    console.log(value);
  }
}

Reliable transmission via streams

"Reliable" means that transmission and order of data are guaranteed. That provides slower delivery (albeit faster than with WebSockets), and is needed in situations where reliability and ordering are important (such as chat applications, for example).

When using reliable transmission via streams you can also set the relative priority of different streams over the same transport.

Unidirectional transmission

To open a unidirectional stream from a user agent, you use the WebTransport.createUnidirectionalStream() method to get a reference to a WritableStream. From this you can get a writer to allow data to be written to the stream and sent to the server.

async function writeData() {
  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}`);
  }
}

Note also the use of the WritableStreamDefaultWriter.close() method to close the associated HTTP/3 connection once all data has been sent.

If the server opens a unidirectional stream to transmit data to the client, this can be accessed on the client via the WebTransport.incomingUnidirectionalStreams property, which returns a ReadableStream of WebTransportReceiveStream objects. These can be used to read Uint8Array instances sent by the server.

In this case, the first thing to do is set up a function to read a WebTransportReceiveStream. These objects inherit from the ReadableStream class, so can be used in just the same way:

async function readData(receiveStream) {
  const reader = receiveStream.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) {
      break;
    }
    // value is a Uint8Array
    console.log(value);
  }
}

Next, call WebTransport.incomingUnidirectionalStreams and get a reference to the reader available on the ReadableStream it returns, and then use the reader to read the data from the server. Each chunk is a WebTransportReceiveStream, and we use the readFrom() set up earlier to read them:

async function receiveUnidirectional() {
  const uds = transport.incomingUnidirectionalStreams;
  const reader = uds.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) {
      break;
    }
    // value is an instance of WebTransportReceiveStream
    await readData(value);
  }
}

Bidirectional transmission

To open a bidirectional stream from a user agent, you use the WebTransport.createBidirectionalStream() method to get a reference to a WebTransportBidirectionalStream. This contains readable and writable properties returning references to WebTransportReceiveStream and WebTransportSendStream instances that can be used to read from and write to the server.

Note: WebTransportBidirectionalStream is similar to WebTransportDatagramDuplexStream, except that in that interface the readable and writable properties are ReadableStream and WritableStream respectively.

async function setUpBidirectional() {
  const stream = await transport.createBidirectionalStream();
  // stream is a WebTransportBidirectionalStream
  // stream.readable is a WebTransportReceiveStream
  const readable = stream.readable;
  // stream.writable is a WebTransportSendStream
  const writable = stream.writable;

  // …
}

Reading from the WebTransportReceiveStream can then be done as follows:

async function readData(readable) {
  const reader = readable.getReader();
  while (true) {
    const { value, done } = await reader.read();
    if (done) {
      break;
    }
    // value is a Uint8Array.
    console.log(value);
  }
}

And writing to the WebTransportSendStream can be done like this:

async function writeData(writable) {
  const writer = writable.getWriter();
  const data1 = new Uint8Array([65, 66, 67]);
  const data2 = new Uint8Array([68, 69, 70]);
  writer.write(data1);
  writer.write(data2);
}

If the server opens a bidirectional stream to transmit data to and receive it from the client, this can be accessed via the WebTransport.incomingBidirectionalStreams property, which returns a ReadableStream of WebTransportBidirectionalStream objects. Each one can be used to read and write Uint8Array instances as shown above. However, as with the unidirectional example, you need an initial function to read the bidirectional stream in the first place:

async function receiveBidirectional() {
  const bds = transport.incomingBidirectionalStreams;
  const reader = bds.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) {
      break;
    }
    // value is an instance of WebTransportBidirectionalStream
    await readData(value.readable);
    await writeData(value.writable);
  }
}

For complete examples, see:

联系我们 contact @ memedata.com