关闭

青桃传媒

全国

本文将介绍 WebSocket 的封装,比如:心跳机制,重连和一些问题如何去处理

青桃传媒·2025-03-14 07:09:32·阅读

HTTP 是基于请求-响应模型的,客户端必须发起请求,服务器才能返回数据。如果需要实时更新(如股票价格、在线聊天),通常需要使用轮询()或长轮询(Long ),这会导致:

其实 HTTP 是可以实现的,如果 HTTP 请求频繁三次握手和四次挥手的操作会占用大量资源,HTTP/1.1 以后开启了 Keep-Alive (长连接),可以复用连接,但是实时的情况下,响应模型仍然会导致较高的延迟和资源消耗。

相比之下, 通过一次握手建立连接以后,就可以保持双向通信,服务器可以主动推送数据,无需客户端轮询。解决了 HTTP 带来的一些痛点。

四、封装

我们将实现以下几个功能点:

4.1 版本

class ReSocket {
  constructor(url, options = {}) {
    this.url = url; // WebSocket 服务器地址
    this.options = options; // 可选参数
    this.socket = null; // WebSocket 实例
    this.maxReconnectTimes = options.maxReconnectTimes || 5; // 最大重连次数
    this.reconnectTimes = 0; // 当前重连次数
    this.reconnectInterval = options.reconnectInterval || 3000; // 重连间隔时间(毫秒)
    this.isClosed = false; // 是否已关闭
    this.isOpen = false; // 是否已打开
    this.isConnect = false; // 是否已连接
    this.isReconnecting = false; // 是否正在重连
    this.isDestroyed = false; // 是否已销毁
    this.reconnectTimer = null; // 重连定时器
    this.heartbeatTimer = null; // 心跳定时器
    this.heartbeatInterval = options.heartbeatInterval || 30000; // 心跳间隔时间(默认30秒)
    this.heartbeatData = options.heartbeatData || "ping"; // 心跳数据
    this.onMessageCallback = null; // 消息接收回调
    this.onOpenCallback = null; // 连接成功回调
    this.onCloseCallback = null; // 连接关闭回调
  }
  
  // 创建WebSocket实例
  createSocket() {
    this.socket = new WebSocket(this.url);
    this.socket.onopen = () => {
      this.isOpen = true;
      this.isConnect = true;
      this.reconnectTimes = 0; // 重连次数归零
      this.startHeartbeat(); // 启动心跳机制
      if (this.onOpenCallback) this.onOpenCallback(); // 调用连接成功回调
    };
    this.socket.onmessage = event => {
      if (this.onMessageCallback) this.onMessageCallback(event.data); // 调用消息接收回调
    };
    this.socket.onclose = () => {
      this.isOpen = false;
      this.isConnect = false;
      this.stopHeartbeat(); // 停止心跳机制
      if (this.onCloseCallback) this.onCloseCallback(); // 调用连接关闭回调
      if (!this.isClosed && this.reconnectTimes < this.maxReconnectTimes) {
        this.reconnect(); // 尝试重连
      }
    };
    this.socket.onerror = error => {
      console.error("WebSocket 错误: ", error); // 错误处理
    };
  }
  // 开始连接
  connect() {
    if (this.isDestroyed) return; // 如果已销毁,则不再连接
    this.createSocket(); // 创建WebSocket实例
  }
  // 重连
  reconnect() {
    if (this.isReconnecting || this.reconnectTimes >= this.maxReconnectTimes)
      return; // 防止重复重连
    this.isReconnecting = true;
    this.reconnectTimes++; // 增加重连次数
    this.reconnectTimer = setTimeout(() => {
      console.log(`正在重连... (${this.reconnectTimes})`); // 打印重连次数
      this.createSocket(); // 再次创建WebSocket实例
      this.isReconnecting = false; // 重连状态设置为false
    }, this.reconnectInterval); // 按设定时间重连
  }
  // 发送消息
  send(data) {
    if (this.isOpen) {
      this.socket.send(data); // 发送数据
    } else {
      console.error("WebSocket 未打开,无法发送消息。"); // 提示错误
    }
  }
  // 设置消息接收回调
  onMessage(callback) {
    this.onMessageCallback = callback; // 绑定接收消息的回调
  }
  // 设置连接成功回调
  onOpen(callback) {
    this.onOpenCallback = callback; // 绑定连接成功的回调
  }
  // 设置连接关闭回调
  onClose(callback) {
    this.onCloseCallback = callback; // 绑定连接关闭的回调
  }
  // 启动心跳机制
  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.isOpen) {
        this.send(this.heartbeatData); // 发送心跳数据
      }
    }, this.heartbeatInterval); // 按设定的时间间隔发送
  }
  // 停止心跳机制
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer); // 清除心跳定时器
      this.heartbeatTimer = null;
    }
  }
  // 关闭连接
  close() {
    this.isClosed = true; // 设置为已关闭
    this.isOpen = false;
    this.socket.close(); // 关闭WebSocket连接
    this.stopHeartbeat(); // 停止心跳机制
    clearTimeout(this.reconnectTimer); // 清除重连定时器
  }
  // 销毁实例
  destroy() {
    this.isDestroyed = true; // 设置为已销毁
    this.close(); // 关闭连接
  }
}

