一、WebSocket 基础回顾
WebSocket 是在单个 TCP 连接上提供全双工(双向实时)通信的协议。
简单说:服务器可以随时主动推消息给浏览器,不用你一直去轮询。
二、websocket与http的区别?
- 通信模式:
-
- HTTP是单项的(客户端请求,服务器响应)。
- Websocket是双向的(客户端和服务器可以随时发送消息)。
- 连接方式:
-
- HTTP是无状态的,每次请求都需要重新建立连接。
- Websocket是持久连接,建立后可以持续通信。
- 性能:
-
- WebSocket减少了每次通信的开销,适合实时性要求高的场景。
三、WebSocket 的四大生命周期事件
WebSocket可能用到的生命周期
onopen → 连接成功建立时
onmessage → 每收到一条服务器消息时
onerror → 连接发生错误时(注意:错误后很快会触发 onclose)
onclose → 连接关闭时(正常关闭或异常断开都会触发)
简单的websocket连接
const websocket = new WebSocket('ws://192.168.1.1:8000/example/websocket');
websocket.onopen = function () { console.log('连接成功'); };
websocket.onmessage = function (event) { console.log(event.data, 'event.data打印'); };
websocket.onerror = function (event) { console.log(event, 'event打印'); };
websocket.onclose = function (event) { console.log('连接关闭', event.code, event.reason); };
四、原生Websocket入门。
WebSocket 的用法简单到离谱,真正常用的就这四件事:
new WebSocket(url) → 建立连接
ws.send(data) → 发消息
ws.onmessage → 收消息(项目里 90% 的逻辑都写在这里)
ws.close() → 关连接(一定要记得关,不然会漏连接!)
<body>
<h1>websocket练习</h1>
<div style="padding: 20px;">
<div>
<input type="text" id="messageInput" placeholder="输入要发送的消息" style="padding: 8px; width: 300px;">
<button onclick="sendMessage()">发送消息</button>
</div>
<div style="margin-top: 10px;">
<button onclick="closeConnection()">关闭连接</button>
</div>
<div id="status" style="margin-top: 20px; padding: 10px; background: #f0f0f0;">
状态:连接中...
</div>
</div>
<script>
// 1. new WebSocket(url) → 建立连接
const websocket = new WebSocket('ws://192.168.1.1:8000/data/status');
websocket.onopen = function () {
console.log('连接成功');
document.getElementById('status').textContent = '状态:已连接';
document.getElementById('status').style.background = '#d4edda';
};
// 3. ws.onmessage → 收消息(项目里 90% 的逻辑都写在这里)
websocket.onmessage = function (event) {
console.log('收到消息:', event.data);
const statusDiv = document.getElementById('status');
statusDiv.innerHTML = `状态:已连接<br>收到消息: ${event.data}`;
};
websocket.onerror = function (event) {
console.log('连接错误:', event);
document.getElementById('status').textContent = '状态:连接错误';
document.getElementById('status').style.background = '#f8d7da';
};
websocket.onclose = function (event) {
console.log('连接关闭', event.code, event.reason);
document.getElementById('status').textContent = `状态:连接已关闭 (代码: ${event.code})`;
document.getElementById('status').style.background = '#fff3cd';
};
// 2. ws.send(data) → 发消息
function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (websocket.readyState === WebSocket.OPEN) {
websocket.send(message);
console.log('发送消息:', message);
input.value = '';
} else {
alert('WebSocket 未连接,无法发送消息');
console.log('WebSocket 状态:', websocket.readyState);
}
}
// 4. ws.close() → 关连接(一定要记得关,不然会漏连接!)
function closeConnection() {
if (websocket.readyState === WebSocket.OPEN || websocket.readyState === WebSocket.CONNECTING) {
websocket.close();
console.log('主动关闭连接');
} else {
alert('连接已关闭或未连接');
}
}
// 页面关闭时自动关闭连接(防止内存泄漏)
window.addEventListener('beforeunload', function () {
if (websocket.readyState === WebSocket.OPEN) {
websocket.close();
}
});
</script>
</body>
五、websocket中心跳机制与自动重连
AI举个生活例子:
- 心跳:就像你和女朋友每隔半小时发一句“我在呢”,这样她就不会以为你挂了。
- 自动重连:你们电话真的断了,你再重新拨一次。
心跳机制需要变量:
1、需要一个变量记录心跳间隔时间heartbeatInterval(数值型)。
2、需要有一个超时检测变量pongReceived(布尔值,true/false)。
发 ping 用 setInterval 最稳定
重连机制需要变量:
1、设置最大重连次数变量。
2、重连次数的计数器变量。
3、设置一个延迟时间设置上限。
<body>
<h1>websocket练习</h1>
<div style="padding: 20px;">
<div>
<input type="text" id="messageInput" placeholder="输入要发送的消息" style="padding: 8px; width: 300px;">
<button onclick="sendMessage()">发送消息</button>
</div>
<div style="margin-top: 10px;">
<button onclick="closeConnection()">关闭连接</button>
</div>
<div id="status" style="margin-top: 20px; padding: 10px; background: #f0f0f0;">
状态:连接中...
</div>
</div>
<script>
// 1. new WebSocket(url) → 建立连接
const websocket = new WebSocket('ws://192.168.1.1:8000/data/status');
// 心跳机制相关变量
let heartbeatTimer = null; // 心跳定时器
let reconnectTimer = null; // 重连定时器
let heartbeatInterval = 30000; // 心跳间隔:30秒(可根据服务器要求调整)
let reconnectDelay = 3000; // 重连延迟:3秒
let isManualClose = false; // 是否手动关闭
websocket.onopen = function () {
console.log('连接成功');
document.getElementById('status').textContent = '状态:已连接';
document.getElementById('status').style.background = '#d4edda';
// 启动心跳机制
startHeartbeat();
};
// 3. ws.onmessage → 收消息(项目里 90% 的逻辑都写在这里)
websocket.onmessage = function (event) {
console.log('收到消息:', event.data);
const statusDiv = document.getElementById('status');
statusDiv.innerHTML = `状态:已连接<br>收到消息: ${event.data}`;
};
websocket.onerror = function (event) {
console.log('连接错误:', event);
document.getElementById('status').textContent = '状态:连接错误';
document.getElementById('status').style.background = '#f8d7da';
};
websocket.onclose = function (event) {
console.log('连接关闭', event.code, event.reason);
document.getElementById('status').textContent = `状态:连接已关闭 (代码: ${event.code})`;
document.getElementById('status').style.background = '#fff3cd';
// 停止心跳
stopHeartbeat();
// 如果不是手动关闭,则尝试重连
if (!isManualClose) {
console.log('尝试重新连接...');
reconnectTimer = setTimeout(function () {
location.reload(); // 或者可以重新创建 WebSocket 连接
}, reconnectDelay);
}
};
// 2. ws.send(data) → 发消息
function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (websocket.readyState === WebSocket.OPEN) {
websocket.send(message);
console.log('发送消息:', message);
input.value = '';
} else {
alert('WebSocket 未连接,无法发送消息');
console.log('WebSocket 状态:', websocket.readyState);
}
}
// 心跳机制:定期发送心跳消息保持连接
function startHeartbeat() {
stopHeartbeat(); // 先清除可能存在的定时器
heartbeatTimer = setInterval(function () {
if (websocket.readyState === WebSocket.OPEN) {
// 发送心跳消息(根据服务器要求,可能是 'ping'、'heartbeat' 或 JSON 格式)
websocket.send(JSON.stringify({ type: 'ping' }));
console.log('发送心跳消息');
} else {
// 如果连接已关闭,停止心跳
stopHeartbeat();
}
}, heartbeatInterval);
}
function stopHeartbeat() {
if (heartbeatTimer) {
clearInterval(heartbeatTimer);
heartbeatTimer = null;
}
}
// 4. ws.close() → 关连接(一定要记得关,不然会漏连接!)
function closeConnection() {
isManualClose = true; // 标记为手动关闭
stopHeartbeat(); // 停止心跳
if (websocket.readyState === WebSocket.OPEN || websocket.readyState === WebSocket.CONNECTING) {
websocket.close();
console.log('主动关闭连接');
} else {
alert('连接已关闭或未连接');
}
}
// 页面关闭时自动关闭连接(防止内存泄漏)
window.addEventListener('beforeunload', function () {
isManualClose = true; // 标记为手动关闭,避免触发重连
stopHeartbeat(); // 停止心跳
if (websocket.readyState === WebSocket.OPEN) {
websocket.close();
}
});
</script>
</body>
六、封装Websocket方法。
第一步:连接管理 + 自动重连 + 状态监听(附代码)
第二步:消息总线 + 消息去重 + 消息队列(断网缓存)
第三步:心跳检测 + 超时主动断开
/**
* websocket 管理器
* @param {string} url - websocket 地址
* @param {Object} options - 配置选项
* @param {number} options.maxReconnectCount - 最大重连次数,默认 10
* @param {number} options.heartbeatInterval - 心跳间隔时间(ms),默认 30000
* @param {number} options.reconnectInterval - 重连间隔时间(ms),默认 3000
* @param {boolean} options.autoConnect - 是否自动连接,默认 true
* @param {boolean} options.enableHeartbeat - 是否启用心跳,默认 true
* @param {Function} options.onOpen - 连接打开回调
* @param {Function} options.onMessage - 消息接收回调
* @param {Function} options.onError - 错误回调
* @param {Function} options.onClose - 连接关闭回调
*/
class WebSocketManager {
constructor(url, options = {}) {
this.url = url;
this.ws = null; // 原生 WebSocket 实例
this.isConnected = false; // 当前是否真正连接成功
this.isClosed = false; // 是否被手动关闭(手动关闭后不再自动重连)
this.reconnectTimer = null; // 重连定时器
this.heartbeatTimer = null; // 心跳定时器
this.reconnectCount = 0; // 已重连次数
// 配置
this.maxReconnectCount = options.maxReconnectCount ?? 10;
this.heartbeatInterval = options.heartbeatInterval || 30000;
this.reconnectInterval = options.reconnectInterval || 3000;
this.autoConnect = options.autoConnect !== false;
this.enableHeartbeat = options.enableHeartbeat !== false;
// 外部回调
this.onOpenCallback = options.onOpen || null;
this.onMessageCallback = options.onMessage || null;
this.onErrorCallback = options.onError || null;
this.onCloseCallback = options.onClose || null;
if (this.autoConnect) this.init();
}
/**
* 初始化 WebSocket 连接(核心入口)
* 每次重连都会调用此方法,负责创建新的 WebSocket 实例并绑定所有事件
*/
init() {
if (this.isClosed) return;
this.ws = new WebSocket(this.url);
// 连接成功:重置重连次数,启动心跳,通知上层
this.ws.onopen = () => {
this.isConnected = true;
this.reconnectCount = 0; // 成功连接必须清零!!!
if (this.enableHeartbeat) {
this.startHeartbeat();
}
this.onOpenCallback?.();
};
// 收到消息:统一处理心跳响应和业务消息
this.ws.onmessage = (event) => {
this.handleMessage(event.data);
};
// 连接出错:仅打印日志,不直接触发重连(onclose 会处理)
this.ws.onerror = (err) => {
console.error("WebSocket 错误:", err);
this.isConnected = false;
this.stopHeartbeat();
this.onErrorCallback?.(err);
};
// 连接关闭:清理状态 + 判断是否需要自动重连
this.ws.onclose = () => {
this.isConnected = false;
this.stopHeartbeat();
this.ws = null;
this.onCloseCallback?.();
// 只有非手动关闭才触发重连
if (!this.isClosed) {
this.reconnect();
}
};
}
/**
* 发送消息(安全发送)
* 自动判断连接状态,未连接时直接丢弃(或后续可加入队列缓存)
*/
send(data) {
if (
!this.isConnected ||
!this.ws ||
this.ws.readyState !== WebSocket.OPEN
) {
console.warn("WebSocket 未连接或已关闭");
return;
}
this.ws.send(typeof data === "object" ? JSON.stringify(data) : data);
}
/**
* 统一消息处理中心
* 负责:心跳响应识别 + 业务消息转发(支持 uni-app 和 纯 H5 两套事件总线)
*/
handleMessage(data) {
// 如果外部传了回调,优先走外部逻辑
if (this.onMessageCallback) {
this.onMessageCallback(data);
return;
}
try {
const message = JSON.parse(data);
// 心跳响应直接消费,不向上抛
if (message.type === "pong") return;
// 业务消息:兼容 uni-app 和 纯浏览器
if (typeof uni !== "undefined" && uni.$emit) {
uni.$emit("ws-message", message);
} else {
window.dispatchEvent(
new CustomEvent("ws-message", { detail: message })
);
}
} catch (e) {
// 非 JSON 消息原样转发(某些服务端发纯文本)
if (typeof uni !== "undefined" && uni.$emit) {
uni.$emit("ws-message", data);
} else {
window.dispatchEvent(new CustomEvent("ws-message", { detail: data }));
}
}
}
/**
* 启动心跳机制
* 负责:周期性发送心跳消息,并处理心跳响应
*/
startHeartbeat() {
this.stopHeartbeat();
this.heartbeatTimer = setInterval(() => {
this.send({ type: "ping" });
}, this.heartbeatInterval);
}
/**
* 停止心跳机制
* 负责:清除心跳定时器
*/
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
/**
* 重连机制
* 负责:定时器控制重连次数和间隔
*/
reconnect() {
if (this.isClosed) return;
if (this.reconnectTimer) return;
this.reconnectTimer = setTimeout(() => {
this.reconnectCount++;
console.log(`开始第 ${this.reconnectCount} 次重连,目标: ${this.url}`);
this.reconnectTimer = null;
this.init();
}, this.reconnectInterval);
}
/**
* 关闭连接
* 负责:标记关闭状态,清理定时器和连接实例
*/
close() {
this.isClosed = true;
this.isConnected = false;
this.stopHeartbeat();
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
if (this.ws) {
this.ws.close(); // 4. 直接 close()
this.ws = null;
}
}
/**
* 手动重连
* 负责:重置状态,重新初始化连接
*/
reconnectManually() {
this.isClosed = false;
this.reconnectCount = 0;
this.init();
}
/**
* 获取连接状态
* 负责:返回当前连接状态
*/
getStatus() {
return {
isConnected: this.isConnected,
isClosed: this.isClosed,
reconnectCount: this.reconnectCount,
url: this.url,
};
}
}
export default WebSocketManager;
七、使用方式
// store中使用
// stores/useWsStore.js
import { defineStore } from 'pinia'
import WebSocketManager from '@/utils/WebSocketManager.js'
export const useWsStore = defineStore('ws', {
state: () => ({
ws: null, // WebSocketManager 实例
connected: false, // 连接状态
messages: [], // 所有历史消息(可选)
latest: null, // 最新一条数据
isLoading: false,
error: null,
}),
actions: {
// 连接(整个项目只调用一次)
connect(url = 'wss://your-server.com/ws') {
if (this.ws) this.disconnect()
this.isLoading = true
this.ws = new WebSocketManager(url, {
heartbeatInterval: 10000,
reconnectInterval: 2000,
onOpen: () => {
this.connected = true
this.isLoading = false
this.error = null
console.log('WebSocket 已连接')
},
onMessage: (data) => {
// 统一把所有消息存起来
this.latest = data
this.messages.push({
data,
timestamp: Date.now(),
})
try {
this.latest = JSON.parse(data)
} catch {}
},
onError: (e) => {
this.error = '连接出错'
this.isLoading = false
},
onClose: () => {
this.connected = false
this.isLoading = false
console.log('已断开,自动重连中...')
},
})
},
// 发送消息
send(payload) {
if (this.ws && this.connected) {
this.ws.send(payload)
} else {
console.warn('WebSocket 未连接,消息已丢弃', payload)
}
},
// 断开
disconnect() {
this.ws?.close()
this.ws = null
this.connected = false
this.isLoading = false
},
// 清空消息记录
clear() {
this.messages = []
this.latest = null
},
},
})
页面中使用
import { useWsStore } from '@/stores/useWsStore'
import { storeToRefs } from 'pinia'
import { onMounted } from 'vue'
const ws = useWsStore()
const { connected, latest, messages, isLoading } = storeToRefs(ws)
onMounted(() => { ws.connect('wss://your-real-url.com/api/ws') // 改成你的地址})
const sendHello = () => { ws.send({ type: 'say', content: 'hello world' })}