当前位置:   article > 正文

赶快收藏!全网最佳websocket封装:完美支持断网重连、自动心跳!

websocket 断网重连

  1. 大厂技术  高级前端  Node进阶
  2. 点击上方 程序员成长指北,关注公众号
  3. 回复1,加入高级Node交流群

简介

websocket在前端开发中,是一个必须掌握的技术!你可以不用,但必须掌握!

前几天,就遇到这样一个需求,要求界面的数据通过websocket实时推送,并且必须支持断网重连、自动心跳

自动心跳是定期向服务端发送小型数据包,如果一段时间内服务端没有收到心跳响应,系统可能会断开连接。

websokect的API非常简单

  1. // 创建ws连接
  2. const ws = new WebSocket('ws://localhost:8080/test');
  3. ws.onopen = function() {
  4.     console.log('WebSocket 连接已经建立。');
  5.     ws.send('Hello, server!');
  6. };
  7. ws.onmessage = function(event) {
  8.     console.log('收到服务器消息:', event.data);
  9. };
  10. ws.onerror = function(event) {
  11.     console.error('WebSocket 连接出现错误:', event);
  12. };
  13. ws.onclose = function() {
  14.     console.log('WebSocket 连接已经关闭。');
  15. }

但是,要封装一个支持断网重连、自动心跳的websokect没有那么容易!

封装成功演示

核心优势

我们先看我封装的websokect,首先,最重要的,它的使用方法和官方Api完全一致!零学习成本,上手即用!

  1. import WebSocketClient from "./WebSocketClient"
  2. // 创建实例
  3. const ws = new WebSocketClient('ws://localhost:3200');
  4. // 连接
  5. ws.connect()
  6. // 同原生方法
  7. ws.onclose(()=>{})
  8. // 同原生方法
  9. ws.onerror(()=>{})
  10. // 同原生方法
  11. ws.onmessage(()=>{
  12.   // 同原生方法
  13.   ws.send("自定义发送的数据")
  14. })
  15. // 同原生方法
  16. ws.onopen(()=>{})
  17. // 关闭连接
  18. ws.close()

效果演示

后端服务创建

我们先使用node创建一个后端服务,安装ws库:

npm install ws

创建node index.js文件,引入WebSocket 服务器

  1. const WebSocket = require("ws");
  2. const wss = new WebSocket.Server({ port: 3200 });
  3. console.log("服务运行在http://localhost:3200/");
  4. wss.on("connection", (ws) => {
  5.   console.log("[服务器]:客官您来了~里边请");
  6.   ws.send(`[websocket云端]您已经连接云端!数据推送中!`);
  7.   let index = 1;
  8.   const interval = setInterval(() => {
  9.     ws.send(`[websocket]数据推送第${index}次`);
  10.     index ++
  11.   }, 1000 * 10);
  12.   ws.on("close", () => {
  13.     clearInterval(interval); // 清除定时器
  14.     console.log("[服务器]:客官下次再来呢~");
  15.   });
  16. });

我们启动这个服务

node index.js

现在,我们在前端服务内进行连接测试

前端websokect测试

我们先写前端的相关逻辑

  1. import { WebSocketClient } from '@/utils/dataDispatcher/WebSocketClient';
  2. const ws = new WebSocketClient('ws://localhost:3200');
  3. // 连接
  4. ws.connect();
  5. // 同原生方法
  6. ws.onclose(() => {});
  7. // 同原生方法
  8. ws.onerror(() => {});
  9. // 同原生方法
  10. ws.onmessage(() => {
  11.     // 同原生方法
  12.     ws.send('自定义发送的数据');
  13. });
  14. // 同原生方法
  15. ws.onopen(() => {});

启动项目,我们会发现控制台已经有了提示

cf9de37e83d28f5c15fc9d889dd1f3c5.jpeg

心跳验证:

等待一段时间后,我们可以看到ws连接里,前端已经发送了多次心跳数据

f02a095224840a084ea0eb44b7ee122b.jpeg

服务端与客户端也一直在进行数据交互

1bf3fd625432840ac7a257dc6866ea7d.jpeg

断网重连验证:

可以看到,当我们断开服务端的时候,断网重连被自动触发。efacab7240fae02ca2f35eec0e14736e.jpeg

技术路线

