实现可靠的 WebSocket 连接:心跳与自动重连的最佳实践

概览

本文将手把手教你如何从零编写一个可用于直播或在线聊天的 WSocket 类,依次实现连接建立、心跳检测、断线重连、消息收发以及资源清理等功能。我们将结合 WebSocket API 的标准用法、心跳保持重连策略,并充分运用现代 JavaScript 语法(如类、解构、箭头函数)来确保代码既高效又易读。


1. 创建基础 WebSocket 连接

1.1 引入与初始化

首先,新建一个 WSocket 类,接受目标 URL 和回调函数作为构造参数:

javascript 复制代码
class WSocket {
  constructor({ url, onMessage, onDisconnect }) {
    this.url = url;
    this.onMessage = onMessage;
    this.onDisconnect = onDisconnect;
    this.ws = null;
    this.reconnectCount = 0;
  }
}
  • url:WebSocket 服务器地址。

  • onMessage /onDisconnect:消息与断开回调。

1.2 建立连接

在类中添加 connect() 方法,使用原生 API 建立并绑定事件:

javascript 复制代码
connect() {
  this.ws = new WebSocket(this.url);
  this.ws.binaryType = 'arraybuffer';                                // 二进制类型  
  this.ws.onopen    = () => this.handleOpen();                      // 连接成功:contentReference[oaicite:0]{index=0}  
  this.ws.onmessage = e  => this.handleMessage(e);                  // 收到消息  
  this.ws.onclose   = e  => this.handleClose(e);                    // 连接关闭  
  this.ws.onerror   = e  => this.handleError(e);                    // 错误处理  
}

2. 实现心跳机制

2.1 为什么要心跳

许多网络设备会在连接空闲一定时长后断开,心跳可保持连接活跃并及时发现断线MDN Web Docs

2.2 心跳代码

javascript 复制代码
const DEFAULTS = { HEART_INTERVAL: 10000, STATUS_CHECK: 3000 };

handleOpen() {
  this.clearTimers();
  this.startHeartbeat();
}

startHeartbeat() {
  this.heartbeatTimer = setInterval(() => {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({ type: 'PING' }));             // 心跳包  
    }
  }, DEFAULTS.HEART_INTERVAL);

  this.statusCheckTimer = setInterval(() => {
    if (this.ws.readyState !== WebSocket.OPEN) {
      this.reconnect();                                            // 状态检查:contentReference[oaicite:3]{index=3}  
    }
  }, DEFAULTS.STATUS_CHECK);
}

clearTimers() {
  clearInterval(this.heartbeatTimer);
  clearInterval(this.statusCheckTimer);
}

3. 自动重连策略

3.1 基本重连

在断线或错误时,调用 reconnect()

javascript 复制代码
reconnect() {
  if (this.reconnecting) return;
  this.reconnecting = true;
  this.clearTimers();
  setTimeout(() => {
    this.reconnectCount++;
    if (this.reconnectCount <= 5) {                                // 最多重连5次  
      this.connect();
    } else {
      this.onDisconnect();
    }
    this.reconnecting = false;
  }, 2000);                                                       // 延迟2秒重连:contentReference[oaicite:5]{index=5}
}

handleClose(e) {
  console.warn('WebSocket closed:', e);
  this.reconnect();
}

handleError(e) {
  console.error('WebSocket error:', e);
  this.reconnect();
}

4. 消息接收与分发

handleMessage 中解析并交给用户回调:

javascript 复制代码
handleMessage(event) {
  try {
    const data = event.data instanceof ArrayBuffer
      ? JSON.parse(new TextDecoder().decode(event.data))
      : JSON.parse(event.data);
    this.onMessage(data);
  } catch (err) {
    console.error('Message parse error:', err);
  }
}
  • 兼容二进制/文本 :用 TextDecoder 解码二进制MDN Web Docs

  • 异常捕获 :防止单条消息解析失败导致整个连接中断Stack Overflow


