WebSocket断线后恢复消息顺序
WebSocket 断线后想恢复消息顺序,关键不是让浏览器的 WebSocket 对象“记住”上一条消息,而是给业务消息增加可比较的应用层序号。客户端持久化最后一个已经应用的 sequence,重连时把它作为游标发给服务端;服务端先回放游标之后的缺口,再把实时流交给客户端。客户端还要按序号去重,并在回放追平前暂存实时消息。
RFC 6455 约束的是一条连接上的消息和分片顺序。连接断开以后,新的连接不会自动知道旧连接哪些消息已经送达,也不会替应用补发断线期间的事件。因此,顺序恢复属于应用层协议设计。
先定义顺序契约:WebSocket 有序不等于断线可恢复
可以把每条业务事件表示成下面的对象:
{
// sequence 是服务端为同一业务流单调递增的应用层序号
sequence: 101,
stream: "orders:user-42",
type: "order.updated",
payload: { status: "paid" }
}
这里至少要约定三件事:
- 同一条业务流的
sequence单调递增,不能用客户端收到消息的时间代替。 - 客户端只有在业务状态真正应用成功后,才推进
lastAppliedSeq。 - 回放接口和 WebSocket 实时推送使用同一套序号,不要各自从 1 开始计数。

例如客户端最后应用到 100,重连后请求 after=100,服务端应返回 101、102、103。这样客户端恢复的是一个明确的序号区间,而不是“从大概断开的时间点继续推送”。
保存最后游标:只在应用成功后推进
游标可以放在内存中,也可以按业务需要放到本地存储。重要的是推进时机:收到消息不代表状态已经应用,解析失败、版本不兼容或业务更新失败时都不能提前确认。
const cursorKey = "orders:last-applied-seq";
let lastAppliedSeq = Number(localStorage.getItem(cursorKey) || 0);
function applyEvent(event) {
// 小于等于游标的事件已经处理过,重连回放时直接幂等跳过
if (event.sequence
真实项目中,updateOrderStore 还应具备幂等性。只按序号去重适合单一业务流;如果一条事件会更新多个客户端表,服务端也应使用事件 ID 或版本号辅助排查。
重连后请求回放:让服务端明确返回缺口
重连成功后不要立即假设实时推送已经覆盖断线期间的消息。可以先通过 HTTP 回放接口获取缺口,再建立实时订阅;也可以在 WebSocket 握手后的第一条业务消息中发送游标。下面用一个抽象的回放接口表示前一种方式:
async function replayAfter(cursor) {
// 服务端按同一条业务流的 sequence 升序返回缺失事件
const response = await fetch(`/api/orders/events?after=${cursor}`);
if (!response.ok) {
throw new Error(`replay failed: ${response.status}`);
}
const events = await response.json();
// 回放结果仍按序应用,不把数组下标当成业务序号
for (const event of events) {
applyEvent(event);
}
return events.at(-1)?.sequence ?? cursor;
}
服务端必须说明回放保留多久、游标过旧时返回什么,以及回放是否包含边界值。常见约定是返回 sequence > after 的事件;如果游标已经早于事件保留窗口,则返回“需要全量同步”,而不是静默返回一段不完整的数据。
合并回放与实时流:用切换水位避免实时消息插队
最容易出错的场景是:客户端重连后已经开始接收 204、205,但 HTTP 回放还在返回 201、202、203。若两个通道直接同时调用 applyEvent,界面就可能先应用 204,再应用 201。
一个简单的模式是“回放阶段先入缓冲,追平后再切换实时流”。实时消息先进入 liveBuffer;回放完成后按序合并,最后才允许实时消息直接进入状态层:
let recovering = true;
const liveBuffer = new Map();
function onLiveMessage(event) {
// 回放未追平时先按 sequence 暂存,防止实时消息插队
if (recovering) {
liveBuffer.set(event.sequence, event);
return;
}
applyIfNext(event);
}
function applyIfNext(event) {
// 只接受下一个连续序号;旧消息也能安全跳过
if (event.sequence

生产实现通常会把“切换水位”做得更明确:服务端在回放响应中返回一个快照水位 highWatermark,客户端先应用不超过该水位的回放事件,再消费缓冲中大于水位的实时事件。这样比单纯依赖网络先后更容易解释和排错。
出现重复、缺口或游标过旧怎么办
| 现象 | 处理方式 | 不要做什么 |
|---|---|---|
| 重复 sequence | 按 sequence 或 eventId 幂等跳过 | 不要因为重复就把整个连接判为坏连接 |
| 出现 sequence 缺口 | 暂停应用并重新回放缺口 | 不要直接按到达顺序渲染 |
| 游标早于保留窗口 | 请求全量快照,再从快照水位继续 | 不要假装回放成功 |
| 业务应用失败 | 保留旧游标,记录事件并重试或进入人工补偿 | 不要先写入新游标 |
一个可落地的判断清单
如果只需要短连接内的顺序,WebSocket 自身的消息交付语义通常够用;只要需求包含断线补发、页面切后台后追平、多个标签页恢复或服务端重启后的继续消费,就应该把下面四个字段写进应用协议:
- 业务流标识
stream。 - 单调递增的
sequence或可排序的事件版本。 - 客户端最后成功应用的
lastAppliedSeq。 - 回放不可用时的全量同步入口和快照水位。
常见问题
WebSocket 重连后会自动收到断线期间的消息吗?
不会。浏览器会创建新的连接,缺失消息是否可恢复取决于服务端是否保存事件,以及应用协议是否提供回放或同步机制。
只记录客户端收到消息的时间可以吗?
不建议。客户端时间可能漂移,且多个消息可能拥有相同时间戳。使用服务端分配的同一业务流序号更容易判断缺口和去重。
回放接口和实时 WebSocket 必须是两个接口吗?
不必须。它们可以共用一个协议,只要都能表达游标、序号、回放边界和切换水位;分成 HTTP 回放加 WebSocket 实时推送只是更容易拆分实现的一种方案。
OffscreenCanvas 怎样把 Worker 绘制结果显示到主线程
- 上一篇
- OffscreenCanvas 怎样把 Worker 绘制结果显示到主线程
- 下一篇
- CSS field-sizing怎样让textarea随内容自动增高
-
- 文章 · 前端 | 33分钟前 | xml · javascript · 前端开发 · JavaScript dom xml lookupNamespaceURI 默认命名空间
- JavaScript怎样查询XML节点当前生效的默认命名空间
- 109浏览 收藏
-
- 文章 · 前端 | 36分钟前 | 前端 · JavaScript content-box ResizeObserver 容器尺寸
- ResizeObserver处理容器尺寸变化
- 282浏览 收藏
-
- 文章 · 前端 | 55分钟前 | javascript · css · 前端开发 · 浏览器API · CSS选择器 querySelector CSSOM CSS.escape
- CSS.escape怎样拼接带特殊字符的选择器
- 177浏览 收藏
-
- 文章 · 前端 | 59分钟前 | 前端开发 · 浏览器API · CSS View Transition API view-transition-name view-transition-class 伪元素动画
- CSS view-transition-class 怎样让多元素复用过渡样式
- 413浏览 收藏
-
- 文章 · 前端 | 1小时前 | javascript · 前端开发 · 浏览器API · Shadow DOM adoptedStyleSheets CSSStyleSheet replaceSync Constructable Stylesheets
- CSSStyleSheet.replaceSync怎样批量更新构造样式
- 500浏览 收藏
-
- 文章 · 前端 | 1小时前 | 前端 · javascript · Fetch AbortController ReadableStream TextDecoder
- Fetch流式读取ReadableStream响应
- 326浏览 收藏
-
- 文章 · 前端 | 16小时前 | javascript · 前端开发 · 浏览器API · URLPattern pathname URLPattern.exec 命名路由参数 baseURL
- URLPattern exec 怎样提取命名路由参数
- 461浏览 收藏
-
- 文章 · 前端 | 16小时前 | javascript · web安全 · 前端开发 · 浏览器API · writeText Permissions Policy Clipboard API clipboard-write 安全上下文 用户激活
- Clipboard API 写入文本需要什么权限上下文
- 390浏览 收藏
-
- 文章 · 前端 | 16小时前 | html · javascript · 前端开发 · 可访问性 · 浏览器API · JavaScript 模态对话框 ::backdrop HTML dialog showModal inert top layer
- HTML dialog showModal 怎样阻止背景页面交互
- 160浏览 收藏
-
- 文章 · 前端 | 16小时前 | javascript · 前端开发 · 浏览器API · @import Shadow DOM CSSStyleSheet replaceSync Constructable Stylesheets
- CSSStyleSheet replaceSync 为什么忽略 @import 规则
- 333浏览 收藏
-
- 文章 · 前端 | 16小时前 | key 前端路由 Navigation API currentEntry NavigationHistoryEntry currententrychange
- Navigation API currentEntry 怎样读取当前导航状态
- 348浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 1040次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 1039次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1182次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1143次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 987次使用
-
- golang基于websocket通信tcpkeepalive研究记录
- 2022-12-23 311浏览
-
- golang实现一个简单的websocket聊天室功能
- 2022-12-28 447浏览
-
- Golang使用WebSocket通信的实现
- 2022-12-26 493浏览
-
- 使用Go语言创建WebSocket服务的实现示例
- 2022-12-28 156浏览
-
- golang websocket 服务端的实现
- 2023-02-16 370浏览