基本框架搭建

  1. export class WebSocketClient {
  2.     // #socket链接
  3.     private url = '';
  4.     // #socket实例
  5.     private socket: WebSocket | null = null;
  6.     
  7.     constructor(url: string) {
  8.         super();
  9.         this.url = url;
  10.     }
  11.    
  12.     // >消息发送
  13.     public send(message: string): void {
  14.         if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  15.             this.socket.send(message);
  16.         } else {
  17.             console.error('[WebSocket] 未连接');
  18.         }
  19.     }
  20.     // !初始化连接
  21.     public connect(): void {
  22.         if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  23.             return;
  24.         }
  25.         this.socket = new WebSocket(this.url);
  26.         // !websocket连接成功
  27.         this.socket.onopen = event => {
  28.             console.log(`连接成功,等待服务端数据推送[onopen]...`);
  29.         };
  30.         this.socket.onmessage = event => {
  31.         };
  32.         this.socket.onclose = event => {
  33.             console.log(`连接断开[onclose]...`);
  34.         };
  35.         this.socket.onerror = event => {
  36.             console.log(`连接异常[onerror]...`);
  37.         };
  38.     }
  39.     // >关闭连接
  40.     public close(): void {
  41.         if (this.socket) {
  42.             this.socket.close();
  43.             this.socket = null;
  44.         }
  45.     }
  46. }

上述代码借助官方API实现了一个基本的 WebSocket 客户端,具有以下功能:

  • 初始化连接并处理各种 WebSocket 事件(打开、消息、关闭、错误)。

  • 发送消息到服务器

  • 关闭连接。

现在,我们开始逐步完善代码,进行封装。

