Skip to content

JavaScript的WebSocket封装类

WebSocket封装类

javascript
/**
 * WebSocket 封装类
 * - 支持心跳检测
 * - 支持心跳超时延时重连
 * - 支持断线延时重连
 */
export class SuWebSocket {
    /**
     * 构造函数
     * @param {string} url WebSocket 地址
     */
    constructor(url) {
        // WebSocket 地址
        this.url = url;
        /**
         * WebSocket 实例
         * @type {WebSocket}
         */
        this.handle = null;
        // 心跳检测定时器
        this.heartBeatTimer = null;
        // 重连定时器
        this.reconnectTimer = null;
        // 心跳超时检测定时器
        this.heartBeatTimeoutTimer = null;
        // 重连间隔(毫秒)
        this.reconnectDelay = 5000;
        // 心跳检测间隔(毫秒)
        this.heartBeatInterval = 30000;
        // 心跳检测超时(毫秒)
        this.heartBeatTimeout = 10000;
        // 是否处于连接中
        this.isConnecting = false;
        // 重连次数
        this.reconnectAttempts = 0;
        // 是否允许重连
        this.allowReconnect = true;
        // 回调函数:连接成功
        this.onOpenCallback = null;
        // 回调函数:接收到消息
        this.onMessageCallback = null;
        // 回调函数:连接关闭
        this.onCloseCallback = null;
        // 回调函数:连接错误
        this.onErrorCallback = null;
    }

    /**
     * 设置连接成功回调
     * @param {Function} callback
     */
    onOpen(callback) {
        this.onOpenCallback = callback;
    }

    /**
     * 设置消息接收回调
     * @param {Function} callback
     */
    onMessage(callback) {
        this.onMessageCallback = callback;
    }

    /**
     * 设置连接关闭回调
     * @param {Function} callback
     */
    onClose(callback) {
        this.onCloseCallback = callback;
    }

    /**
     * 设置错误回调
     * @param {Function} callback
     */
    onError(callback) {
        this.onErrorCallback = callback;
    }

    /**
     * 设置是否允许重连
     * @param {boolean} allow 是否允许重连
     */
    setAllowReconnect(allow) {
        this.allowReconnect = allow;
    }

    /**
     * 建立 WebSocket 连接
     */
    connect() {
        if (this.isConnecting || this.isOpen()) {
            return;
        }

        this.isConnecting = true;
        this.allowReconnect = true;

        this.handle = new WebSocket(this.url);
        this.bindSocketEvents();
    }

    /**
     * 绑定 WebSocket 事件
     */
    bindSocketEvents() {
        // 监听连接打开
        this.handle.onopen = () => {
            console.log('WebSocket 连接已打开');
            this.isConnecting = false;
            this.reconnectAttempts = 0;
            this.startHeartBeat();
            if (this.onOpenCallback) {
                this.onOpenCallback();
            }
        };

        // 监听消息接收
        this.handle.onmessage = (res) => {
            console.log('收到服务器消息:', res.data);
            if (res.data === 'pong') {
                this.handlePong();
                return;
            }
            if (this.onMessageCallback) {
                this.onMessageCallback(res.data);
            }
        };

        // 监听连接关闭
        this.handle.onclose = () => {
            console.log('WebSocket 连接已关闭');
            this.isConnecting = false;
            this.stopHeartBeat();
            if (this.onCloseCallback) {
                this.onCloseCallback();
            }
            this.handleReconnect();
        };

        // 监听连接错误
        this.handle.onerror = (err) => {
            console.error('WebSocket 发生错误:', err);
            this.isConnecting = false;
            this.stopHeartBeat();
            if (this.onErrorCallback) {
                this.onErrorCallback(err);
            }
            this.handleReconnect();
        };
    }

    /**
     * 开始心跳检测
     */
    startHeartBeat() {
        this.stopHeartBeat();
        this.heartBeatTimer = setInterval(() => {
            this.sendPing();
        }, this.heartBeatInterval);
    }

    /**
     * 停止心跳检测
     */
    stopHeartBeat() {
        if (this.heartBeatTimer) {
            clearInterval(this.heartBeatTimer);
            this.heartBeatTimer = null;
        }

        // 清除心跳超时定时器
        if (this.heartBeatTimeoutTimer) {
            clearTimeout(this.heartBeatTimeoutTimer);
            this.heartBeatTimeoutTimer = null;
        }
    }

    /**
     * 发送 ping 心跳包
     */
    sendPing() {
        this.send('ping');

        if (this.heartBeatTimeoutTimer) {
            clearTimeout(this.heartBeatTimeoutTimer);
        }

        // 启动心跳超时定时器
        this.heartBeatTimeoutTimer = setTimeout(() => {
            this.handleHeartBeatTimeout();
        }, this.heartBeatTimeout);
    }

    /**
     * 处理 pong 响应
     */
    handlePong() {
        console.log('收到服务器 pong 响应');
        // 清除心跳超时定时器
        if (this.heartBeatTimeoutTimer) {
            clearTimeout(this.heartBeatTimeoutTimer);
            this.heartBeatTimeoutTimer = null;
        }
    }

    /**
     * 处理心跳超时
     */
    handleHeartBeatTimeout() {
        console.warn('心跳超时,未收到服务器 pong 响应');
        // 关闭当前连接并触发重连
        this.close();
        this.handleReconnect();
    }

    /**
     * 处理重连逻辑
     */
    handleReconnect() {
        // 如果不允许重连,则不执行重连逻辑
        if (!this.allowReconnect) {
            return;
        }

        if (this.reconnectTimer) {
            clearTimeout(this.reconnectTimer);
        }

        this.reconnectTimer = setTimeout(() => {
            this.reconnectAttempts++;
            console.log(`WebSocket 尝试重连第 ${this.reconnectAttempts} 次`);
            this.connect();
        }, this.reconnectDelay);
    }

    /**
     * 关闭 WebSocket 连接
     */
    close() {
        if (this.reconnectTimer) {
            clearTimeout(this.reconnectTimer);
            this.reconnectTimer = null;
        }

        this.stopHeartBeat();

        if (this.handle) {
            this.handle.close();
            this.handle = null;
        }

        this.isConnecting = false;
    }

    /**
     * 发送消息
     * @param {*} data
     */
    send(data) {
        if (this.isOpen()) {
            this.handle.send(data);
        } else {
            console.warn('WebSocket 未连接,无法发送消息');
        }
    }

    /**
     * 销毁WebSocket实例
     */
    destroy() {
        // 设置不允许重连,防止在关闭过程中触发重连
        this.allowReconnect = false;
        this.close();
    }

    /**
     * 获取连接状态
     * @return {boolean}
     */
    isOpen() {
        return this.handle && this.handle.readyState === WebSocket.OPEN;
    }
}

/**
 * 创建 WebSocket 实例
 * @param {string} url
 * @return {SuWebSocket}
 */
export function makeWebSocket(url) {
    return new SuWebSocket(url);
}