Socket.IO 连接状态恢复(Connection State Recovery)实战指南:断线重连后不丢消息、不掉房间
Socket.IO 连接状态恢复Connection State Recovery实战指南断线重连后不丢消息、不掉房间【免费下载链接】socket.ioBidirectional and low-latency communication for every platform项目地址: https://gitcode.com/gh_mirrors/so/socket.io导读本指南以 examples/connection-state-recovery-example 为例完整讲解 Socket.IO 4.6 引入的连接状态恢复Connection State Recovery特性当客户端网络临时中断时服务端会在maxDisconnectionDuration窗口内备份会话与消息客户端重连后自动恢复房间、socket.data以及断开期间错过的消息并可通过socket.recovered区分恢复连接与全新连接。读完本文你将掌握该特性的配置方式、两端ESM/CJS完整可运行示例、与普通重连的本质区别以及源码层面会话恢复的完整调用链。本文对应的示例仓库还提供 csr.gif 运行效果演示图可直观看到连接中断后状态恢复的过程。为什么需要连接状态恢复从重连到恢复在没有启用该特性之前客户端断线重连reconnection只能保证重新建立一条新的连接但以下状态会全部丢失客户端断线期间服务端广播的消息missed packets该 Socket 加入的房间rooms附着在socket.data上的应用状态原有socket.id新连接会生成全新的 id。连接状态恢复特性正是为了解决这一问题只要断线时间在maxDisconnectionDuration允许的备份时长内服务端就能在客户端带着pidsession id与offset最后一次收到消息的偏移量重连时从适配器中还原出上述全部状态并补发错过的数据包。从 packages/socket.io/lib/index.ts 的类型定义可以看出恢复的状态明确包含三部分The connection state includes the missed packets, the rooms the socket was in and thedataattribute.需要强调的是连接状态恢复依赖适配器Adapter在内存中缓存会话数据因此它不能与无状态的多节点横向扩展如使用socket.io/redis-adapter的集群部署直接兼容这一点在规划生产架构时需要特别注意。快速上手选择 ES Modules 或 CommonJS 运行示例仓库在 examples/connection-state-recovery-example 下同时提供了两种模块语法的完整实现esm/使用 ES Modulestype: modulecjs/使用 CommonJStype: commonjs。两者的业务代码完全一致仅模块导入语法不同。按以下步骤运行# 选择你的模块语法ES Modules 或 CommonJS 二选一 $ cd esm/ # 安装依赖 $ npm i # 启动服务端 $ node index.js然后在浏览器中访问http://localhost:3000即可看到效果。示例依赖socket.io^4.7.2见 esm/package.json连接状态恢复特性从 4.6.0 版本开始可用请确保依赖版本不低于 4.6.0。服务端配置connectionStateRecovery选项详解服务端核心配置位于 esm/index.jsimport { Server } from socket.io; const io new Server(httpServer, { connectionStateRecovery: { // the backup duration of the sessions and the packets maxDisconnectionDuration: 2 * 60 * 1000, // whether to skip middlewares upon successful recovery skipMiddlewares: true, }, });connectionStateRecovery选项支持两个子参数默认值定义见 packages/socket.io/lib/index.ts参数默认值说明maxDisconnectionDuration1200002 分钟会话与数据包在服务端的备份时长毫秒。断线时间超过该阈值后恢复将失败客户端只能建立全新连接skipMiddlewarestrue恢复成功时是否跳过命名空间中间件middlewares。true表示恢复的连接不再重复执行鉴权等中间件直接进入已连接状态如果希望关闭该特性只需不传connectionStateRecovery选项即可默认关闭。在源码中服务端会在初始化时将默认值与用户传入值合并见 packages/socket.io/lib/index.ts。通过socket.recovered区分恢复连接与全新连接在connection事件回调中可以用socket.recovered判断本次连接是恢复还是新建见 esm/index.jsio.on(connection, (socket) { console.log(connect ${socket.id}); if (socket.recovered) { console.log(recovered!); console.log(socket.rooms:, socket.rooms); console.log(socket.data:, socket.data); } else { console.log(new connection); socket.join(sample room); socket.data.foo bar; } socket.on(disconnect, (reason) { console.log(disconnect ${socket.id} due to ${reason}); }); });这里展示了一个非常典型的恢复判定模式首次连接socket.recovered false执行初始化逻辑——加入房间socket.join(sample room)、写入应用数据socket.data.foo bar恢复连接socket.recovered true不再重复初始化直接读取恢复出来的socket.rooms与socket.data即可继续业务。服务端还会定时每秒向所有客户端广播一条带时间戳的ping消息见 esm/index.jssetInterval(() { io.emit(ping, new Date().toISOString()); }, 1000);该广播用于验证断线期间错过的消息会在恢复后补发这一核心能力。客户端侧如何触发断线并观察恢复过程客户端页面 esm/index.html 展示了完整的观察逻辑script src/socket.io/socket.io.js/script script const socket io({ reconnectionDelay: 5000 // 1000 by default }); socket.on(connect, () { // ... setTimeout(() { // close the low-level connection and trigger a reconnection socket.io.engine.close(); }, Math.random() * 5000 1000); }); socket.on(ping, (value) { // 将最新的消息插入列表顶部最多保留 10 条 }); /script关键点说明reconnectionDelay: 5000将重连间隔从默认的 1000ms 调大到 5000ms让断线状态有足够的展示时间便于肉眼观察错过的消息在恢复后被补发。socket.io.engine.close()这是演示的精髓——它主动关闭的是底层 Engine.IO 连接而不是执行socket.disconnect()。这样一来上层 Socket.IO 会话不会被主动终止重连后才有机会触发状态恢复。页面状态栏Status显示连接状态connected / disconnectedRecovered?直接显示socket.recovered的布尔值配合Latest messages消息列表可以直观对比恢复前错过哪些消息、恢复后补发了哪些消息。页面的完整运行效果可参考仓库中的 csr.gif 演示动图页面显示Status: connected、Recovered? false以及逐条到达的带时间戳消息。源码剖析会话恢复的完整调用链示例只用了三行配置但背后是一整套会话恢复机制。理解这条调用链有助于在生产环境中调优或排查问题。第一步握手时生成pid客户端记录offset开启恢复特性后新连接在 packages/socket.io/lib/socket.ts 中会额外生成一个持久化会话标识if (this.server._opts.connectionStateRecovery) { this.pid base64id.generateId(); }客户端在握手时拿到pidsession id并持续记录自己最后收到的数据包偏移量offset。这两个值就是断线重连时找回会话的凭证。第二步重连握手携带pid与offset客户端重连时会在握手认证信息auth中携带pid与offset。服务端在 packages/socket.io/lib/namespace.ts 的_createSocket方法中取出并调用适配器还原会话const sessionId auth.pid; const offset auth.offset; if ( this.server.opts.connectionStateRecovery typeof sessionId string typeof offset string ) { let session; try { session await this.adapter.restoreSession(sessionId, offset); } catch (e) { debug(error while restoring session: %s, e); } if (session) { debug(connection state recovered for sid %s, session.sid); return new Socket(this, client, auth, session); } } return new Socket(this, client, auth);只有当connectionStateRecovery开启、且auth.pid与auth.offset都是字符串时才会尝试恢复若restoreSession返回会话则直接以previousSession构造 Socket否则退回普通新连接。第三步Socket 构造时整体还原状态当恢复成功时packages/socket.io/lib/socket.ts 的构造函数会一次性还原全部状态if (previousSession) { this.id previousSession.sid; this.pid previousSession.pid; previousSession.rooms.forEach((room) this.join(room)); this.data previousSession.data as SocketData; previousSession.missedPackets.forEach((packet) { this.packet({ type: PacketType.EVENT, data: packet, }); }); this.recovered true; }可以看到四个关键动作恢复原有socket.id即sid与pid重新join恢复前所在的全部房间还原socket.data应用状态逐个补发missedPackets中错过的事件包最后将recovered置为true——这正是示例中socket.recovered判断的依据。第四步skipMiddlewares跳过中间件在 packages/socket.io/lib/namespace.ts 中恢复成功且skipMiddlewares开启时会直接跳过命名空间中间件if ( this.server.opts.connectionStateRecovery?.skipMiddlewares socket.recovered client.conn.readyState open ) { return this._doConnect(socket, fn); }这意味着鉴权、限流等中间件不会对恢复的连接重复执行——这在恢复时无需重新鉴权的场景下可以显著降低延迟但如果你希望每次恢复都重新鉴权例如 token 已过期需要强制重新登录则应把skipMiddlewares设为false。第五步开启恢复后emit走广播通道保证可补发从 packages/socket.io/lib/socket.ts 可以看到开启恢复后socket.emit()不再直接发送数据包而是通过适配器走一次定向广播if (this.nsp.server.opts.connectionStateRecovery) { // this ensures the packet is stored and can be transmitted upon reconnection this.adapter.broadcast(packet, { rooms: new Set([this.id]), except: new Set(), flags, }); } else { this.notifyOutgoingListeners(packet); this.packet(packet, flags); }注释写得很清楚this ensures the packet is stored and can be transmitted upon reconnection——只有经过适配器广播数据包才会被会话缓存才能在恢复时作为missedPackets补发。这是恢复不丢消息的底层保证。测试佐证socket.recovered与消息补发的验证仓库中的测试用例 packages/socket.io/test/connection-state-recovery.ts 完整验证了上述行为首个用例 should restore session and missed packets建立连接、加入room1、记录pid与offset先后发送广播hello1、房间广播hello2、定向消息hello3然后模拟客户端带着pid/offset重新握手断言恢复后的会话能收到错过的数据包测试中多次断言socket.recovered分别为false新连接与true恢复连接与示例代码中的判定逻辑一一对应见 packages/socket.io/test/connection-state-recovery.ts。这份测试同时表明恢复机制覆盖了三种消息形态——命名空间广播、房间广播、服务端到客户端的定向消息serverSocket.emit都可以在恢复后补发。注意事项与适用边界仅在内存中备份会话与数据包缓存在适配器的内存中进程重启即丢失多进程/多节点集群需使用支持共享存储的适配器方案但需确认其与连接状态恢复的兼容性。超时即失效断线时间超过maxDisconnectionDuration默认 2 分钟后restoreSession将返回空客户端只能得到一条全新连接recovered false需要重新执行初始化与鉴权。幂等设计由于恢复连接会补发断线期间的消息应用层应保证消息处理逻辑具备幂等性避免重复投递造成副作用。skipMiddlewares需按业务权衡默认true跳过中间件以获得低延迟恢复若恢复也必须通过鉴权请显式设为false。版本要求连接状态恢复自 Socket.IO 4.6.0 起可用示例的package.json声明了socket.io: ^4.7.2见 esm/package.json请确保服务端与客户端版本匹配且不低于 4.6.0。总结连接状态恢复特性把 Socket.IO 从断线重连提升到了断线恢复的层次通过connectionStateRecovery选项、maxDisconnectionDuration与skipMiddlewares两个参数配合socket.recovered标志位开发者可以用极少的代码实现断线不丢消息、不掉房间、不丢数据的体验。本文示例esm 与 cjs 两种语法可直接复制运行背后的会话恢复调用链pid/offset握手 →restoreSession→ Socket 构造还原 →missedPackets补发也在 packages/socket.io/lib/namespace.ts 与 packages/socket.io/lib/socket.ts 中有完整实现可作为生产落地时的调优依据。【免费下载链接】socket.ioBidirectional and low-latency communication for every platform项目地址: https://gitcode.com/gh_mirrors/so/socket.io创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