断网重连封装

  1. export class WebSocketClient{
  2.     // #socket链接
  3.     private url = '';
  4.     // #socket实例
  5.     private socket: WebSocket | null = null;
  6.     // #重连次数
  7.     private reconnectAttempts = 0;
  8.     // #最大重连数
  9.     private maxReconnectAttempts = 5;
  10.     // #重连间隔
  11.     private reconnectInterval = 10000// 10 seconds
  12.   
  13.     constructor(url: string) {
  14.         super();
  15.         this.url = url;
  16.     }
  17.     // >消息发送
  18.     public send(message: string): void {
  19.         if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  20.             this.socket.send(message);
  21.         } else {
  22.             console.error('[WebSocket] 未连接');
  23.         }
  24.     }
  25.     // !初始化连接
  26.     public connect(): void {
  27.         if (this.reconnectAttempts === 0) {
  28.             console.log(`初始化连接中...`);
  29.         }
  30.         if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  31.             return;
  32.         }
  33.         this.socket = new WebSocket(this.url);
  34.         // !websocket连接成功
  35.         this.socket.onopen = event => {
  36.             // 重置重连尝试成功连接
  37.             this.reconnectAttempts = 0;
  38.             console.log(`连接成功,等待服务端数据推送[onopen]...`);
  39.         };
  40.         this.socket.onmessage = event => {
  41.         };
  42.         this.socket.onclose = event => {
  43.             if (this.reconnectAttempts === 0) {
  44.                 console.log(`连接断开[onclose]...`);
  45.             }
  46.             if (!this.stopWs) {
  47.                 this.handleReconnect();
  48.             }
  49.         };
  50.         this.socket.onerror = event => {
  51.             if (this.reconnectAttempts === 0) {
  52.                 console.log(`连接异常[onerror]...`);
  53.             }
  54.         };
  55.     }
  56.     // > 断网重连逻辑
  57.     private handleReconnect(): void {
  58.         if (this.reconnectAttempts < this.maxReconnectAttempts) {
  59.             this.reconnectAttempts++;
  60.             console.log(`尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
  61.             setTimeout(() => {
  62.                 this.connect();
  63.             }, this.reconnectInterval);
  64.         } else {
  65.             console.log(`最大重连失败,终止重连: ${this.url}`);
  66.         }
  67.     }
  68.     // >关闭连接
  69.     public close(): void {
  70.         if (this.socket) {
  71.             this.socket.close();
  72.             this.socket = null;
  73.         }
  74.     }
  75. }

上述代码添加了自动断网重连的机制。其核心逻辑在于以下几个方面:

  1. 记录重连次数:通过 reconnectAttempts 属性记录当前已经尝试重连的次数。

  2. 设置最大重连次数:通过 maxReconnectAttempts 属性设置允许的最大重连次数。

  3. 重连逻辑:在 onclose 和 onerror 事件中调用重连处理函数 handleReconnect。

  4. 重连间隔:通过 reconnectInterval 属性设置每次重连的间隔时间,可以在每次重连时增加间隔以实现指数退避。

初始化连接并处理事件

在 connect 方法中,初始化 WebSocket 连接并为其设置事件处理函数。特别关注 onclose 和 onerror 事件,在连接关闭和出现错误时调用重连逻辑。

  1. public connect(): void {
  2.     if (this.reconnectAttempts === 0) {
  3.         console.log(`初始化连接中...`);
  4.     }
  5.     if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  6.         return;
  7.     }
  8.     this.socket = new WebSocket(this.url);
  9.     this.socket.onopen = (event: Event) => {
  10.         this.reconnectAttempts = 0;
  11.         console.log(`连接成功,等待服务端数据推送[onopen]...`);
  12.     };
  13.     this.socket.onclose = (event: CloseEvent) => {
  14.         if (this.reconnectAttempts === 0) {
  15.             console.log(`连接断开[onclose]...`);
  16.         }
  17.         this.handleReconnect();
  18.     };
  19.     this.socket.onerror = (event: Event) => {
  20.         if (this.reconnectAttempts === 0) {
  21.             console.log(`连接异常[onerror]...`);
  22.         }
  23.         this.handleReconnect();
  24.     };
  25. }

处理重连逻辑

在 handleReconnect 方法中,实现了实际的重连逻辑。该方法会递增 reconnectAttempts,检查是否达到最大重连次数,如果没有达到,则在指定的重连间隔后再次调用 connect 方法尝试重连。

  1. private handleReconnect(): void {
  2.     if (this.reconnectAttempts < this.maxReconnectAttempts) {
  3.         this.reconnectAttempts++;
  4.         console.log(`尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
  5.         setTimeout(() => {
  6.             this.connect();
  7.         }, this.reconnectInterval * this.reconnectAttempts); // 重连间隔可以增加,例如指数退避
  8.     } else {
  9.         console.log(`最大重连失败,终止重连: ${this.url}`);
  10.     }
  11. }

关闭连接

在 close 方法中,手动关闭 WebSocket 连接并将 socket 设置为 null。

  1. public close(): void {
  2.     if (this.socket) {
  3.         this.socket.close();
  4.         this.socket = null;
  5.     }
  6. }

自动心跳封装

自动心跳(Automatic Heartbeat)是一种在网络通信中常用的机制,用于维持连接的活跃状态,检测连接是否仍然有效,并及时发现和处理连接断开或故障的情况。心跳机制通过定期发送“心跳”消息(通常是一个简单的 ping 或者 pong 消息)来确认连接双方的状态。

实现自动心跳的基本思路

  1. 发送心跳消息:在 WebSocket 连接建立后,启动一个定时器,定期发送心跳消息到服务器。

  2. 接收心跳响应:服务器收到心跳消息后返回响应,客户端接收到响应后重置定时器。

  3. 检测心跳超时:如果在指定时间内没有收到心跳响应,则认为连接断开,进行重连。

  1. export class WebSocketClient {
  2.     // #socket链接
  3.     private url = '';
  4.     // #socket实例
  5.     private socket: WebSocket | null = null;
  6.     // #重连次数
  7.     private reconnectAttempts = 0;
  8.     // #最大重连数
  9.     private maxReconnectAttempts = 5;
  10.     // #重连间隔
  11.     private reconnectInterval = 10000// 10 seconds
  12.     // #发送心跳数据间隔
  13.     private heartbeatInterval = 1000 * 30;
  14.     // #计时器id
  15.     private heartbeatTimer?: NodeJS.Timeout;
  16.     // #彻底终止ws
  17.     private stopWs = false;
  18.     // *构造函数
  19.     constructor(url: string) {
  20.         super();
  21.         this.url = url;
  22.     }
  23.     // >消息发送
  24.     public send(message: string): void {
  25.         if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  26.             this.socket.send(message);
  27.         } else {
  28.             console.error('[WebSocket] 未连接');
  29.         }
  30.     }
  31.     // !初始化连接
  32.     public connect(): void {
  33.         if (this.reconnectAttempts === 0) {
  34.             console.log('WebSocket'`初始化连接中...`);
  35.         }
  36.         if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  37.             return;
  38.         }
  39.         this.socket = new WebSocket(this.url);
  40.         // !websocket连接成功
  41.         this.socket.onopen = event => {
  42.             this.stopWs = false;
  43.             // 重置重连尝试成功连接
  44.             this.reconnectAttempts = 0;
  45.             // 在连接成功时停止当前的心跳检测并重新启动
  46.             this.startHeartbeat();
  47.             console.log(`连接成功,等待服务端数据推送[onopen]...`);
  48.         };
  49.         this.socket.onmessage = event => {
  50.             this.dispatchEvent('message', event);
  51.             this.startHeartbeat();
  52.         };
  53.         this.socket.onclose = event => {
  54.             if (this.reconnectAttempts === 0) {
  55.                 console.log(`连接断开[onclose]...`);
  56.             }
  57.             if (!this.stopWs) {
  58.                 this.handleReconnect();
  59.             }
  60.         };
  61.         this.socket.onerror = event => {
  62.             if (this.reconnectAttempts === 0) {
  63.                 console.log(`连接异常[onerror]...`);
  64.             }
  65.             this.closeHeartbeat();
  66.         };
  67.     }
  68.     // > 断网重连逻辑
  69.     private handleReconnect(): void {
  70.         if (this.reconnectAttempts < this.maxReconnectAttempts) {
  71.             this.reconnectAttempts++;
  72.             console.log('WebSocket'`尝试重连...`);
  73.             setTimeout(() => {
  74.                 this.connect();
  75.             }, this.reconnectInterval);
  76.         } else {
  77.             this.closeHeartbeat();
  78.             console.log(`最大重连失败,终止重连: ${this.url}`);
  79.         }
  80.     }
  81.     // >关闭连接
  82.     public close(): void {
  83.         if (this.socket) {
  84.             this.stopWs = true;
  85.             this.socket.close();
  86.             this.socket = null;
  87.         }
  88.         this.closeHeartbeat();
  89.     }
  90.     // >开始心跳检测 -> 定时发送心跳消息
  91.     private startHeartbeat(): void {
  92.         if (this.stopWs) return;
  93.         if (this.heartbeatTimer) {
  94.             this.closeHeartbeat();
  95.         }
  96.         this.heartbeatTimer = setInterval(() => {
  97.             if (this.socket) {
  98.                 this.socket.send(JSON.stringify({ type'heartBeat', data: {} }));
  99.                 console.log('WebSocket''送心跳数据...');
  100.             } else {
  101.                 console.error('[WebSocket] 未连接');
  102.             }
  103.         }, this.heartbeatInterval);
  104.     }
  105.     // >关闭心跳
  106.     private closeHeartbeat(): void {
  107.         clearInterval(this.heartbeatTimer);
  108.         this.heartbeatTimer = undefined;
  109.     }
  110. }

