当前位置:首页 > 文章列表 > 文章 > 前端 > WebTransport datagrams怎样传递低延迟不可靠消息

WebTransport datagrams怎样传递低延迟不可靠消息

来源:17golang原创 2026-11-05 03:51:01 0浏览 收藏

如果页面传的是实时光标、游戏状态、传感器快照或进度提示,最重要的消息往往不是“每一条都到达”,而是“尽快看到还没过时的那一条”。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 datagrams 在浏览器与 HTTP3 服务端之间传递低延迟不可靠状态的关系说明图
图1:浏览器、WebTransport datagrams 和 HTTP/3 服务端之间的关系说明;可丢失与可能乱序属于业务需要承担的传输语义。

先把连接和读写端准备好

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 设置;不过业务仍应保留序号或版本号,因为网络中的乱序和应用状态的新旧关系不是同一个概念。

WebTransport datagrams 的发送队列、writer.ready、大小上限和接收过期丢弃边界说明图
图2: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 最适合“尽快看到新状态,旧状态没有保存价值”的消息。把大小、年龄、顺序和背压写进协议后,低延迟才不会变成难以排查的随机丢数据。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
WebSocketStream怎样用 Streams API 处理背压WebSocketStream怎样用 Streams API 处理背压
上一篇
WebSocketStream怎样用 Streams API 处理背压
JavaScript Uint8Array.fromBase64怎样避免手写 Base64 解码
下一篇
JavaScript Uint8Array.fromBase64怎样避免手写 Base64 解码
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    888次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    888次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    971次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    957次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    782次使用