WebTransport Datagrams and Unreliable Transmission
WebTransport brings low-latency, bidirectional communication to the web using the HTTP/3 and QUIC protocols. Unlike traditional WebSockets or WebRTC data channels, WebTransport natively provides access to both reliable streams and unreliable datagrams. This article explains what datagrams are within the WebTransport API, why they are essential for real-time applications, and how JavaScript developers handle unreliable, unordered data transmission in practice.
What Are Datagrams in WebTransport?
In WebTransport, a datagram is a discrete, self-contained message sent over the network. Datagrams are transmitted using QUIC’s underlying datagram extension, offering properties similar to the User Datagram Protocol (UDP):
- Unreliable Delivery: Datagrams are not automatically retransmitted if they are lost in transit.
- Unordered Delivery: Packets can arrive in a different order than they were sent.
- No Head-of-Line Blocking: Because packets do not wait for missing data to be retransmitted, packet loss does not delay subsequent data.
- Message Boundaries: Each datagram is sent and received as an independent chunk, preserving message boundaries without the need for manual framing.
This makes datagrams ideal for time-sensitive use cases where low latency is critical and outdated data is useless, such as online gaming state updates, live media streaming, and real-time telemetry.
The JavaScript WebTransport Datagram API
The JavaScript WebTransport interface exposes datagram
functionality through the datagrams property, which
utilizes the Streams API (ReadableStream and
WritableStream).
Sending Datagrams
To send datagrams, you acquire a writer from the
transport.datagrams.writable stream and write a
Uint8Array buffer.
const transport = new WebTransport("https://example.com:4433/webtransport");
await transport.ready;
const writer = transport.datagrams.writable.getWriter();
const data = new TextEncoder().encode("player_position:120,340");
await writer.write(data);
writer.releaseLock();Because datagrams are unreliable, the resolution of
writer.write() only confirms that the datagram was handed
off to the transport layer, not that the remote peer successfully
received it. If network congestion occurs, the browser or operating
system may drop datagrams automatically.
Receiving Datagrams
To receive datagrams, you read from the
transport.datagrams.readable stream using an asynchronous
loop:
const reader = transport.datagrams.readable.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
// value is a Uint8Array
const message = new TextDecoder().decode(value);
console.log("Received datagram:", message);
}
} catch (error) {
console.error("Error reading datagrams:", error);
} finally {
reader.releaseLock();
}Handling Unreliable Transmission in JavaScript
Because datagrams lack built-in reliability and ordering, the application layer in JavaScript is responsible for managing packet anomalies.
1. Packet Size Limitations
Datagrams cannot exceed the Maximum Transmission Unit (MTU) of the
network path without being fragmented or dropped. The WebTransport API
exposes the maximum allowed payload size via
transport.datagrams.maxDatagramSize:
const maxBytes = transport.datagrams.maxDatagramSize;
if (payload.byteLength <= maxBytes) {
await writer.write(payload);
} else {
// Split data or send via reliable WebTransport stream instead
}2. Handling Out-of-Order Delivery
To detect and handle packets arriving out of sequence, applications typically attach a sequence number or timestamp to the beginning of each datagram payload:
let localSequence = 0;
function createDatagramPayload(dataBuffer) {
const buffer = new ArrayBuffer(4 + dataBuffer.byteLength);
const view = new DataView(buffer);
// Set 32-bit sequence number header
view.setUint32(0, localSequence++, false);
new Uint8Array(buffer, 4).set(new Uint8Array(dataBuffer));
return new Uint8Array(buffer);
}On the receiving end, the receiver tracks the highest sequence number seen. Any incoming packet with an older sequence number can be discarded if the application only cares about the freshest state.
3. Handling Dropped Packets
Since lost datagrams are never re-sent by the protocol, JavaScript applications handle loss using one of two strategies: * State Replacement: The sender continuously transmits the latest complete snapshot (e.g., current player coordinates). Missing an intermediate update is harmless because the next packet provides the current state. * Application-Level Acknowledgment: If specific datagrams require confirmation, the receiver sends back a lightweight acknowledgment over an unreliable datagram or a reliable stream, prompting the sender to retransmit manually if necessary.