WebTransport datagrams怎样传递低延迟不可靠消息
如果页面传的是实时光标、游戏状态、传感器快照或进度提示,最重要的消息往往不是“每一条都到达”,而是“尽快看到还没过时的那一条”。WebTransport datagrams 就是为这类低延迟、允许丢失且不要求按序的消息准备的通道。
本文只讨论浏览器端的 WebTransport datagrams:如何连接、发送、接收,以及怎样处理大小上限、写入背压和过期消息。官方资料入口:
官方地址:https://developer.mozilla.org/en-US/docs/Web/API/WebTransportDatagramDuplexStream
核心做法是:连接就绪后通过transport.datagrams取得读写流,写入前等待writer.ready,把每条消息控制在maxDatagramSize内;接收端按序号或时间戳丢弃旧状态。datagram 不保证送达,也不保证顺序,必须把这个事实写进业务协议。
先判断消息是否真的适合 datagram
WebTransport 同时提供可靠的 stream 和不可靠的 datagram。stream 更适合配置、文件片段、聊天正文、订单状态这类不能缺一段的数据;datagram 更适合“新值会覆盖旧值”的状态快照。
| 消息特征 | 建议通道 | 原因 |
|---|---|---|
| 缺一条就无法还原内容 | 双向或单向 stream | 需要可靠、有序的字节流 |
| 只关心最新位置、角度或进度 | datagram | 旧消息晚到时可以直接丢弃 |
| 小型实时状态,同时还要发送控制命令 | datagram + stream | 状态追求时延,命令追求可靠 |
“不可靠”不是“没有加密”。WebTransport 通过安全上下文连接 HTTP/3 服务端,datagram 的不可靠性指的是消息可能丢失、可能乱序,并不是让应用绕过传输层安全。

先把连接和读写端准备好
WebTransport 构造函数需要一个 HTTPS 地址,服务端必须提供对应的 WebTransport 会话。连接建立后再使用 transport.datagrams,可以避免把“还在连接中”和“已经能发消息”混为一谈。
async function openDatagramChannel(url) {
// URL 必须指向 HTTPS 的 WebTransport 服务端,不能填普通 HTTP 接口。
const transport = new WebTransport(url, {
// 这是给浏览器的拥塞控制倾向,不等于业务层的可靠性保证。
congestionControl: 'low-latency'
});
try {
// ready 成功后才开始依赖 datagrams 发送和接收。
await transport.ready;
// createWritable 是较新的写入入口,不支持时回退到 writable 属性。
const writable = typeof transport.datagrams.createWritable === 'function'
? transport.datagrams.createWritable()
: transport.datagrams.writable;
return { transport, writable };
} catch (error) {
// 连接失败时交给上层切换到降级通道或提示用户,不要静默丢失状态。
await transport.close().catch(() => {});
throw error;
}
}
这里的 congestionControl: 'low-latency' 只是用户代理可参考的倾向,不能替代消息过期策略。某些浏览器或运行环境不支持 createWritable() 时,示例保留了旧的 writable 访问方式;实际项目应按自己的兼容范围决定是否保留回退。
发送消息时,把背压和大小上限当成协议边界
datagram 的发送端不是一个无限容量的邮箱。maxDatagramSize 给出单条 outgoing datagram 的最大尺寸;writer.ready 反映写队列是否有空间。写入失败只说明这一条没有交付,不能用重试把 datagram 伪装成可靠队列。
function createStateSender(transport, writable) {
const writer = writable.getWriter();
const encoder = new TextEncoder();
return async function sendState(state) {
// 先编码再计算长度,不能用 JavaScript 字符串长度代替字节长度。
const bytes = encoder.encode(JSON.stringify({
// 序号帮助接收端拒绝迟到的旧状态。
sequence: state.sequence,
// 时间戳只用于过期判断,不代表服务端一定使用本机时钟。
sentAt: Date.now(),
value: state.value
}));
// 超过单条上限时直接跳过;需要拆分的数据应该改用 stream。
if (bytes.byteLength > transport.datagrams.maxDatagramSize) {
return { sent: false, reason: 'too-large' };
}
try {
// 队列满时等待可写,避免无界地把实时状态堆在内存里。
await writer.ready;
await writer.write(bytes);
return { sent: true };
} catch (error) {
// datagram 写入被拒绝时只报告本条失败,是否补发由业务决定。
return { sent: false, reason: 'write-failed', error };
}
};
}
实时状态通常不应该把每个中间值都补发。比如拖动滑块时,可以只保留最新状态;如果业务必须记录每一条变化,那它已经更接近可靠事件流,应考虑双向 stream 或在应用层设计明确的确认、重传和去重协议。
接收端按序号和年龄过滤旧消息
接收端通过 transport.datagrams.readable 读取 Uint8Array。由于 datagram 可能乱序,不能简单地“读到什么就覆盖什么”。下面的示例把序号较小的包当作迟到状态,并用本地年龄阈值过滤已经失去意义的更新。
async function consumeStates(transport, applyState) {
const reader = transport.datagrams.readable.getReader();
const decoder = new TextDecoder();
let lastSequence = -1;
const maxAgeMs = 250;
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
// 每个 datagram 都要单独解析;解析失败时跳过坏包而不是终止整个会话。
let message;
try {
message = JSON.parse(decoder.decode(value));
} catch {
continue;
}
// 序号不递增说明消息迟到,实时状态不值得为它回滚。
if (!Number.isInteger(message.sequence) || message.sequence maxAgeMs) {
continue;
}
lastSequence = message.sequence;
applyState(message.value);
}
} finally {
// 组件销毁或会话关闭时释放 reader 锁,避免阻塞其他读取方。
reader.releaseLock();
}
}
如果更希望让浏览器在流层面淘汰过期包,可以结合 incomingMaxAge 等 datagram 设置;不过业务仍应保留序号或版本号,因为网络中的乱序和应用状态的新旧关系不是同一个概念。

