返回首页

WebSocket + SharedWorker 多标签实时通信

布莱克2026-05-09 10:05已编辑
Tip:文章封面与内容无关,作者旅游时拍摄,因为没什么值得把四季都错过!

WebSocket —— 全双工实时通信

传统HTTP的问题:

  • 单向请求:客户端不请求,服务器不能主动发数据
  • 每次请求都有HTTP头开销
  • 轮询方式浪费资源


// 客户端连接示例
const ws = new WebSocket('ws://localhost:8080');
// 监听连接打开
ws.onopen = () => {
  console.log('连接建立');
  ws.send('Hello Server'); // 发送消息
};
// 监听接收消息
ws.onmessage = (event) => {
  console.log('收到消息:', event.data); // 服务器主动推送
};
// 监听错误
ws.onerror = function(error) {
  console.log('连接错误:', error);
};

// 监听关闭 
//1. 主动关闭:代码调用了 socket.close()
//2. 服务端主动断开连接
//3. 网络中断(断网、WiFi 切换等)
//4. 服务端崩溃或重启
//5. 心跳超时后代码调用 socket.close()
//6. 浏览器关闭页面(如果是普通 WebSocket)
ws.onclose = function(event) {
  console.log('连接关闭:', event.code, event.reason);
  // event.code: 1000(正常关闭), 1001(离开), 1006(异常关闭)等
};

特点:

  • 一次握手,持久连接
  • 双向实时通信
  • 轻量级协议头(2-10字节)
  • 支持二进制传输


WebSocket核心状态:

CONNECTING (0) - 正在连接

// 状态特征:
// - WebSocket 对象刚创建
// - 正在进行 TCP 握手 + HTTP 升级
// - 还不能发送消息

OPEN (1) - 已连接

// 状态特征
// - 握手成功
// - 可以双向通信
// - 可以发送和接收消息

CLOSING (2) - 正在关闭

// 状态特征:
// - 正在关闭连接
// - 不能发送新消息
// - 可能还在接收最后的消息

CLOSED (3) - 已关闭

// 状态特征:
// - 连接完全断开
// - 不能再发送或接收消息
// - 可以重新创建新连接


WebSocket心跳保活机制:

心跳保活是一种通过定时发送小数据包来检测和维护 WebSocket 连接健康状态的机制。

生活类比:

  • 就像两个人通电话时,时不时问一句"你还在吗?"
  • 如果对方回答"在",说明连接正常
  • 如果没有回答,说明可能断线了
// 简单的心跳实现
setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send('ping');  // 发送心跳
  }
}, 30000); // 每30秒发送一次

ws.onmessage = (event) => {
  if (event.data === 'pong') {
    console.log('心跳正常');
  }
};


为什么需要心跳保活?

WebSocket 虽然叫"持久连接",但实际网络环境中有很多因素会导致连接意外断开:

/*
真实场景:
1. 客户端和服务器建立了 WebSocket 连接
2. 30分钟没有数据传输
3. 路由器/防火墙/NAT认为这个连接已经失效
4. 悄悄把连接断开了
5. 客户端和服务端都不知道连接已断
6. 当有数据要发时,才发现连接已死
*/

心跳保活机制的作用:

  1. 检测幽灵连接:发现那些已经断开但浏览器没通知的连接
  2. 维持网络映射:保持NAT/防火墙的连接映射不过期
  3. 快速故障发现:秒级发现断线,而不是等几分钟
  4. 节省服务器资源:及时清理无效连接
  5. 提升用户体验:断线后快速重连,减少消息丢失


降级方案:重连 + 消息缓存

当心跳检测到连接异常时,不是简单地放弃,而是通过自动重连消息缓存来保证服务的连续性和消息的可靠性

基础重连方案:

class BasicReconnection {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5; //最大重连次数
    this.reconnectDelay = 1000;
    this.isManualClose = false; // 是否正常关闭
  }
  
  connect() {
    this.ws = new WebSocket(this.url);
    
    this.ws.onopen = () => {
      console.log('连接成功');
      this.reconnectAttempts = 0; // 重置重连计数
    };
    
    this.ws.onclose = (event) => {
      console.log(`连接关闭: ${event.code}`);
      
      // 如果不是主动关闭,尝试重连
      if (!this.isManualClose && event.code !== 1000) {
        this.reconnect();
      }
    };
    
    this.ws.onerror = (error) => {
      console.error('连接错误:', error);
    };
  }
  
  reconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('达到最大重连次数,停止重连');
      this.notifyReconnectFailed();
      return;
    }
    
    // 指数退避算法:1s, 2s, 4s, 8s, 16s...
    const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts);
    console.log(`${delay}ms 后进行第${this.reconnectAttempts + 1}次重连`);
    
    setTimeout(() => {
      this.reconnectAttempts++;
      this.connect();
    }, delay);
  }
  
  close() {
    this.isManualClose = true;
    if (this.ws) {
      this.ws.close(1000, '用户主动关闭');
    }
  }
  
  notifyReconnectFailed() {
    // 显示提示给用户
    alert('无法连接到服务器,请刷新页面重试');
  }
}


SharedWorker —— 多标签页共享线程

如果你在应用中使用了 WebSocket 或长轮询,用户每开一个标签页,服务器就会多一个连接。这不仅浪费客户端内存,也会给后端服务器带来巨大的压力。

传统WebWorker的限制:

  • 每个标签页独立的Worker
  • 无法在标签页间共享数据

SharedWorker核心特性:

1. 独立线程:运行在后台,不阻塞UI
2. 多标签共享:同源下所有标签页可连接同一个Worker
3. 生命周期:只要有标签页连接就存活,最后一个断开后销毁
4. 独立作用域:有自己的全局对象,不能访问DOM


SharedWorker 和 WebWorker 的区别

核心区别:
1. 生命周期不同
   - WebWorker: 跟随创建它的标签页,标签页关闭则销毁
   - SharedWorker: 所有连接的标签页关闭后才销毁

2. 通信范围不同
   - WebWorker: 只能与创建它的页面通信
   - SharedWorker: 可以与同源下所有标签页通信

3. 创建方式不同
   - WebWorker: new Worker('worker.js')
   - SharedWorker: new SharedWorker('worker.js')

4. 通信端口不同
   - WebWorker: 直接使用 worker.postMessage()
   - SharedWorker: 需要通过 port 通信


SharedWorker + WebSocket

初始化:第一个打开的页面启动 Shared Worker。

建立连接:Shared Worker 内部建立一个 WebSocket 连接。

分发数据:当 WebSocket 收到服务器消息,Worker 遍历所有已连接的 ports,将数据广播出去。

发送数据:页面将消息发给 Worker,由 Worker 通过唯一的 WebSocket 发送给后端。


assistant