5. 完整示例代码

javascript 复制代码
class WSocket {
  constructor({ url, onMessage, onDisconnect }) {
    this.url = url;
    this.onMessage = onMessage;
    this.onDisconnect = onDisconnect;
    this.ws = null;
    this.heartbeatTimer = null;
    this.statusCheckTimer = null;
    this.reconnecting = false;
    this.reconnectCount = 0;
  }

  connect() {
    this.ws = new WebSocket(this.url);
    this.ws.binaryType = 'arraybuffer';
    this.ws.onopen    = () => this.handleOpen();
    this.ws.onmessage = e => this.handleMessage(e);
    this.ws.onclose   = e => this.handleClose(e);
    this.ws.onerror   = e => this.handleError(e);
  }

  handleOpen() {
    this.reconnectCount = 0;
    this.clearTimers();
    this.startHeartbeat();
    console.log('WebSocket connected');
  }

  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({ type: 'PING' }));
      }
    }, DEFAULTS.HEART_INTERVAL);

    this.statusCheckTimer = setInterval(() => {
      if (this.ws.readyState !== WebSocket.OPEN) this.reconnect();
    }, DEFAULTS.STATUS_CHECK);
  }

  clearTimers() {
    clearInterval(this.heartbeatTimer);
    clearInterval(this.statusCheckTimer);
  }

  reconnect() {
    if (this.reconnecting) return;
    this.reconnecting = true;
    this.clearTimers();
    setTimeout(() => {
      this.reconnectCount++;
      if (this.reconnectCount <= 5) {
        this.connect();
      } else {
        this.onDisconnect();
      }
      this.reconnecting = false;
    }, 2000);
  }

  handleMessage(event) {
    try {
      const raw = event.data instanceof ArrayBuffer
        ? new TextDecoder().decode(event.data)
        : event.data;
      const data = JSON.parse(raw);
      this.onMessage(data);
    } catch (err) {
      console.error('Message parse error:', err);
    }
  }

  handleClose(e) {
    console.warn('WebSocket closed:', e);
    this.reconnect();
  }

  handleError(e) {
    console.error('WebSocket error:', e);
    this.reconnect();
  }

  send(data) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(typeof data === 'string' ? data : JSON.stringify(data));
    }
  }

  disconnect() {
    this.clearTimers();
    this.ws?.close();
  }
}

6. 使用示例

javascript 复制代码
const ws = new WSocket({
  url: 'wss://example.com/live',
  onMessage: msg => console.log('Recv:', msg),
  onDisconnect: () => alert('Connection lost')
});

ws.connect();

// 发送消息
ws.send({ type: 'CHAT', content: 'Hello world' });

// 手动断开
// ws.disconnect();
相关推荐
码农捻旧1 小时前
解决Mongoose “Cannot overwrite model once compiled“ 错误的完整指南
javascript·数据库·mongodb·node.js·express
淡笑沐白1 小时前
探索Turn.js:打造惊艳的3D翻页效果
javascript·html5·turn.js
sunxunyong2 小时前
yarn任务筛选spark任务,判断内存/CPU使用超过限制任务
javascript·ajax·spark
Ynov2 小时前
详细解释api
javascript·visual studio code
三天不学习3 小时前
一文讲透 Vue3 + Three.js 材质属性之皮革篇【扫盲篇】
javascript·webgl·three.js·材质
不爱吃饭爱吃菜4 小时前
uniapp微信小程序-长按按钮百度语音识别回显文字
前端·javascript·vue.js·百度·微信小程序·uni-app·语音识别
程序员拂雨5 小时前
Angular 知识框架
前端·javascript·angular.js
zhengddzz5 小时前
从卡顿到丝滑:JavaScript性能优化实战秘籍
开发语言·javascript·性能优化
Go_going_5 小时前
ajax,Promise 和 fetch
javascript·ajax·okhttp