pyasc 反正弦算子 asin 接口全解析:从 Python 调用到 Ascend C 代码发射

pyasc 反正弦算子 asin 接口全解析:从 Python 调用到 Ascend C 代码发射

pyasc 反正弦算子 asin 接口全解析:从 Python 调用到 Ascend C 代码发射 【免费下载链接】pyasc 本项目为Python用户提供算子编程接口,支持在昇腾AI处理器上加速计算,接口与Ascend C一一对应并遵守Python原生语法。 项目地址: https://gitc…

2026/9/18 9:30:08 阅读更多 →
服务器硬件选型与运维实战:从RAID到BMC带外管理

服务器硬件选型与运维实战:从RAID到BMC带外管理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 9:30:08 阅读更多 →
四足机器人强化学习:Isaac Lab中奖励函数设计的5个实战技巧

四足机器人强化学习:Isaac Lab中奖励函数设计的5个实战技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 12:41:58 阅读更多 →

最新新闻

从规范到发布:用SDD驾驭AI协作开发npm工具包的实践

从规范到发布:用SDD驾驭AI协作开发npm工具包的实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 20:20:54 阅读更多 →
QQ空间历史说说全量备份 5 分钟跑通:扫码一次,Excel 与网页双格式导出指南

QQ空间历史说说全量备份 5 分钟跑通:扫码一次,Excel 与网页双格式导出指南

