Jansiel Notes

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

简介

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

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

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

websokect的API非常简单

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

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

封装成功演示

核心优势

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

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

效果演示

后端服务创建

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

1npm install ws
2

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

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

我们启动这个服务

1node index.js
2

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

前端websokect测试

我们先写前端的相关逻辑

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

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

心跳验证:

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

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

Jansiel_Essay_1717403464326

断网重连验证:

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

Jansiel_Essay_1717403640588

技术路线

基本框架搭建

 1export 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    // !初始化连接
22    public connect(): void {
23        if (this.socket && this.socket.readyState === WebSocket.OPEN) {
24            return;
25        }
26        this.socket = new WebSocket(this.url);
27
28        // !websocket连接成功
29        this.socket.onopen = event => {
30            console.log(`连接成功,等待服务端数据推送[onopen]...`);
31        };
32
33        this.socket.onmessage = event => {
34        };
35
36        this.socket.onclose = event => {
37            console.log(`连接断开[onclose]...`);
38        };
39
40        this.socket.onerror = event => {
41            console.log(`连接异常[onerror]...`);
42        };
43    }
44
45    // >关闭连接
46    public close(): void {
47        if (this.socket) {
48            this.socket.close();
49            this.socket = null;
50        }
51    }
52}
53

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

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

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

断网重连封装

 1export 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    // !初始化连接
