代码封装

编写 WebSocketClient.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { EventDispatcher } from './dispatcher'

export class WebSocketClient extends EventDispatcher {
constructor(url) {
console.log(url, 'urlurl')
super()
this.url = url
}

// #socket实例
socket = null
// #重连次数
reconnectAttempts = 0
// #最大重连数
maxReconnectAttempts = 5
// #重连间隔
reconnectInterval = 10000 // 10 seconds
// #发送心跳数据间隔
heartbeatInterval = 1000 * 30
// #计时器id
heartbeatTimer = undefined
// #彻底终止ws
stopWs = false

// >生命周期钩子
onopen(callBack) {
this.addEventListener('open', callBack)
}

onmessage(callBack) {
this.addEventListener('message', callBack)
}

onclose(callBack) {
this.addEventListener('close', callBack)
}

onerror(callBack) {
this.addEventListener('error', callBack)
}

// >消息发送
send(message) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(message)
} else {
console.error('[WebSocket] 未连接')
}
}

// !初始化连接
connect() {
if (this.reconnectAttempts === 0) {
this.log('WebSocket', `初始化连接中... ${this.url}`)
}
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
return
}
this.socket = new WebSocket(this.url)

// !websocket连接成功
this.socket.onopen = (event) => {
this.stopWs = false
// 重置重连尝试成功连接
this.reconnectAttempts = 0
// 在连接成功时停止当前的心跳检测并重新启动
this.startHeartbeat()
this.log('WebSocket', `连接成功,等待服务端数据推送[onopen]... ${this.url}`)
this.dispatchEvent('open', event)
}

this.socket.onmessage = (event) => {
this.dispatchEvent('message', event)
this.startHeartbeat()
}

this.socket.onclose = (event) => {
if (this.reconnectAttempts === 0) {
this.log('WebSocket', `连接断开[onclose]... ${this.url}`)
}
if (!this.stopWs) {
this.handleReconnect()
}
this.dispatchEvent('close', event)
}

this.socket.onerror = (event) => {
if (this.reconnectAttempts === 0) {
this.log('WebSocket', `连接异常[onerror]... ${this.url}`)
}
this.closeHeartbeat()
this.dispatchEvent('error', event)
}
}

// > 断网重连逻辑
handleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++
this.log(
'WebSocket',
`尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts}) ${this.url}`
)
setTimeout(() => {
this.connect()
}, this.reconnectInterval)
} else {
this.closeHeartbeat()
this.log('WebSocket', `最大重连失败,终止重连: ${this.url}`)
}
}

// >关闭连接
close() {
if (this.socket) {
this.stopWs = true
this.socket.close()
this.socket = null
this.removeEventListener('open')
this.removeEventListener('message')
this.removeEventListener('close')
this.removeEventListener('error')
}
this.closeHeartbeat()
}

// >开始心跳检测 -> 定时发送心跳消息
startHeartbeat() {
if (this.stopWs) return
if (this.heartbeatTimer) {
this.closeHeartbeat()
}
this.heartbeatTimer = setInterval(() => {
if (this.socket) {
this.socket.send(JSON.stringify({ type: 'heartBeat', data: {} }))
this.log('WebSocket', '送心跳数据...')
} else {
console.error('[WebSocket] 未连接')
}
}, this.heartbeatInterval)
}

// >关闭心跳
closeHeartbeat() {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = undefined
}
}

引用的 dispatcher.js 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { Log } from './log'

export class EventDispatcher extends Log {
constructor() {
super()
this.listeners = {}
}

addEventListener(type, listener) {
if (!this.listeners[type]) {
this.listeners[type] = []
}
if (this.listeners[type].indexOf(listener) === -1) {
this.listeners[type].push(listener)
}
}

removeEventListener(type) {
this.listeners[type] = []
}

dispatchEvent(type, data) {
const listenerArray = this.listeners[type] || []
if (listenerArray.length === 0) return
listenerArray.forEach((listener) => {
listener.call(this, data)
})
}
}

上面还用到了一个 log.js ,用于美化控制台打印的,这个文件在其他地方也通用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export class Log {
static console = true

log(title, text) {
if (!Log.console) return
const color = '#09c'
console.log(
`%c ${title} %c ${text} %c`,
`background:${color};border:1px solid ${color}; padding: 1px; border-radius: 2px 0 0 2px; color: #fff;`,
`border:1px solid ${color}; padding: 1px; border-radius: 0 2px 2px 0; color: ${color};`,
'background:transparent'
)
}

closeConsole() {
Log.console = false
}
}

至此一个 WebSocket 就封装好了

使用方法

首先使用node编写一个后端服务,用于 WebSocket 连接

需要安装一下 ws

1
npm install ws
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const WebSocket = require("ws");

const wss = new WebSocket.Server({port: 3200});

console.log("服务运行在http://localhost:3200/");

wss.on("connection", (ws) => {
console.log("[服务器]:连接成功");
ws.send(`[websocket云端]您已经连接云端!等待数据推送~`);

ws.on("message", (res) => {
ws.send(`[websocket云端]收到消息:${res.toString()}`);
});

ws.on("close", () => {
console.log("[服务器]:连接已关闭~");
});
});

然后我这里编写了一个简单的demo页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<template>
<div>
<el-button type="primary" @click="connection">创建连接</el-button>
<el-button type="danger" @click="close">关闭连接</el-button>

<el-input v-model="message" placeholder="placeholder"></el-input>
<el-button type="primary" @click="send">发送消息</el-button>

<ul>
<li v-for="(item, index) in messageList" :key="index">{{ item }}</li>
</ul>
</div>
</template>

<script>
import { WebSocketClient } from '@/utils/WebSocketClient'

export default {
data() {
return {
message: '',
messageList: [],
ws: null,
}
},
methods: {
connection() {
if (this.ws) {
this.close()
}
this.ws = new WebSocketClient('ws://localhost:3200')
this.setupWebSocketListeners()
this.ws.connect()
},
close() {
if (this.ws) {
this.ws.close()
this.ws = null
}
},
send() {
if (this.ws) {
this.ws.send(this.message)
}
},
setupWebSocketListeners() {
this.ws.onmessage((msg) => {
this.ws.log('WebSocketClient', msg.data)
this.messageList.push(msg.data)
})
this.ws.onopen(() => {
this.ws.log('WebSocketClient', '连接已打开')
})
this.ws.onclose(() => {
this.ws.log('WebSocketClient', '连接已关闭')
})
this.ws.onerror((error) => {
this.ws.log('WebSocketClient', '连接错误')
console.error(error)
})
},
},
mounted() {
this.connection()
},
}
</script>

初次连接

image-20240531100922169

消息发送

image-20240531100944165

关闭连接后,消息就无法发送了

image-20240531101029443

再次连接

image-20240531101057517