4.2 版本

type ReSocketOptions = {
  maxReconnectTimes?: number; // 最大重连次数
  reconnectInterval?: number; // 重连间隔时间(毫秒)
  heartbeatInterval?: number; // 心跳间隔时间(毫秒)
  heartbeatData?: string; // 心跳数据
};
class ReSocket {
  private url: string;
  private socket: WebSocket | null = null;
  private maxReconnectTimes: number;
  private reconnectTimes: number = 0;
  private reconnectInterval: number;
  private isClosed: boolean = false;
  private isOpen: boolean = false;
  private isConnect: boolean = false;
  private isReconnecting: boolean = false;
  private isDestroyed: boolean = false;
  private reconnectTimer: NodeJS.Timeout | null = null;
  private heartbeatTimer: NodeJS.Timeout | null = null;
  private heartbeatInterval: number;
  private heartbeatData: string;
  private onMessageCallback: ((message: string) => void) | null = null;
  private onOpenCallback: (() => void) | null = null;
  private onCloseCallback: (() => void) | null = null;
  constructor(url: string, options: ReSocketOptions = {}) {
    this.url = url;
    this.maxReconnectTimes = options.maxReconnectTimes || 5;
    this.reconnectInterval = options.reconnectInterval || 3000;
    this.heartbeatInterval = options.heartbeatInterval || 30000;
    this.heartbeatData = options.heartbeatData || 'ping';
  }
  private createSocket(): void {
    this.socket = new WebSocket(this.url);
    this.socket.onopen = () => {
      this.isOpen = true;
      this.isConnect = true;
      this.reconnectTimes = 0;
      this.startHeartbeat();
      if (this.onOpenCallback) this.onOpenCallback();
    };
    this.socket.onmessage = (event: MessageEvent) => {
      if (this.onMessageCallback) this.onMessageCallback(event.data);
    };
    this.socket.onclose = () => {
      this.isOpen = false;
      this.isConnect = false;
      this.stopHeartbeat();
      if (this.onCloseCallback) this.onCloseCallback();
      if (!this.isClosed && this.reconnectTimes < this.maxReconnectTimes) {
        this.reconnect();
      }
    };
    this.socket.onerror = (error: Event) => {
      console.error("WebSocket 错误: ", error);
    };
  }
  public connect(): void {
    if (this.isDestroyed) return;
    this.createSocket();
  }
  private reconnect(): void {
    if (this.isReconnecting || this.reconnectTimes >= this.maxReconnectTimes) return;
    this.isReconnecting = true;
    this.reconnectTimes++;
    this.reconnectTimer = setTimeout(() => {
      console.log(`正在重连... (${this.reconnectTimes})`);
      this.createSocket();
      this.isReconnecting = false;
    }, this.reconnectInterval);
  }
  public send(data: string): void {
    if (this.isOpen && this.socket) {
      this.socket.send(data);
    } else {
      console.error("WebSocket 未打开,无法发送消息。");
    }
  }
  public onMessage(callback: (message: string) => void): void {
    this.onMessageCallback = callback;
  }
  public onOpen(callback: () => void): void {
    this.onOpenCallback = callback;
  }
  public onClose(callback: () => void): void {
    this.onCloseCallback = callback;
  }
  private startHeartbeat(): void {
    this.heartbeatTimer = setInterval(() => {
      if (this.isOpen && this.socket) {
        this.send(this.heartbeatData);
      }
    }, this.heartbeatInterval);
  }
  private stopHeartbeat(): void {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }
  public close(): void {
    this.isClosed = true;
    this.isOpen = false;
    if (this.socket) {
      this.socket.close();
    }
    this.stopHeartbeat();
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
    }
  }
  public destroy(): void {
    this.isDestroyed = true;
    this.close();
  }
}
export { ReSocket };

4.3 如何使用?

首先简单写个 ws 的服务,我的 Node 环境是 20.18.0

创建一个 的文件夹 打开执行:

npm init -y

生成完毕 .json 之后,我们安装 ws :

npm i ws

创建 app.js 写一个简单服务 :