QQ空间历史说说全量备份 5 分钟跑通:扫码一次,Excel 与网页双格式导出指南 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory GetQzonehistory 是个 QQ 空间说说备份…

2026/9/20 20:20:54 阅读更多 →
JetBrains AI Assistant 配置实战:从安装到高效使用的完整指南

JetBrains AI Assistant 配置实战:从安装到高效使用的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 20:20:54 阅读更多 →
ANSYS Electromagnetics Suite 19 R2安装排障与有限元仿真实践指南

ANSYS Electromagnetics Suite 19 R2安装排障与有限元仿真实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 20:20:54 阅读更多 →
主力吸筹与主力轨迹:通达信资金动量指标公式解析

主力吸筹与主力轨迹:通达信资金动量指标公式解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 20:20:54 阅读更多 →
BoxMOT:给检测模型配上 ID 稳定的多目标追踪,10 分钟跑通

BoxMOT:给检测模型配上 ID 稳定的多目标追踪,10 分钟跑通

BoxMOT:给检测模型配上 ID 稳定的多目标追踪,10 分钟跑通 【免费下载链接】boxmot BoxMOT: Pluggable Python and C SOTA multi-object tracking modules with support for axis-aligned and oriented bounding boxes 项目地址: https://gitcode.com/G…

2026/9/20 20:19:54 阅读更多 →

日新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/20 0:00:46 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/20 0:00:46 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/20 0:00:46 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/20 0:00:46 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/20 0:00:46 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/20 0:00:46 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/19 23:01:36 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/19 17:50:38 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →