-
Notifications
You must be signed in to change notification settings - Fork 7
Add Web Datagrams blog post skeleton #145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,114 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||
| layout: "@/layouts/global.astro" | ||||||||||||||||||||||||||||||||||||||||||||||
| title: Web Datagrams | ||||||||||||||||||||||||||||||||||||||||||||||
| author: kixelated | ||||||||||||||||||||||||||||||||||||||||||||||
| description: Every way to send an unreliable message from a browser, ranked by how much of a hack it is. | ||||||||||||||||||||||||||||||||||||||||||||||
| cover: "/blog/web-datagrams/TODO.jpg" | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π‘ Minor | β‘ Quick win π Supported by static analysisπ Script executed: set -eu
printf '%s\n' '--- target frontmatter ---'
cat -n src/pages/blog/web-datagrams.mdx | sed -n '1,20p'
printf '%s\n' '--- global layout cover references ---'
rg -n -C 4 'cover|og:image|twitter:image|Image' src/layouts/global.astro src/pages src/components 2>/dev/null | head -240
printf '%s\n' '--- relevant asset paths ---'
find public/blog/web-datagrams -maxdepth 2 -type f -print 2>/dev/null || true
printf '%s\n' '--- tracked matching assets ---'
git ls-files | rg '(^|/)web-datagrams/|TODO\.jpg$' || trueRepository: moq-dev/moq.dev Length of output: 17724 π€ get_repo_knowledge executed:
Length of output: 5684 Add the cover asset or remove the
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| date: 2026-09-18 | ||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Web Datagrams | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| TODO: Intro. | ||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On any deployment containing this file, Astro publishes Useful? React with πΒ / π. |
||||||||||||||||||||||||||||||||||||||||||||||
| We want unreliable messages in the browser: send a packet, and if it gets lost, *don't* retransmit it. | ||||||||||||||||||||||||||||||||||||||||||||||
| Real-time media, game state, anything where a late packet is worse than a missing one. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| This post is a tour of every option, roughly in order of increasing desperation. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## Why not WebSocket? | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| TODO. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - TCP: head-of-line blocking, retransmits everything. | ||||||||||||||||||||||||||||||||||||||||||||||
| - HTTP/3 fetch is QUIC underneath, but the browser API is still a reliable stream. | ||||||||||||||||||||||||||||||||||||||||||||||
| - Link to [Never* use Datagrams](/blog/never-use-datagrams) for the "do you actually need this" caveat. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## WebRTC Data Channels | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| TODO. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - SCTP over DTLS. `ordered: false, maxRetransmits: 0` looks like a datagram API. | ||||||||||||||||||||||||||||||||||||||||||||||
| - It isn't. See [Distribution at Twitch](/blog/distribution-at-twitch): flow control is scoped per *message*, a message counts against the window until fully received, and large messages deadlock against hard-coded browser limits. | ||||||||||||||||||||||||||||||||||||||||||||||
| - SCTP can't drop messages out of order. | ||||||||||||||||||||||||||||||||||||||||||||||
| - SCTP congestion control is its own thing, and not a good one. | ||||||||||||||||||||||||||||||||||||||||||||||
| - Extra RTTs to negotiate SCTP, then data channels. | ||||||||||||||||||||||||||||||||||||||||||||||
| - SCTP ACKs are a ton of extra UDP packets. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### One message per datagram + FORWARD-TSN | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| TODO. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - Keep each message under the MTU so it never fragments; use [PR-SCTP / FORWARD-TSN](https://www.rfc-editor.org/rfc/rfc3758) so the sender can skip lost ones. | ||||||||||||||||||||||||||||||||||||||||||||||
| - Only works native (libwebrtc, pion, etc.). The browser side is still a hack on top of a hack. | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+42
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π‘ Minor | β‘ Quick win π Supported by static analysisπ Script executed: sed -n '18,44p' src/pages/blog/web-datagrams.mdxRepository: moq-dev/moq.dev Length of output: 1317 π Web query:
π‘ Result: <search_synthesis> <source_evidence> Citations:
Correct the PR-SCTP capability description.
Proposed correction-- SCTP can't drop messages out of order.
+- With unordered delivery and zero retransmissions, browser data channels can provide a UDP-like per-message service, but SCTP flow-control and fragmentation limits still apply.
-- Only works native (libwebrtc, pion, etc.). The browser side is still a hack on top of a hack.
+- This behavior is available through browser data channels and native stacks, but browser APIs do not expose SCTP controls directly.(RFC 8831) π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| - Still congestion controlled by SCTP. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## WebRTC Encoded Transforms | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| The media path has no SCTP. RTP is basically a datagram already, so... smuggle arbitrary bytes inside RTP. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### Video: 1x1 frames as payload | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| TODO. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - Publish a 1x1 video track, use [encoded transforms](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_Encoded_Transform) to replace each encoded frame's bytes with your payload. | ||||||||||||||||||||||||||||||||||||||||||||||
| - Receiver's transform pulls the payload back out before it hits the decoder. | ||||||||||||||||||||||||||||||||||||||||||||||
| - Gotchas: the packetizer will fragment large "frames" into multiple RTP packets, and the depacketizer needs *all* of them, so you're back to reassembly. Keep it under one packet or accept partial loss = whole loss. | ||||||||||||||||||||||||||||||||||||||||||||||
| - Is this still congestion controlled? TODO: check whether the pacer / GCC throttles a track that isn't a real encoder. Probably yes. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### Audio: one packet every 10ms | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| TODO. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - Audio frames are small enough to never fragment, and the sender emits one every 10ms (or 20ms) like clockwork. | ||||||||||||||||||||||||||||||||||||||||||||||
| - No fragmentation, and audio is (mostly) exempt from congestion control. | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π‘ Minor | β‘ Quick win π Supported by static analysisπ Script executed: sed -n '45,70p' src/pages/blog/web-datagrams.mdx
sed -n '104,114p' src/pages/blog/web-datagrams.mdxRepository: moq-dev/moq.dev Length of output: 1842 π Web query:
π‘ Result: <search_synthesis> <source_evidence> Citations:
π Web query:
π‘ Result: <search_synthesis> <source_evidence> Citations:
π Web query:
π‘ Result: <search_synthesis> <source_evidence> Citations:
π Web query:
π‘ Result: <search_synthesis> <source_evidence> Citations:
Mark encoded-audio traffic as congestion controlled. The encoded transform runs before the RTP packetizer, so it does not bypass WebRTC media transport controls. RFC 8834 requires WebRTC endpoints to implement congestion control and does not exempt audio. Update both the prose and the table. Proposed correction-- No fragmentation, and audio is (mostly) exempt from congestion control.
+- No fragmentation for a packet-sized payload, but the RTP transport remains congestion controlled.
-| Encoded transform (audio) | no | no | yes | 4 |
+| Encoded transform (audio) | no | yes | yes | 4 |π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| - Downside: exactly one packet per 10ms, so bandwidth is capped by the packet rate. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### More tracks, more bandwidth | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| TODO. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - Each track is another 100 packets/sec. Want more? Add tracks. | ||||||||||||||||||||||||||||||||||||||||||||||
| - ~1100 bytes of payload per packet after RTP/DTLS overhead, so 1100 B Γ 100 pkt/s = 880 kb/s per track. | ||||||||||||||||||||||||||||||||||||||||||||||
| - 8 tracks (or 8 connections) β 7 Mb/s. TODO: measurements. | ||||||||||||||||||||||||||||||||||||||||||||||
| - TODO: where does this fall over (SDP size, CPU, packet count). | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## WebTransport Datagrams | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| TODO. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - The actual API: `transport.datagrams.writable`. QUIC DATAGRAM frames, no SCTP, no encoded-frame cosplay. | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π‘ Minor | β‘ Quick win π Supported by static analysisπ Script executed: sed -n '75,83p' src/pages/blog/web-datagrams.mdxRepository: moq-dev/moq.dev Length of output: 676 π Web query:
π‘ Result: <search_synthesis> <source_evidence> Citations:
Use the current WebTransport datagram API. The WebTransport specification defines Proposed correction-- The actual API: `transport.datagrams.writable`. QUIC DATAGRAM frames, no SCTP, no encoded-frame cosplay.
+- The current API: `transport.datagrams.createWritable()`. QUIC DATAGRAM frames, no SCTP, no encoded-frame cosplay.π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| - Still congestion controlled. Datagrams share the connection's congestion window, so a lossy link means the browser drops them *before* sending rather than letting you decide. | ||||||||||||||||||||||||||||||||||||||||||||||
| - Client-server only. No P2P, no ICE, no NAT traversal. | ||||||||||||||||||||||||||||||||||||||||||||||
| - Link to [QUIC Powers](/blog/quic-powers) and/or [Never* use Datagrams](/blog/never-use-datagrams) for how MoQ uses (and mostly doesn't use) this. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## Future | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### WebTransport P2P | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| TODO. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - The WebTransport WG [recharter](https://www.w3.org/2026/07/webtransport-wg-charter.html) (effective ~Sep 2026) expands scope to "explore specific low-latency needs" and the "possibility to use WebTransport in P2P operations". | ||||||||||||||||||||||||||||||||||||||||||||||
| - The charter says the group is "considering incubating mechanisms for peer-to-peer capability". That is permission to explore, not a deliverable: the normative spec is still WebTransport itself, and the initial version stays client-server. | ||||||||||||||||||||||||||||||||||||||||||||||
| - So: P2P is in-bounds now. P2P WebTransport standardized? Not yet, and nothing shipped. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### RTCTransport | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| TODO. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - Behind a Chrome feature flag. ICE + DTLS with *no* SCTP and no congestion control. | ||||||||||||||||||||||||||||||||||||||||||||||
| - Basically "give me the raw pipe". I doubt browsers will let this fly unflagged; an unthrottled UDP socket in a webpage is a DDoS button. | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+98
to
+99
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π‘ Minor | β‘ Quick win π Supported by static analysisπ Script executed: sed -n '84,100p' src/pages/blog/web-datagrams.mdx
sed -n '110,114p' src/pages/blog/web-datagrams.mdxRepository: moq-dev/moq.dev Length of output: 1353 π Web query:
π‘ Result: <search_synthesis> <source_evidence> Citations:
Do not describe The proposal gives the application control over packet scheduling and rate control. It also uses transport feedback with a circuit breaker that can stop sending during excessive congestion or abusive behavior. Describe this as application-controlled rate control, not an unrestricted UDP socket. (github.com) Proposed correction-- Behind a Chrome feature flag. ICE + DTLS with *no* SCTP and no congestion control.
-- Basically "give me the raw pipe". I doubt browsers will let this fly unflagged; an unthrottled UDP socket in a webpage is a DDoS button.
+- Behind a Chrome feature flag. ICE + DTLS with *no* SCTP; rate control is application-controlled and protected by transport feedback and a circuit breaker.
+- This is a low-level transport API, not an unthrottled UDP socket.
-| RTCTransport | no | no | yes | flagged |
+| RTCTransport | no | application-controlled | yes | flagged |π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| - TODO: link explainer, and what the congestion control story ends up being. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## TL;DR | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| TODO: table. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| | Approach | Fragmentation | Congestion controlled | P2P | Hack level | | ||||||||||||||||||||||||||||||||||||||||||||||
| |---|---|---|---|---| | ||||||||||||||||||||||||||||||||||||||||||||||
| | WebSocket | n/a | yes (TCP) | no | 0 | | ||||||||||||||||||||||||||||||||||||||||||||||
| | Data channel message | yes | yes (SCTP) | yes | 1 | | ||||||||||||||||||||||||||||||||||||||||||||||
| | Data channel + FORWARD-TSN | no | yes (SCTP) | yes | 2, native only | | ||||||||||||||||||||||||||||||||||||||||||||||
| | Encoded transform (video) | yes | probably | yes | 3 | | ||||||||||||||||||||||||||||||||||||||||||||||
| | Encoded transform (audio) | no | no | yes | 4 | | ||||||||||||||||||||||||||||||||||||||||||||||
| | WebTransport datagram | no | yes (QUIC) | no | 0 | | ||||||||||||||||||||||||||||||||||||||||||||||
| | RTCTransport | no | no | yes | flagged | | ||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This path has no corresponding file under
public/blog/web-datagrams/, so the blog index renders a broken thumbnail and both social-card tags point to a 404. It also makes the required PR workflow fail:.github/workflows/pr.ymlrunsscripts/check-og.ts, whose lines 160β163 reject anyog:imageortwitter:imagewithout a file indist. Add the raster asset or reference an existing valid cover before merging.Useful? React with πΒ / π.