const WebSocket = require("ws");
// 创建 WebSocket 服务器,监听端口 8080
const wss = new WebSocket.Server({ port: 8080 });
// 监听客户端连接
wss.on("connection", (ws) => {
  console.log("客户端已连接");
  // 监听客户端发送的消息
  ws.on("message", (message) => {
    console.log("收到客户端消息:", message);
    // 向客户端发送回复
    ws.send(`服务器回复: ${message}`);
  });
  // 发送一条欢迎消息给客户端
  ws.send("欢迎连接 WebSocket 服务器");
});
// 打印服务器地址
console.log("WebSocket 服务器已启动: ws://localhost:8080");

执行运行命令 :

node .\app.js

这里可以先用一个 调试工具试试是否创建成功 这里我是用的是 在线测试工具 ,效果如下图:

本文将介绍 WebSocket 的封装,比如:心跳机制,重连和一些问题如何去处理

看到欢迎连接的时候,说明我们这个服务已经成功启动了,接下来就是 中如何使用了,创建一个 index.html ,然后引入我们封装好的

html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ws 调试title>
head>
<script src="./socket.js">script>
<script>
    var ws = new ReSocket('ws://localhost:8080');
    ws.connect()
    ws.onMessage((res) => {
        console.log('onMessage', res)
    })
script>
<body>
body>
html>

打开浏览器之后在控制台中看日志,如图:

在网络中我们需要在这里看:

本文将介绍 WebSocket 的封装,比如:心跳机制,重连和一些问题如何去处理

到这里,如果你跟着做了一遍,你已经掌握了,如果感觉现在没时间,可以收藏,点赞,留个标记,毕竟收藏等于学会了

五、我的痛点如何处理

其实我的封装对于很多浏览器都是可以跑的,如果你复制去跑不了,那就人跑,明白我的意思吧?好了,其实这个封装,没有一些特殊兼容,比如:

等等...

所以,需要各位根据自己使用场景,简单的需求基本上还是可以用的,如果场景涵盖比较多,这时候就可以优先考虑三方库使用

浏览器生命周期事件: 当我在移动设备息屏时,我的 确实不会触发心跳,然后后端就给我挂了,那么我们浏览器其实提供了一个 给我们使用,可以这样写:

// 页面可见性监听
document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "visible") {
    console.log("页面可见,尝试恢复 WebSocket 连接...");
    if (!socket.isConnect) {
      socket.connect(); // 页面可见时尝试恢复连接
    }
  } else {
    console.log("页面隐藏,关闭 WebSocket 连接...");
    socket.close(); // 页面隐藏时关闭连接以节省资源
  }
});

其实,这个我感觉还不太满足我,所以我又添加了一个定时任务来执行检验,代码如下:

// 页面可见性监听
document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "visible") {
    console.log("页面可见,尝试恢复 WebSocket 连接...");
    if (!socket.isConnect) {
      socket.connect();
    }
    lastActiveTime = Date.now(); // 更新最近活动时间
  } else {
    console.log("页面隐藏,关闭 WebSocket 连接...");
    socket.close();
  }
});
// 定时任务 - 检测 WebSocket 状态及页面活跃度
const startCheckInterval = () => {
  checkInterval = setInterval(() => {
    const now = Date.now();
    // 检测 WebSocket 是否断开,尝试重连
    if (!socket.isConnect) {
      console.log("WebSocket 未连接,尝试重连...");
      socket.connect();
    }
    // 检测最近活动时间,判断页面是否处于非活跃状态
    if (now - lastActiveTime > 10000) { // 超过10秒未活动
      console.log("检测到页面可能处于非活跃状态!");
      // 此处可执行其他恢复或提醒操作
    }
  }, 5000); // 每5秒检查一次
};
// 清理定时任务
const clearCheckInterval = () => {
  if (checkInterval) {
    clearInterval(checkInterval);
    checkInterval = null;
  }
};
// 初始化定时任务
startCheckInterval();
// 页面销毁的时候调用 clearCheckInterval 清理

结语

很久没有更新了,狠狠的写了3000多字,希望这篇文章还是对读者们有帮助。最近也是经历了裁员,和找工作一系列的事情,小小吐槽以下,就业环境不容乐观,但是基本上看见这篇文章的读者,都是热爱技术的,多学点知识基本上储备量上去了,面试还是很容易通过的。

我一直都有一个技术交流群,如果有希望玩游戏的朋友可以添加一下,群里有小满zs、鲨鱼哥、还有各个的地方人才,说话很好听,代码写的也很优雅,有问题都能帮你解决,只要你愿意等,那你就可以一直等下去,有意向的朋友可以来玩

本文将介绍 WebSocket 的封装,比如:心跳机制,重连和一些问题如何去处理

最后,看到此刻的你,祝你工作顺利,生活愉快!

加载中~

你可能感兴趣的