上述代码通过定时发送心跳消息来实现自动心跳机制,并结合断网重连逻辑来确保 WebSocket 连接的稳定性。

心跳机制的实现原理简析:

  • 在连接成功时启动心跳检测

在 connect() 方法中,当 WebSocket 连接成功(onopen 事件触发)时,调用 startHeartbeat() 方法。

  1. this.socket.onopen = event => {
  2.   this.stopWs = false;
  3.   this.reconnectAttempts = 0;
  4.   this.startHeartbeat();
  5.   console.log(`连接成功,等待服务端数据推送[onopen]...`);
  6. };
  • 定时发送心跳消息

startHeartbeat() 方法启动一个定时器,每隔 heartbeatInterval 时间(30秒)发送一次心跳消息。

  1. private startHeartbeat(): void {
  2.   if (this.stopWs) return;
  3. if (this.heartbeatTimer) {
  4.   this.closeHeartbeat();
  5. }
  6. this.heartbeatTimer = setInterval(() => {
  7.   if (this.socket) {
  8.     this.socket.send(JSON.stringify({ type'heartBeat', data: {} }));
  9.     console.log('WebSocket''发送心跳数据...');
  10.   } else {
  11.     console.error('[WebSocket] 未连接');
  12.   }
  13. }, this.heartbeatInterval);
  14. }
  • 停止心跳检测

closeHeartbeat() 方法用于停止心跳检测,清除定时器。

  1. private closeHeartbeat(): void {
  2.   clearInterval(this.heartbeatTimer);
  3.     this.heartbeatTimer = undefined;
  4. }
  • 在连接断开或发生错误时停止心跳检测