27    public connect(): void {
28        if (this.reconnectAttempts === 0) {
29            console.log(`初始化连接中...`);
30        }
31        if (this.socket && this.socket.readyState === WebSocket.OPEN) {
32            return;
33        }
34        this.socket = new WebSocket(this.url);
35
36        // !websocket连接成功
37        this.socket.onopen = event => {
38            // 重置重连尝试成功连接
39            this.reconnectAttempts = 0;
40            console.log(`连接成功,等待服务端数据推送[onopen]...`);
41        };
42
43        this.socket.onmessage = event => {
44        };
45
46        this.socket.onclose = event => {
47            if (this.reconnectAttempts === 0) {
48                console.log(`连接断开[onclose]...`);
49            }
50            if (!this.stopWs) {
51                this.handleReconnect();
52            }
53        };
54
55        this.socket.onerror = event => {
56            if (this.reconnectAttempts === 0) {
57                console.log(`连接异常[onerror]...`);
58            }
59        };
60    }
61
62    // > 断网重连逻辑
63    private handleReconnect(): void {
64        if (this.reconnectAttempts < this.maxReconnectAttempts) {
65            this.reconnectAttempts++;
66            console.log(`尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
67            setTimeout(() => {
68                this.connect();
69            }, this.reconnectInterval);
70        } else {
71            console.log(`最大重连失败,终止重连: ${this.url}`);
72        }
73    }
74
75    // >关闭连接
76    public close(): void {
77        if (this.socket) {
78            this.socket.close();
79            this.socket = null;
80        }
81    }
82}
83

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

  1. 记录重连次数:通过 reconnectAttempts 属性记录当前已经尝试重连的次数。
  2. 设置最大重连次数:通过 maxReconnectAttempts 属性设置允许的最大重连次数。
  3. 重连逻辑:在 onclose 和 onerror 事件中调用重连处理函数 handleReconnect。
  4. 重连间隔:通过 reconnectInterval 属性设置每次重连的间隔时间,可以在每次重连时增加间隔以实现指数退避。

初始化连接并处理事件

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

 1public 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
10    this.socket.onopen = (event: Event) => {
11        this.reconnectAttempts = 0;
12        console.log(`连接成功,等待服务端数据推送[onopen]...`);
13    };
14    this.socket.onclose = (event: CloseEvent) => {
15        if (this.reconnectAttempts === 0) {
16            console.log(`连接断开[onclose]...`);
17        }
18        this.handleReconnect();
19    };
20
21    this.socket.onerror = (event: Event) => {
22        if (this.reconnectAttempts === 0) {
23            console.log(`连接异常[onerror]...`);
24        }
25        this.handleReconnect();
26    };
27}
28

处理重连逻辑

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

 1private 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}
12

关闭连接

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

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

自动心跳封装

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

实现自动心跳的基本思路

  1. 发送心跳消息:在 WebSocket 连接建立后,启动一个定时器,定期发送心跳消息到服务器。
  2. 接收心跳响应:服务器收到心跳消息后返回响应,客户端接收到响应后重置定时器。
  3. 检测心跳超时:如果在指定时间内没有收到心跳响应,则认为连接断开,进行重连。
  1
  2export class WebSocketClient {
  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    public send(message: string): void {
 26        if (this.socket && this.socket.readyState === WebSocket.OPEN) {
 27            this.socket.send(message);
 28        } else {
 29            console.error('[WebSocket] 未连接');
 30        }
 31    }
 32
 33    // !初始化连接
 34    public connect(): void {
 35        if (this.reconnectAttempts === 0) {
 36            console.log('WebSocket', `初始化连接中...`);
 37        }
 38        if (this.socket && this.socket.readyState === WebSocket.OPEN) {
 39            return;
 40        }
 41        this.socket = new WebSocket(this.url);
 42
 43        // !websocket连接成功
 44        this.socket.onopen = event => {
 45            this.stopWs = false;
 46            // 重置重连尝试成功连接
 47            this.reconnectAttempts = 0;
 48            // 在连接成功时停止当前的心跳检测并重新启动
 49            this.startHeartbeat();
 50            console.log(`连接成功,等待服务端数据推送[onopen]...`);
 51        };
 52
 53        this.socket.onmessage = event => {
 54            this.dispatchEvent('message', event);
 55            this.startHeartbeat();
 56        };
 57
 58        this.socket.onclose = event => {
 59            if (this.reconnectAttempts === 0) {
 60                console.log(`连接断开[onclose]...`);
 61            }
 62            if (!this.stopWs) {
 63                this.handleReconnect();
 64            }
 65        };
 66
 67        this.socket.onerror = event => {
 68            if (this.reconnectAttempts === 0) {
 69                console.log(`连接异常[onerror]...`);
 70            }
 71            this.closeHeartbeat();
 72        };
 73    }
 74
 75    // > 断网重连逻辑
 76    private handleReconnect(): void {
 77        if (this.reconnectAttempts < this.maxReconnectAttempts) {
 78            this.reconnectAttempts++;
 79            console.log('WebSocket', `尝试重连...`);
 80            setTimeout(() => {
 81                this.connect();
 82            }, this.reconnectInterval);
 83        } else {
 84            this.closeHeartbeat();
 85            console.log(`最大重连失败,终止重连: ${this.url}`);
 86        }
 87    }
 88
 89    // >关闭连接
 90    public close(): void {
 91        if (this.socket) {
 92            this.stopWs = true;
 93            this.socket.close();
 94            this.socket = null;
 95        }
 96        this.closeHeartbeat();
 97    }
 98
 99    // >开始心跳检测 -> 定时发送心跳消息
100    private startHeartbeat(): void {
101        if (this.stopWs) return;
102        if (this.heartbeatTimer) {
103            this.closeHeartbeat();
104        }
105        this.heartbeatTimer = setInterval(() => {
106            if (this.socket) {
107                this.socket.send(JSON.stringify({ type: 'heartBeat', data: {} }));
108                console.log('WebSocket', '送心跳数据...');
109            } else {
110                console.error('[WebSocket] 未连接');
111            }
112        }, this.heartbeatInterval);
113    }
114
115    // >关闭心跳
116    private closeHeartbeat(): void {
117        clearInterval(this.heartbeatTimer);
118        this.heartbeatTimer = undefined;
119    }
120}
121

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

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

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

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

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

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

 1private startHeartbeat(): void {
 2  if (this.stopWs) return;
 3if (this.heartbeatTimer) {
 4  this.closeHeartbeat();
 5}
 6this.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}
15
  • 停止心跳检测

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

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

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

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

如何触发原生函数

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

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

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

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

EventDispatcher源码如下:

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

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

juejin.cn/post/735851…

完整代码

ts版本

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

js版本

  1export 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    // !初始化连接
 46    connect() {
 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
 55        // !websocket连接成功
 56        this.socket.onopen = event => {
 57            this.stopWs = false;
 58            // 重置重连尝试成功连接
 59            this.reconnectAttempts = 0;
 60            // 在连接成功时停止当前的心跳检测并重新启动
 61            this.startHeartbeat();
 62            this.log('WebSocket', `连接成功,等待服务端数据推送[onopen]...     ${this.url}`);
 63            this.dispatchEvent('open', event);
 64        };
 65
 66        this.socket.onmessage = event => {
 67            this.dispatchEvent('message', event);
 68            this.startHeartbeat();
 69        };
 70
 71        this.socket.onclose = event => {
 72            if (this.reconnectAttempts === 0) {
 73                this.log('WebSocket', `连接断开[onclose]...    ${this.url}`);
 74            }
 75            if (!this.stopWs) {
 76                this.handleReconnect();
 77            }
 78            this.dispatchEvent('close', event);
 79        };
 80
 81        this.socket.onerror = event => {
 82            if (this.reconnectAttempts === 0) {
 83                this.log('WebSocket', `连接异常[onerror]...    ${this.url}`);
 84            }
 85            this.closeHeartbeat();
 86            this.dispatchEvent('error', event);
 87        };
 88    }
 89
 90    // > 断网重连逻辑
 91    handleReconnect() {
 92        if (this.reconnectAttempts < this.maxReconnectAttempts) {
 93            this.reconnectAttempts++;
 94            this.log('WebSocket', `尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})       ${this.url}`);
 95            setTimeout(() => {
 96                this.connect();
 97            }, this.reconnectInterval);
 98        } else {
 99            this.closeHeartbeat();
100            this.log('WebSocket', `最大重连失败,终止重连: ${this.url}`);
101        }
102    }
103
104    // >关闭连接
105    close() {
106        if (this.socket) {
107            this.stopWs = true;
108            this.socket.close();
109            this.socket = null;
110            this.removeEventListener('open');
111            this.removeEventListener('message');
112            this.removeEventListener('close');
113            this.removeEventListener('error');
114        }
115        this.closeHeartbeat();
116    }
117
118    // >开始心跳检测 -> 定时发送心跳消息
119    startHeartbeat() {
120        if (this.stopWs) return;
121        if (this.heartbeatTimer) {
122            this.closeHeartbeat();
123        }
124        this.heartbeatTimer = setInterval(() => {
125            if (this.socket) {
126                this.socket.send(JSON.stringify({ type: 'heartBeat', data: {} }));
127                this.log('WebSocket', '送心跳数据...');
128            } else {
129                console.error('[WebSocket] 未连接');
130            }
131        }, this.heartbeatInterval);
132    }
133
134    // >关闭心跳
135    closeHeartbeat() {
136        clearInterval(this.heartbeatTimer);
137        this.heartbeatTimer = undefined;
138    }
139}
140
 1class 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}
18export class EventDispatcher extends Log {
19    listeners = {};
20
21    addEventListener(type, listener) {
22        if (!this.listeners[type]) {
23            this.listeners[type] = [];
24        }
25        if (this.listeners[type].indexOf(listener) === -1) {
26            this.listeners[type].push(listener);
27        }
28    }
29
30    removeEventListener(type) {
31        this.listeners[type] = [];
32    }
33
34    dispatchEvent(type, data) {
35        const listenerArray = this.listeners[type] || [];
36        if (listenerArray.length === 0) return;
37        listenerArray.forEach(listener => {
38            listener.call(this, data);
39        });
40    }
41}
42

总结

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