背景
在我做过的一个对接海康威视监控实现直播流的项目中,需要处理 WebSocket 的长连接,以便于和服务器保持实时通信。WebSocket 的一个挑战是连接的稳定性,特别是在网络波动或断开时,如何确保能自动重连并保持通信流畅。为了简化这一过程,我决定封装一个 WebSocket 类,使得开发者无需每次都重复编写连接、心跳、重连等逻辑。
这个 CustomSocket 类支持以下功能:
- 心跳机制:定时发送心跳包,确保连接不会因长时间无数据传输而被关闭。
- 断线重连:连接断开后,自动尝试重连,最大重连次数可配置。
- 手动连接:支持手动发起连接,适用于取消自动重连并重新启动连接。
- 消息回调:支持自定义处理接收到的消息,并且能够过滤心跳消息。
- 连接状态感知:可以实时监听 WebSocket 的连接状态,便于进行调试和优化。
- 调试日志:在 debug 模式下,可以输出详细的连接状态和操作日志,帮助调试和排查问题。
代码实现
仅此我项目够用了,便不封装太多避免太复杂繁琐,诸君有需要的可拿去更改。
以下是 CustomSocket 类的完整实现:
/**
* WebSocket 封装类
* 功能:自动重连、心跳机制、状态管理、消息回调
*/
const SOCKET_STATUS = {
CONNECTING: "connecting", // 正在连接
CONNECTED: "connected", // 已连接
DISCONNECTED: "disconnected", // 已断开
RECONNECTING: "reconnecting", // 正在重连
CLOSED: "closed", // 已手动关闭
};
export class CustomSocket {
/**
* 构造函数
* @param {string} url - WebSocket 服务器地址
* @param {Object} options - 配置项
// ===== 配置项 =====
* @param {number} [options.heartbeatInterval=10000] - 心跳发送间隔(毫秒)
* @param {number} [options.reconnectDelay=5000] - 重连延迟时间(毫秒)
* @param {string} [options.pingMessage='ping'] - 发送心跳消息内容
* @param {string} [options.pongMessage='pong'] - 响应心跳消息内容
* @param {Function} [options.onMessage] - 接收到非心跳消息时的回调
* @param {Function} [options.onOpen] - 连接成功时的回调
* @param {Function} [options.onError] - 连接错误时的回调
* @param {Function} [options.onClose] - 连接关闭时的回调
* @param {Function} [options.onReconnect] - 尝试重连时的回调
* @param {Function} [options.onStatusChange] - 任意状态变更时的回调(如 connected, disconnected, reconnecting, closed, connecting)
* @param {Function} [options.onConnecting] - 启动连接前的回调(包括首次连接 & 重连)
* @param {boolean} [options.debug=false] - 是否打印调试日志
* @param {number} [options.maxReconnectAttempts=Infinity] - 最大重连次数,默认无限
*/
constructor(url, options = {}) {
this.url = url; // WebSocket 地址
this.socket = null; // WebSocket 实例
// 默认配置项
this.heartbeatInterval = options.heartbeatInterval || 10000; // 心跳间隔时间,默认 10 秒
this.reconnectDelay = options.reconnectDelay || 5000; // 重连等待时间,默认 5 秒
this.pingMessage = options.pingMessage || "ping"; // 发送心跳消息内容
this.pongMessage = options.pongMessage || "pong"; // 响应心跳消息内容
this.debug = options.debug || false; // 是否启用调试日志
this.maxReconnectAttempts = options.maxReconnectAttempts || Infinity; // 最大重连次数
// ===== 回调函数(支持注入)=====
const noop = () => {};
this.onMessage = options.onMessage || noop; // 消息接收回调
this.onOpen = options.onOpen || noop; // 连接开启回调
this.onError = options.onError || noop; // 连接错误回调
this.onClose = options.onClose || noop; // 连接关闭回调
this.onReconnect = options.onReconnect || noop; // 重连时回调
this.onStatusChange = options.onStatusChange || noop; // 状态变更统一回调
this.onConnecting = options.onConnecting || noop; // 连接开始回调
// 状态字段
this.isConnected = false; // 当前是否连接成功
this.isReconnecting = false; // 当前是否正在重连
this.manualClose = false; // 当前是手动关闭
this.reconnectAttempts = 0; // 当前重连尝试次数
this.lastPongTime = null; // 上一次收到 pong 的时间戳
// 定时器句柄
this.heartbeatTimer = null; // 心跳定时器
this.reconnectTimer = null; // 重连定时器
// 初始化连接
this.init();
}
/**
* 获取当前 WebSocket 连接状态(供外部状态感知使用)
* @returns {string} - 当前状态
*/
getStatus() {
if (this.isConnected) return SOCKET_STATUS.CONNECTED;
if (this.isReconnecting) return SOCKET_STATUS.RECONNECTING;
if (this.socket && this.socket.readyState === WebSocket.CONNECTING)
return SOCKET_STATUS.CONNECTING;
return SOCKET_STATUS.DISCONNECTED;
}
/**
* 初始化 WebSocket 连接并设置事件监听
*/
init() {
this.manualClose = false; // 每次新建连接都重置
this.onStatusChange(SOCKET_STATUS.CONNECTING); // 新增:状态回调
this.onConnecting(); // 页面可用来显示 loading
this.socket = new WebSocket(this.url);
// 连接成功
this.socket.onopen = () => {
this.isConnected = true;
this.isReconnecting = false;
this.reconnectAttempts = 0;
this.debug && console.log("WebSocket opened.");
this.onStatusChange("connected");
this.onOpen();
this.startHeartbeat();
};
// 接收消息
this.socket.onmessage = (msg) => {
// 处理 pong 响应(心跳回复)
if (msg.data === this.pongMessage) {
this.lastPongTime = Date.now(); // 记录收到心跳回应时间
return; // 不传给业务逻辑
}
this.onMessage(msg); // 非心跳业务消息,转发给用户
};
// 连接出错
this.socket.onerror = (err) => {
this.debug && console.error("WebSocket error:", err);
this.onError(err);
};
// 连接关闭
this.socket.onclose = () => {
this.debug && console.warn("WebSocket closed.");
this.isConnected = false;
this.stopHeartbeat();
this.onClose();
this.onStatusChange("disconnected");
if (!this.manualClose) {
this.debug &&
console.warn(
"WebSocket closed unexpectedly, attempting to reconnect..."
);
this.reconnect();
} else {
this.debug && console.log("WebSocket closed manually, no reconnect.");
}
};
}
/**
* 手动连接(用于取消自动重连并立即发起连接)
*/
manualConnect() {
this.clearReconnectTimer(); // 清除已有的重连定时器
this.isReconnecting = false; // 重置重连标记
this.reconnectAttempts = 0; // 重置尝试次数
this.manualClose = false; // 标记为非手动关闭
this.init(); // 发起连接
}
/**
* 启动心跳检测,定时发送心跳消息
*/
startHeartbeat() {
this.stopHeartbeat(); // 防止重复定时器
this.heartbeatTimer = setInterval(() => {
if (this.socket.readyState === WebSocket.OPEN) {
this.socket.send(this.pingMessage);
this.debug && console.log("Heartbeat sent:", this.pingMessage);
}
}, this.heartbeatInterval);
}
/**
* 停止心跳检测
*/
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
/**
* 重连逻辑(含延时与重试限制)
*/
reconnect() {
// 已在重连中则不重复触发
if (this.reconnectTimer || this.isReconnecting) return;
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
this.debug &&
console.warn("Max reconnect attempts reached, stopping reconnect.");
this.clearReconnectTimer();
return;
}
this.isReconnecting = true;
this.reconnectAttempts++;
this.onStatusChange(SOCKET_STATUS.RECONNECTING);
this.onReconnect(); // 调用重连回调
// 延迟执行重连
this.reconnectTimer = setTimeout(() => {
this.clearReconnectTimer();
this.socket = null; // 销毁旧连接引用
this.debug &&
console.log(`Reconnecting... (Attempt ${this.reconnectAttempts})`);
this.init();
}, this.reconnectDelay);
}
/**
* 清除重连定时器
*/
clearReconnectTimer() {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
}
/**
* 发送消息,如果是对象会自动序列化为 JSON 字符串
* @param {string|Object} data - 要发送的数据
*/
send(data) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
if (typeof data === "object") {
data = JSON.stringify(data); // 如果是对象,自动序列化为 JSON 字符串
}
this.socket.send(data);
} else {
this.debug && console.warn("WebSocket 未连接,发送失败");
}
}
/**
* 主动关闭连接(将不再自动重连)
*/
close() {
this.manualClose = true; // 设置手动关闭标记
this.stopHeartbeat();
this.clearReconnectTimer();
if (this.socket) {
this.socket.close();
this.socket = null;
}
this.isConnected = false;
this.isReconnecting = false;
this.onStatusChange("closed");
}
}
使用示例(vue2)
import { CustomSocket } from './CustomSocket'; // 假设你的类文件名为 CustomSocket.js
initSocket() {
this.loading = true;
const host = `${this...}/host/${this.userInfo.user_id}`;
this.socketInstance = new CustomSocket(host, {
heartbeatInterval: 9000, // 每 9 秒发送一次心跳
maxReconnectAttempts: 3, // 最多重连 3 次
// 收到警报消息时的处理函数
onMessage: this.handleMsg,
// 开始连接前,展示 loading 状态
onConnecting: () => {
this.loading = true;
this.getState = "";
},
// 成功建立连接
onOpen: () => {
console.log("Alert WebSocket connected");
Notification.closeAll(); // 清除旧的警报提示
this.loading = false;
this.getState = "success";
},
// 断线重连提示
onReconnect: () => {
Notification.error({
title: "提示",
message: "待处理警报连接断开,正在尝试重连",
duration: 10000,
});
},
// 连接异常
onError: (err) => {
console.error("Alert WebSocket error", err);
this.loading = false;
this.getState = "error";
},
// 连接关闭
onClose: () => {
console.warn("Alert WebSocket closed");
this.loading = false;
this.getState = "error";
},
});
}
记得关闭页面时销毁
beforeDestroy() {
if (this.socketInstance) {
this.socketInstance.close();
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。