在 onclose 和 onerror 事件中调用 closeHeartbeat(),停止心跳检测。

  1. this.socket.onclose = event => {
  2.   if (this.reconnectAttempts === 0) {
  3.     console.log(`连接断开[onclose]...`);
  4.   }
  5.   if (!this.stopWs) {
  6.     this.handleReconnect();
  7.   }
  8. };
  9. this.socket.onerror = event => {
  10.   if (this.reconnectAttempts === 0) {
  11.     console.log(`连接异常[onerror]...`);
  12.   }
  13.   this.closeHeartbeat();
  14. };

如何触发原生函数

现在,我们已经基本完成了功能的封装,那么,我们如何在外部调用原生的websokectApi呢?非常简单,借助几个自定义的生命周期函数即可!

  1. import { EventDispatcher } from './dispatcher';
  2. export class WebSocketClient extends EventDispatcher {
  3.     //...
  4.     constructor(url: string) {
  5.         super();
  6.         this.url = url;
  7.     }
  8.     // >生命周期钩子
  9.     onopen(callBack: Function) {
  10.         this.addEventListener('open', callBack);
  11.     }
  12.     onmessage(callBack: Function) {
  13.         this.addEventListener('message', callBack);
  14.     }
  15.     onclose(callBack: Function) {
  16.         this.addEventListener('close', callBack);
  17.     }
  18.     onerror(callBack: Function) {
  19.         this.addEventListener('error', callBack);
  20.     }
  21.     // !初始化连接
  22.     public connect(): void {
  23.         // ...
  24.         // !websocket连接成功
  25.         this.socket.onopen = event => {
  26.             // ...
  27.             this.dispatchEvent('open', event);
  28.         };
  29.         this.socket.onmessage = event => {
  30.             this.dispatchEvent('message', event);
  31.             this.startHeartbeat();
  32.         };
  33.         this.socket.onclose = event => {
  34.             // ...
  35.             this.dispatchEvent('close', event);
  36.         };
  37.         this.socket.onerror = event => {
  38.             // ...
  39.             this.closeHeartbeat();
  40.             this.dispatchEvent('error', event);
  41.         };
  42.     }
  43.     // >关闭连接
  44.     public close(): void {
  45.         if (this.socket) {
  46.             this.stopWs = true;
  47.             this.socket.close();
  48.             this.socket = null;
  49.             this.removeEventListener('open');
  50.             this.removeEventListener('message');
  51.             this.removeEventListener('close');
  52.             this.removeEventListener('error');
  53.         }
  54.         this.closeHeartbeat();
  55.     }
  56.     // ...
  57. }

当原生的onclose、onopen方法触发时,会通过dispatchEvent触发相应的调度,进而触发通过addEventListener绑定的生命周期函数!

注意,这里的this.dispatchEvent方法,addEventListener方法都是通过类继承EventDispatcher方法获得的!

