ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Socket.IO 连接状态恢复(Connection State Recovery)实战指南:断线重连后不丢消息、不掉房间

2026/9/18 9:30:01 拓冰建站 浏览量
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),仅供参考