sharedworker 是浏览器端实现全局唯一 websocket 长连接的唯一可行方案,须同源 https/localhost,safari 有版本限制,websocket 必须在 worker 内创建并管理连接、路由、重连与状态同步,页面仅通过 postmessage 发送指令和接收消息。

SharedWorker 是目前浏览器端实现全局唯一 WebSocket 长连接的唯一可行方案;其他方式(BroadcastChannel 代理、Service Worker 中转、localStorage 轮询)都无法真正复用 TCP 连接,且极易引发竞态或状态错乱。
SharedWorker 必须同源 HTTPS 或 localhost 才能启动
浏览器会静默拒绝非安全上下文中的 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 对象不可序列化。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 所有连接逻辑(
new WebSocket(url)、onopen、onmessage、onclose)必须写在shared-worker.js中 - 页面只发送指令,例如
{ type: "connect", url: "wss://api.example.com" } - Worker 收到后检查
ws.readyState === WebSocket.OPEN,未连接才新建,避免重复new
消息路由必须显式携带 clientId,否则多个窗口会互相污染
单链路 ≠ 单业务流。A 窗口订阅 order/123,B 窗口订阅 chat/456,它们不该收到彼此数据。Worker 不自动分发,全靠协议层标识控制。
- 页面首次接入时发
{ type: "init", clientId: "tab-7f3a" }(可用self.crypto.randomUUID()生成) - Worker 用
Map存储clientId → port映射,并在每个port.onmessage中校验clientId字段 - 服务端下发的消息需含
targetClientId或topic,Worker 查表后只调用对应port.postMessage() - 页面关闭前主动发
{ type: "disconnect", clientId: "tab-7f3a" },Worker 清理映射与订阅关系
重连、心跳、状态同步必须全部下沉到 SharedWorker 内部
如果把重连逻辑放在页面里,多个标签页会同时触发重连请求,造成服务端雪崩;如果依赖主线程定时器,页面切后台后心跳会失效。
- 心跳由 Worker 单点发起:
setInterval(() => ws.send("ping"), 30000),不依赖页面活跃状态 - 断连后执行指数退避重连(1s → 2s → 4s → … 最大 30s),避免请求堆积
- 重连成功后,自动重发各窗口缓存的
subscribe请求(需在 Worker 内维护订阅快照) - 通过
port.postMessage({ type: "status", state: "connecting" })向所有页面同步连接状态,UI 可据此更新指示器
最容易被忽略的是:SharedWorker 的生命周期虽独立于单个页面,但它本身没有持久化能力;一旦所有同源页面关闭,Worker 就会被销毁,此时若未清理 WebSocket 实例或未广播最终状态,服务端可能长时间保留已失效连接。务必在 self.onclose 或 self.onoffline 中做兜底释放。