EventDispatcher源码如下:

  1. export class EventDispatcher {
  2.     private listeners: { [typestring]: Function[] } = {};
  3.     protected addEventListener(typestring, listener: Function) {
  4.         if (!this.listeners[type]) {
  5.             this.listeners[type] = [];
  6.         }
  7.         if (this.listeners[type].indexOf(listener) === -1) {
  8.             this.listeners[type].push(listener);
  9.         }
  10.     }
  11.     protected removeEventListener(typestring) {
  12.         this.listeners[type] = [];
  13.     }
  14.     protected dispatchEvent(typestring, data: any) {
  15.         const listenerArray = this.listeners[type] || [];
  16.         if (listenerArray.length === 0return;
  17.         listenerArray.forEach(listener => {
  18.             listener.call(this, data);
  19.         });
  20.     }
  21. }

关于EventDispatcher的实现原理,请参考博主的其他文章:

juejin.cn/post/735851…[1]

完整代码

ts版本

  1. import { EventDispatcher } from './dispatcher';
  2. export class WebSocketClient extends EventDispatcher {
  3.     // #socket链接
  4.     private url = '';
  5.     // #socket实例
  6.     private socket: WebSocket | null = null;
  7.     // #重连次数
  8.     private reconnectAttempts = 0;
  9.     // #最大重连数
  10.     private maxReconnectAttempts = 5;
  11.     // #重连间隔
  12.     private reconnectInterval = 10000// 10 seconds
  13.     // #发送心跳数据间隔
  14.     private heartbeatInterval = 1000 * 30;
  15.     // #计时器id
  16.     private heartbeatTimer?: NodeJS.Timeout;
  17.     // #彻底终止ws
  18.     private stopWs = false;
  19.     // *构造函数
  20.     constructor(url: string) {
  21.         super();
  22.         this.url = url;
  23.     }
  24.     // >生命周期钩子
  25.     onopen(callBack: Function) {
  26.         this.addEventListener('open', callBack);
  27.     }
  28.     onmessage(callBack: Function) {
  29.         this.addEventListener('message', callBack);
  30.     }
  31.     onclose(callBack: Function) {
  32.         this.addEventListener('close', callBack);
  33.     }
  34.     onerror(callBack: Function) {
  35.         this.addEventListener('error', callBack);
  36.     }
  37.     // >消息发送
  38.     public send(message: string): void {
  39.         if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  40.             this.socket.send(message);
  41.         } else {
  42.             console.error('[WebSocket] 未连接');
  43.         }
  44.     }
  45.     // !初始化连接
  46.     public connect(): void {
  47.         if (this.reconnectAttempts === 0) {
  48.             this.log('WebSocket'`初始化连接中...          ${this.url}`);
  49.         }
  50.         if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  51.             return;
  52.         }
  53.         this.socket = new WebSocket(this.url);
  54.         // !websocket连接成功
  55.         this.socket.onopen = event => {
  56.             this.stopWs = false;
  57.             // 重置重连尝试成功连接
  58.             this.reconnectAttempts = 0;
  59.             // 在连接成功时停止当前的心跳检测并重新启动
  60.             this.startHeartbeat();
  61.             this.log('WebSocket'`连接成功,等待服务端数据推送[onopen]...     ${this.url}`);
  62.             this.dispatchEvent('open', event);
  63.         };
  64.         this.socket.onmessage = event => {
  65.             this.dispatchEvent('message', event);
  66.             this.startHeartbeat();
  67.         };
  68.         this.socket.onclose = event => {
  69.             if (this.reconnectAttempts === 0) {
  70.                 this.log('WebSocket'`连接断开[onclose]...    ${this.url}`);
  71.             }
  72.             if (!this.stopWs) {
  73.                 this.handleReconnect();
  74.             }
  75.             this.dispatchEvent('close', event);
  76.         };
  77.         this.socket.onerror = event => {
  78.             if (this.reconnectAttempts === 0) {
  79.                 this.log('WebSocket'`连接异常[onerror]...    ${this.url}`);
  80.             }
  81.             this.closeHeartbeat();
  82.             this.dispatchEvent('error', event);
  83.         };
  84.     }
  85.     // > 断网重连逻辑
  86.     private handleReconnect(): void {
  87.         if (this.reconnectAttempts < this.maxReconnectAttempts) {
  88.             this.reconnectAttempts++;
  89.             this.log('WebSocket'`尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})       ${this.url}`);
  90.             setTimeout(() => {
  91.                 this.connect();
  92.             }, this.reconnectInterval);
  93.         } else {
  94.             this.closeHeartbeat();
  95.             this.log('WebSocket'`最大重连失败,终止重连: ${this.url}`);
  96.         }
  97.     }
  98.     // >关闭连接
  99.     public close(): void {
  100.         if (this.socket) {
  101.             this.stopWs = true;
  102.             this.socket.close();
  103.             this.socket = null;
  104.             this.removeEventListener('open');
  105.             this.removeEventListener('message');
  106.             this.removeEventListener('close');
  107.             this.removeEventListener('error');
  108.         }
  109.         this.closeHeartbeat();
  110.     }
  111.     // >开始心跳检测 -> 定时发送心跳消息
  112.     private startHeartbeat(): void {
  113.         if (this.stopWs) return;
  114.         if (this.heartbeatTimer) {
  115.             this.closeHeartbeat();
  116.         }
  117.         this.heartbeatTimer = setInterval(() => {
  118.             if (this.socket) {
  119.                 this.socket.send(JSON.stringify({ type'heartBeat', data: {} }));
  120.                 this.log('WebSocket''送心跳数据...');
  121.             } else {
  122.                 console.error('[WebSocket] 未连接');
  123.             }
  124.         }, this.heartbeatInterval);
  125.     }
  126.     // >关闭心跳
  127.     private closeHeartbeat(): void {
  128.         clearInterval(this.heartbeatTimer);
  129.         this.heartbeatTimer = undefined;
  130.     }
  131. }
  1. class Log {
  2.     private static console = true;
  3.     log(title: string, text: string) {
  4.         if (!Log.console) return;
  5.         if (import.meta.env.MODE === 'production'return;
  6.         const color = '#ff4d4f';
  7.         console.log(
  8.             `%c ${title} %c ${text} %c`,
  9.             `background:${color};border:1px solid ${color}; padding: 1px; border-radius: 2px 0 0 2px; color: #fff;`,
  10.             `border:1px solid ${color}; padding: 1px; border-radius: 0 2px 2px 0; color: ${color};`,
  11.             'background:transparent'
  12.         );
  13.     }
  14.     closeConsole() {
  15.         Log.console = false;
  16.     }
  17. }
  18. export class EventDispatcher extends Log {
  19.     private listeners: { [typestring]: Function[] } = {};
  20.     protected addEventListener(typestring, listener: Function) {
  21.         if (!this.listeners[type]) {
  22.             this.listeners[type] = [];
  23.         }
  24.         if (this.listeners[type].indexOf(listener) === -1) {
  25.             this.listeners[type].push(listener);
  26.         }
  27.     }
  28.     protected removeEventListener(typestring) {
  29.         this.listeners[type] = [];
  30.     }
  31.     protected dispatchEvent(typestring, data: any) {
  32.         const listenerArray = this.listeners[type] || [];
  33.         if (listenerArray.length === 0return;
  34.         listenerArray.forEach(listener => {
  35.             listener.call(this, data);
  36.         });
  37.     }
  38. }

js版本

  1. export class WebSocketClient extends EventDispatcher {
  2.     // #socket链接
  3.     url = '';
  4.     // #socket实例
  5.     socket = null;
  6.     // #重连次数
  7.     reconnectAttempts = 0;
  8.     // #最大重连数
  9.     maxReconnectAttempts = 5;
  10.     // #重连间隔
  11.     reconnectInterval = 10000// 10 seconds
  12.     // #发送心跳数据间隔
  13.     heartbeatInterval = 1000 * 30;
  14.     // #计时器id
  15.     heartbeatTimer = undefined;
  16.     // #彻底终止ws
  17.     stopWs = false;
  18.     // *构造函数
  19.     constructor(url) {
  20.         super();
  21.         this.url = url;
  22.     }
  23.     // >生命周期钩子
  24.     onopen(callBack) {
  25.         this.addEventListener('open', callBack);
  26.     }
  27.     onmessage(callBack) {
  28.         this.addEventListener('message', callBack);
  29.     }
  30.     onclose(callBack) {
  31.         this.addEventListener('close', callBack);
  32.     }
  33.     onerror(callBack) {
  34.         this.addEventListener('error', callBack);
  35.     }
  36.     // >消息发送
  37.     send(message) {
  38.         if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  39.             this.socket.send(message);
  40.         } else {
  41.             console.error('[WebSocket] 未连接');
  42.         }
  43.     }
  44.     // !初始化连接
  45.     connect() {
  46.         if (this.reconnectAttempts === 0) {
  47.             this.log('WebSocket'`初始化连接中...          ${this.url}`);
  48.         }
  49.         if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  50.             return;
  51.         }
  52.         this.socket = new WebSocket(this.url);
  53.         // !websocket连接成功
  54.         this.socket.onopen = event => {
  55.             this.stopWs = false;
  56.             // 重置重连尝试成功连接
  57.             this.reconnectAttempts = 0;
  58.             // 在连接成功时停止当前的心跳检测并重新启动
  59.             this.startHeartbeat();
  60.             this.log('WebSocket'`连接成功,等待服务端数据推送[onopen]...     ${this.url}`);
  61.             this.dispatchEvent('open', event);
  62.         };
  63.         this.socket.onmessage = event => {
  64.             this.dispatchEvent('message', event);
  65.             this.startHeartbeat();
  66.         };
  67.         this.socket.onclose = event => {
  68.             if (this.reconnectAttempts === 0) {
  69.                 this.log('WebSocket'`连接断开[onclose]...    ${this.url}`);
  70.             }
  71.             if (!this.stopWs) {
  72.                 this.handleReconnect();
  73.             }
  74.             this.dispatchEvent('close', event);
  75.         };
  76.         this.socket.onerror = event => {
  77.             if (this.reconnectAttempts === 0) {
  78.                 this.log('WebSocket'`连接异常[onerror]...    ${this.url}`);
  79.             }
  80.             this.closeHeartbeat();
  81.             this.dispatchEvent('error', event);
  82.         };
  83.     }
  84.     // > 断网重连逻辑
  85.     handleReconnect() {
  86.         if (this.reconnectAttempts < this.maxReconnectAttempts) {
  87.             this.reconnectAttempts++;
  88.             this.log('WebSocket'`尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})       ${this.url}`);
  89.             setTimeout(() => {
  90.                 this.connect();
  91.             }, this.reconnectInterval);
  92.         } else {
  93.             this.closeHeartbeat();
  94.             this.log('WebSocket'`最大重连失败,终止重连: ${this.url}`);
  95.         }
  96.     }
  97.     // >关闭连接
  98.     close() {
  99.         if (this.socket) {
  100.             this.stopWs = true;
  101.             this.socket.close();
  102.             this.socket = null;
  103.             this.removeEventListener('open');
  104.             this.removeEventListener('message');
  105.             this.removeEventListener('close');
  106.             this.removeEventListener('error');
  107.         }
  108.         this.closeHeartbeat();
  109.     }
  110.     // >开始心跳检测 -> 定时发送心跳消息
  111.     startHeartbeat() {
  112.         if (this.stopWs) return;
  113.         if (this.heartbeatTimer) {
  114.             this.closeHeartbeat();
  115.         }
  116.         this.heartbeatTimer = setInterval(() => {
  117.             if (this.socket) {
  118.                 this.socket.send(JSON.stringify({ type'heartBeat', data: {} }));
  119.                 this.log('WebSocket''送心跳数据...');
  120.             } else {
  121.                 console.error('[WebSocket] 未连接');
  122.             }
  123.         }, this.heartbeatInterval);
  124.     }
  125.     // >关闭心跳
  126.     closeHeartbeat() {
  127.         clearInterval(this.heartbeatTimer);
  128.         this.heartbeatTimer = undefined;
  129.     }
  130. }
  1. class Log {
  2.     static console = true;
  3.     log(title, text) {
  4.         if (!Log.console) return;
  5.         if (import.meta.env.MODE === 'production'return;
  6.         const color = '#ff4d4f';
  7.         console.log(
  8.             `%c ${title} %c ${text} %c`,
  9.             `background:${color};border:1px solid ${color}; padding: 1px; border-radius: 2px 0 0 2px; color: #fff;`,
  10.             `border:1px solid ${color}; padding: 1px; border-radius: 0 2px 2px 0; color: ${color};`,
  11.             'background:transparent'
  12.         );
  13.     }
  14.     closeConsole() {
  15.         Log.console = false;
  16.     }
  17. }
  18. export class EventDispatcher extends Log {
  19.     listeners = {};
  20.     addEventListener(type, listener) {
  21.         if (!this.listeners[type]) {
  22.             this.listeners[type] = [];
  23.         }
  24.         if (this.listeners[type].indexOf(listener) === -1) {
  25.             this.listeners[type].push(listener);
  26.         }
  27.     }
  28.     removeEventListener(type) {
  29.         this.listeners[type] = [];
  30.     }
  31.     dispatchEvent(type, data) {
  32.         const listenerArray = this.listeners[type] || [];
  33.         if (listenerArray.length === 0return;
  34.         listenerArray.forEach(listener => {
  35.             listener.call(this, data);
  36.         });
  37.     }
  38. }

总结

这篇文章封装了weboskect,完美支持了断网重连、自动心跳的功能,且完全兼容原生写法,无任何学习负担,开开箱即用!但美中不足的是,断网重连时间、心跳数据内容目前都是写死的,大家可以根据自己的情况做一些更改,让它更灵活!

参考资料

[1]

https://juejin.cn/post/7358518759118700607: https://juejin.cn/post/7358518759118700607

Node 社群

 
 

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

3b864a72689f6aa149f50ac73c92fb14.png

“分享、点赞、在看” 支持一下
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/964220
推荐阅读
相关标签
  

闽ICP备14008679号