什么时候该换成 stream 或混合通道
可以把判断压缩成一个问题:消息晚到或缺失时,用户还能否直接使用下一条状态?如果答案是否定的,就不要只用 datagram。
- 配置和控制命令:用可靠 stream,必要时在应用层返回确认。
- 实时快照:用 datagram,携带单调递增的版本号,旧包直接丢弃。
- 大对象或连续字节:用 stream,不要试图把内容拆成许多 datagram 后自行重组。
- 同一会话的两类消息:让控制面走 stream,让状态面走 datagram,分别设置超时和关闭策略。
WebTransport 的价值不在于把 WebSocket 换一个名字,而在于让可靠流、不可靠消息和多路会话可以被放到同一个连接模型里。采用前先画出消息价值,而不是先决定所有数据都走最快的通道。
上线前保留这张简明清单
| 检查点 | 要确认的事实 |
|---|---|
| 连接 | 使用 HTTPS,服务端确实提供 WebTransport;连接失败有降级路径 |
| 大小 | 发送前检查 maxDatagramSize,不要把大消息硬拆成隐式协议 |
| 背压 | 写入前等待 writer.ready,实时状态只保留业务真正需要的最新值 |
| 顺序 | 消息携带序号、版本或时间戳,接收端拒绝迟到旧状态 |
| 关闭 | 释放 reader/writer,并处理 ready、closed 和单条写入错误 |
相关问题
WebTransport datagrams 会保证消息送达吗?
不会。它们允许丢失,也可能乱序;需要可靠送达的内容应改用 WebTransport stream,或由业务层明确设计确认和重传。
为什么要在写入前等待 writer.ready?
因为写队列可能暂时没有空间。等待 ready 可以把发送节奏和队列背压连接起来,减少实时状态在本地无界堆积。
单条 datagram 太大怎么办?
先判断消息是否应该改用 stream。只有在协议明确支持分片、丢弃和重组时才自行拆分;否则拆成多条不可靠包会把一个大小问题变成可靠性问题。
WebTransport datagrams 最适合“尽快看到新状态,旧状态没有保存价值”的消息。把大小、年龄、顺序和背压写进协议后,低延迟才不会变成难以排查的随机丢数据。
WebSocketStream怎样用 Streams API 处理背压
- 上一篇
- WebSocketStream怎样用 Streams API 处理背压
- 下一篇
- JavaScript Uint8Array.fromBase64怎样避免手写 Base64 解码
-
- 文章 · 前端 | 56分钟前 |
- JavaScript Uint8Array.fromBase64怎样避免手写 Base64 解码
- 251浏览 收藏
-
- 文章 · 前端 | 1小时前 |
- WebSocketStream怎样用 Streams API 处理背压
- 500浏览 收藏
-
- 文章 · 前端 | 1小时前 |
- Web Crypto Ed25519怎样生成与验证签名
- 194浏览 收藏
-
- 文章 · 前端 | 1小时前 |
- JavaScript Iterator.prototype.drop怎样跳过前 N 个元素
- 456浏览 收藏
-
- 文章 · 前端 | 1小时前 |
- JavaScript Iterator.from怎样把类迭代对象统一转换
- 396浏览 收藏
-
- 文章 · 前端 | 1小时前 | 前端 · CSS 单页应用 document.startViewTransition View Transitions view-transition-name
- CSS view transitions怎样为同文档状态切换设置过渡
- 494浏览 收藏
-
- 文章 · 前端 | 1小时前 | 兼容性 · CSS animation-timeline timeline-scope Scroll-driven Animations scroll-timeline-name
- CSS timeline-scope怎样让不同滚动容器共享命名时间线
- 112浏览 收藏
-
- 文章 · 前端 | 1小时前 |
- CSS animation-timeline怎样按滚动进度驱动进度条
- 346浏览 收藏
-
- 文章 · 前端 | 1小时前 | 前端 · CSS anchor positioning anchor-name position-anchor anchor-size 弹出层宽度
- CSS anchor-size怎样让弹出层宽度跟随触发按钮
- 348浏览 收藏
-
- 文章 · 前端 | 1小时前 |
- HTML details name 怎样让手风琴只展开一个面板
- 129浏览 收藏
-
- 文章 · 前端 | 1小时前 | 前端 · javascript · css · 浏览器兼容 · CSS 兼容性 表单 输入框 渐进增强 field-sizing
- CSS field-sizing 不支持时怎么给自适应输入框做降级
- 293浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 888次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 888次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 971次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 957次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 782次使用
-
- Go语言对前端领域的入侵WebAssembly运行原理
- 2022-12-31 130浏览
-
- golang网络通信超时设置方式
- 2022-12-30 182浏览
-
- Go Server-Sent Events 怎么正确刷新事件并关闭连接
- 2026-09-07 491浏览
-
- go+gin 静态资源路由与后端api路由冲突如何解决?
- 2023-02-24 414浏览
-
- 分片上传文件,后端接收怎么生成了一个文件名为blob的文件?
- 2023-01-09 163浏览
