还没有写完,bug 是有的,我在想解决办法了…
演示
大纲
- 基础的 webSocket 连接
- 前后端:添加心跳机制
- 后端无心跳反应,前端尝试重新连接
- 设置重新连接次数,超过最大尝试次数之后,不再尝试重新连接
基础 WebSocket
前端的基础就是这些,大概的效果是这样的
<body>
<button onclick="reConnect()">1. 重建连接</button>
<button onclick="sendMessage()">2. 发消息</button>
<button onclick="stopConnect()">3. 断开连接</button>
</body>
<script>
let ws = null // 使用null来标记当前没有活动的 WebSocket 连接
function createNewWebSocket() {
if (ws && ws.readyState !== WebSocket.CLOSED) {
ws.close() // 确保关闭旧的连接
}
ws = new WebSocket('ws://localhost:8080')
ws.onopen = function (evt) {
console.log('Connection open ...')
}
ws.onmessage = function (evt) {
console.log('Received Message: ' + evt.data)
}
ws.onclose = function (evt) {
console.log('Connection closed.')
}
}
function sendMessage() {
if (ws) ws.send(`前端发送:>> ${new Date()}`)
}
function stopConnect() {
if (ws) ws.close()
}
function reConnect() {
createNewWebSocket()
}
</script>
后端的代码基本不变,所以我直接把心跳也做好
后端的心跳就是:拿到前端的值,如果是ping
的话,就返回一个pong
,其他逻辑保持不变
const http = require('http')
const WebSocket = require('ws')
const server = http.createServer()
const wss = new WebSocket.Server({ server })
wss.on('connection', (socket) => {
console.log('webSocket 连接成功')
socket.on('message', (message) => {
// 将 Buffer 转换为字符串
const messageStr = message.toString()
const currentRandom = Math.random()
const isSendPong = currentRandom < 0.5
console.log('后端收到消息:>>' + messageStr)
// 检查是否为心跳请求
if (messageStr === 'ping') {
socket.send(`当前随机值为 ${currentRandom}, 是否发送心跳:${isSendPong}`)
// 50%的概率发送 "pong"
if (isSendPong) {
socket.send('pong') // 心跳响应
}
} else {
const message = `后端发送消息:>> 你好前端~ ${new Date().toLocaleString()}`
socket.send(message)
}
})
socket.on('close', () => {
console.log('websocket 已经关闭')
})
})
server.on('request', (request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' })
response.end('Hello, World')
})
server.listen(8080, () => {
console.log('服务器已启动,端口号为 8080')
})
前端: 添加心跳机制
思路:前端写一个定时器,用于隔一段时间发送一个
ping
值
效果如下图所示
好吧,false
的概率有点高,不顾可以看历史记录
我在后端设置了随机逻辑,模拟一下出错的场景,百分之50的概率回应前端的心跳,如果 true
的话,后端就回应前端的心跳,返回 pong
前端 代码如下
<body>
<button onclick="reConnect()">1. 重建连接</button>
<button onclick="sendMessage()">2. 发消息</button>
<button onclick="stopConnect()">3. 断开连接</button>
</body>
<script>
let ws = null // 使用null来标记当前没有活动的 WebSocket 连接
let heartbeatTimer = null // 心跳定时器
const HEARTBEAT_INTERVAL = 5000 // 每隔 5 秒发送一次心跳
function createNewWebSocket() {
if (ws && ws.readyState !== WebSocket.CLOSED) {
ws.close() // 确保关闭旧的连接
}
ws = new WebSocket('ws://localhost:8080')
ws.onopen = function (evt) {
console.log('Connection open ...')
startHeartbeat()
}
ws.onmessage = function (evt) {
console.log('Received Message: ' + evt.data)
handleHeartbeatResponse(evt.data)
}
ws.onclose = function (evt) {
console.log('Connection closed.')
stopHeartbeat()
}
}
function sendMessage() {
if (ws) ws.send(`前端发送:>> ${new Date().toLocaleString()}`)
}
function stopConnect() {
if (ws) ws.close()
stopHeartbeat()
}
function reConnect() {
createNewWebSocket()
}
function startHeartbeat() {
heartbeatTimer = setInterval(() => {
ws.send('ping')
}, HEARTBEAT_INTERVAL)
}
function stopHeartbeat() {
clearInterval(heartbeatTimer)
heartbeatTimer = null
}
function handleHeartbeatResponse(message) {
if (message === 'heartbeat') {
console.log('Heartbeat received.')
clearTimeout(heartbeatTimer) // 清除超时定时器
startHeartbeat() // 重新启动心跳
}
}
</script>
前端: 尝试重新连接
设置一个场景: 前端发送三个心跳包,如果都没有反应,那么就判断为断开连接了,就去重新连接
历史代码
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button id="btn">发消息</button>
<button id="stop">断链</button>
<button id="reconnect">重新连接</button>
</body>
<script>
const btn = document.querySelector('#btn')
const stop = document.querySelector('#stop')
const reconnect = document.querySelector('#reconnect')
let ws = null // 使用null来标记当前没有活动的WebSocket连接
let heartbeatInterval = null // 存储心跳定时器
let timeoutId = null // 存储超时定时器
let reconnectAttempts = 0 // 重连尝试次数
const maxReconnectAttempts = 3 // 最大重连次数
function createNewWebSocket() {
if (ws && ws.readyState !== WebSocket.CLOSED) {
ws.close() // 确保关闭旧的连接
}
ws = new WebSocket('ws://localhost:8080')
ws.onopen = function (evt) {
console.log('Connection open ...')
startHeartbeat() // 开始发送心跳
}
ws.onmessage = function (evt) {
console.log('Received Message: ' + evt.data)
// 检查是否为心跳响应
if (evt.data === 'pong') {
clearTimeout(timeoutId) // 清除超时定时器
resetHeartbeatTimer() // 重置心跳定时器
}
}
ws.onclose = function (evt) {
console.log('Connection closed.')
clearInterval(heartbeatInterval) // 清除心跳定时器
reconnectWebSocket() // 尝试重新连接
}
}
// 发送心跳包
function sendHeartbeat() {
if (ws) {
ws.send('ping')
timeoutId = setTimeout(() => {
// 如果在超时时间内没有收到 "pong" 响应,则关闭当前连接
console.log('超时,关闭连接')
ws.close()
}, 15000) // 设置超时时间为 15 秒
}
}
// 启动心跳
function startHeartbeat() {
sendHeartbeat() // 立即发送第一个心跳包
heartbeatInterval = setInterval(sendHeartbeat, 30000) // 每30秒发送一次
}
// 重置心跳定时器
function resetHeartbeatTimer() {
clearInterval(heartbeatInterval)
heartbeatInterval = setInterval(sendHeartbeat, 30000) // 重新设置定时器
}
// 重新连接
function reconnectWebSocket() {
console.log('尝试重新连接', reconnectAttempts)
// 检查是否超过最大重连次数
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++
createNewWebSocket()
} else {
console.log('超过最大重连次数,不再尝试连接')
}
}
btn.addEventListener('click', () => {
if (ws) {
ws.send(`前端发送:>> ${new Date()}`)
}
})
stop.addEventListener('click', () => {
if (ws) {
ws.close()
}
})
reconnect.addEventListener('click', () => {
createNewWebSocket()
})
</script>
</html>
后端
const http = require('http')
const WebSocket = require('ws')
const server = http.createServer()
const wss = new WebSocket.Server({ server })
wss.on('connection', (socket) => {
console.log('webSocket 连接成功')
socket.on('message', (message) => {
// 将 Buffer 转换为字符串
const messageStr = message.toString();
console.log('后端收到消息:>>' + messageStr);
// 检查是否为心跳请求
if (messageStr === 'ping') {
socket.send('pong'); // 心跳响应
} else {
socket.send('后端发送消息:>> hello 我是 socket.send');
}
})
socket.on('close', () => {
console.log('websocket 已经关闭');
})
})
server.on('request', (request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('Hello, World');
})
server.listen(8080, () => {
console.log('服务器已启动,端口号为 8080');
})