sharedworker 是浏览器端唯一能复用单条 websocket 连接的方案,需同源、https 安全上下文,连接逻辑必须全部在 worker 内实现,页面仅通过 postmessage 发送指令并携带 clientid 实现多标签隔离与状态同步。

SharedWorker 是目前浏览器端唯一能真正复用单条 WebSocket 连接、降低服务端并发连接数的方案。其他方式(BroadcastChannel + 主页面代理、localStorage 轮询、Service Worker)要么无法共享连接实例,要么状态不可靠或兼容性差。
SharedWorker 必须同源且运行在安全上下文
浏览器会静默拒绝非 HTTPS 或非 localhost 的 SharedWorker 构造调用——连 onerror 都不会触发。开发时务必确认:
- 本地调试用
http://localhost:3000,不能用file://协议 - 上线必须是
https://;HTTP 域名即使同源也会直接失败 - Safari 仅支持 macOS 13.3+ / iOS 16.4+,旧版本需 fallback 到单页独连
WebSocket 实例只能在 SharedWorker 内创建,不能传入或共享
常见错误是页面中 new WebSocket() 后试图通过 postMessage 传给 SharedWorker,这会抛出 DOMException: Failed to execute 'postMessage'。原因很直接:WebSocket 对象不可序列化,也不支持跨线程传递。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 所有连接逻辑(
new WebSocket(url)、onopen、onmessage、onclose)必须写在ws-manager.js中 - 页面只发指令,例如
{ type: "connect", url: "wss://api.example.com" } - SharedWorker 收到后检查
ws.readyState === WebSocket.OPEN,未连接才新建,避免重复new
消息路由必须显式携带 clientId,否则多个标签页会互相污染
单连接不等于单业务流。A 标签页订阅了 order/123,B 订阅了 chat/456,它们不该收到对方的数据。SharedWorker 不会自动做隔离,全靠协议层控制。
- 页面首次通信发
{ type: "init", clientId: "tab-7f3a" }(推荐用crypto.randomUUID()生成) - SharedWorker 用
Map存储clientId → port映射,并在每次port.onmessage中校验字段 - 服务端返回的消息必须含
targetClientId或topic,Worker 查表后只port.postMessage()给匹配端口 - 页面关闭前主动发
{ type: "disconnect", clientId: "tab-7f3a" },Worker 清理映射与订阅关系
重连、心跳、状态同步必须全部下沉到 SharedWorker 内部
如果把重连逻辑放在页面里,多个标签页会同时触发请求,造成服务端雪崩;如果依赖主线程定时器,页面切后台后心跳就停摆。这些都必须由 SharedWorker 独立承担。
- 心跳用
setInterval(() => ws.send("ping"), 30000),不依赖页面活跃状态 - 断连后执行指数退避:1s → 2s → 4s → … 上限 30s,避免重连风暴
- 重连成功后,自动重发各窗口缓存的
subscribe请求(需在 Worker 内维护订阅快照) - 通过
port.postMessage({ type: "status", state: "connecting" })向所有页面广播连接状态,UI 可据此更新指示器
最容易被忽略的一点是:SharedWorker 的生命周期虽比页面长,但它本身没有持久化能力。一旦所有同源页面关闭,Worker 就会被浏览器终止,此时若没清理好资源(如未 ws.close()),可能留下半开连接。务必在 self.onclose 或监听最后一个 port 断开时,主动关闭 WebSocket 并清空缓存。










