SharedWorker 是唯一能真正实现多页面共享单个 WebSocket 连接的机制,其他方案本质仍是多连接;其构造失败无报错因非安全上下文静默拒绝,须用 localhost 或 HTTPS;Safari 仅支持新版本,旧版需降级;WebSocket 实例不可跨线程传递,必须在 Worker 内创建;需用 clientId 路由消息,避免广播混乱;重连与心跳须由 Worker 单点控制,配合指数退避与端口生命周期管理。
sharedworker 是唯一能真正实现多页面共享单个 websocket 连接的机制,其他方案(broadcastchannel + 页面代理、localstorage 轮询)本质仍是多连接,无法规避服务端频控、状态错乱和竞态问题。
SharedWorker 构造失败却无报错?检查协议与上下文
浏览器在非安全上下文中会静默拒绝 new SharedWorker(),连 onerror 都不会触发。常见误判是以为代码写错了,其实是运行环境不合规:
- 开发阶段必须用
http://localhost:port,禁用file://协议 - 上线后必须是
https://,哪怕同域名的http://也会直接失败 - Safari 仅支持 macOS 13.3+ / iOS 16.4+,旧版本需 fallback 到单页独连
WebSocket 实例不能传入 SharedWorker?所有连接逻辑必须下沉
试图在页面中 new WebSocket() 后通过 postMessage 传给 SharedWorker,必然抛出 DOMException: Failed to execute 'postMessage' on 'MessagePort'——因为 WebSocket 对象不可序列化。
-
onopen、onmessage、onclose必须写在shared-worker.js内 - 页面只发指令,例如
{ type: "connect", url: "wss://api.example.com" } - Worker 收到后检查
ws?.readyState === WebSocket.OPEN,未连接才新建,避免重复实例
多个页面共用连接,但消息不能全量广播?必须带 clientId 路由
服务端下发的消息若无目标标识,Worker 无法判断该推给谁。A 页面订阅了 order/123,B 页面订阅了 chat/456,它们不该互相收到对方数据。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 页面首次通信时必须携带唯一
clientId,推荐用crypto.randomUUID()生成 - Worker 用
Map维护clientId → port映射,并在校验port.onmessage时检查字段合法性 - 服务端消息需带
targetClientId或topic,Worker 查表后仅对匹配port.postMessage() - 页面关闭前主动发
{ type: "disconnect", clientId: "tab-7f3a" },Worker 清理映射与订阅关系
重连和心跳失效?必须由 SharedWorker 单点控制
若把重连或心跳放在页面主线程,切后台后定时器暂停,或多个标签页同时触发重连请求,会导致服务端雪崩。
- 断开后使用指数退避:第一次等
1000ms,第二次2000ms,第三次4000ms……上限30000ms - 心跳由 Worker 内
setInterval(() => ws.send("ping"), 30000)发起,不依赖页面活跃状态 - 每次发送 ping 前记录
lastPingSent = Date.now(),连续 2 次未收到pong则ws.close()并触发重连 - 重连前必须检查
ws?.readyState === WebSocket.CLOSED,防止重复new WebSocket()
最易被忽略的是端口生命周期管理:SharedWorker 不会自动感知页面是否关闭或崩溃,port 引用残留会导致消息发不出、内存泄漏、重连卡死。必须监听 port.onclose 或定期调用 self.clients.matchAll() 核对活跃端口,空列表时主动 ws.close() 并清空定时器。

















