sharedworker 是唯一能实现多标签页共用同一 websocket 长连接的方案,需在 sharedworker 内创建连接、用 clientid 路由消息、由 worker 统一管理心跳重连,并为 safari 旧版本提供 broadcastchannel 降级方案。

WebSocket 本身不支持跨标签页共享连接实例,因为每个 new WebSocket() 都会新建一条 TCP 连接。要实现多标签页共用**同一个长连接**、真正降低服务端并发压力,必须把连接逻辑完全抽离出页面,交由一个跨上下文共享的执行环境统一管理——目前唯一可行且被主流浏览器支持的方案是 SharedWorker。
SharedWorker 是唯一能复用 TCP 连接的机制
普通 Worker 每个标签页独立实例;Service Worker 生命周期不可控,无法主动维持长连接;只有 SharedWorker 被设计为同源下所有页面(tab/iframe)共用同一运行时环境。只要还有一个同源页面开着,它就持续运行,WebSocket 实例就能保持存活。
- 所有页面必须用完全相同的路径创建实例:
new SharedWorker('./ws-manager.js')(协议、域名、端口、文件名全一致) - WebSocket 必须在 SharedWorker 脚本内创建,不能由页面 new 后传入 —— WebSocket 对象不可序列化,
postMessage会直接报错 - 开发调试必须用
http://localhost或上线用https://;file://、http://127.0.0.1、非同源路径均静默失败
消息路由必须靠 clientId 显式隔离
单连接 ≠ 全广播。A 页面订阅的订单更新不能推给 B 页面,否则业务逻辑混乱。SharedWorker 不自动分发,需靠协议层标识精准路由:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 每个页面首次通信时生成唯一
clientId(推荐crypto.randomUUID()),发送{ type: "init", clientId: "tab-abc123" } - Worker 用
Map<port clientid></port>维护映射关系,并校验后续每条消息是否携带合法clientId - 服务端返回的消息必须带
targetClientId或topic字段,Worker 查表后只调用对应port.postMessage(),绝不广播 - 页面关闭前主动发
{ type: "disconnect", clientId: "tab-abc123" },Worker 清理映射与缓存的订阅状态,防止内存泄漏
心跳与重连必须全部下沉到 SharedWorker 内部
页面切后台或冻结时,主线程定时器会失准甚至暂停,导致心跳中断、服务端误判掉线。SharedWorker 独立于页面活跃状态,是唯一可靠位置:
- 心跳用
setInterval(() => ws.send("ping"), 30000)写在 Worker 内,服务端需响应"pong" - 连续两次未在超时窗口(如 45s)内收到 pong,就主动
ws.close()并触发重连 - 重连必须指数退避(1s → 2s → 4s → … 上限 30s),且仅由 Worker 单点执行,避免多标签页同时重连引发服务端雪崩
- 断连期间缓存各页面的
subscribe请求,重连成功后自动重发
兼容性兜底不能省略
Safari 直到 macOS 13.3+ / iOS 16.4+ 才支持 SharedWorker。旧版本必须降级:
- 先检测
typeof SharedWorker === 'function',不支持则 fallback 到 BroadcastChannel + 主从选举 - 主从选举可用
localStorage时间戳竞拍:各页面尝试写入ws_owner,以最新时间戳者为真主 - 主控页负责建连并用
new BroadcastChannel('app-socket-bus')广播状态(ws-open/ws-closed),其他页面监听并同步 UI - 该方案仍为多连接,但比无协调的各自建连更可控,适合过渡期
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










