🎬 HoRain 云小助手个人主页

⛺️生活的理想,就是为了理想的生活!


⛳️ 推荐

前些天发现了一个超棒的服务器购买网站,性价比超高,大内存超划算!忍不住分享一下给大家。点击跳转到网站。

目录

⛳️ 推荐

📊 核心架构设计

心跳检测机制

🔧 前端完整实现(JavaScript)

1. WebSocket管理器类

2. 使用示例

🔧 后端实现示例(Node.js + ws库)

1. WebSocket服务器

🔧 后端实现示例(Spring Boot)

⚡ 性能优化与最佳实践

1. 网络优化策略

2. 消息队列和重发机制

3. 连接质量监控

🚨 错误处理与恢复

1. 错误分类处理

2. 降级策略

💎 最佳实践总结

1. 心跳检测配置建议

2. 安全考虑

3. 监控和日志

4. 测试策略


实现WebSocket心跳检测和断线重连是构建可靠实时应用的关键。下面我将提供完整的前后端解决方案,涵盖核心机制、错误处理和最佳实践。

📊 核心架构设计

心跳检测机制

// 心跳检测状态机
class HeartbeatState {
    constructor() {
        this.pingInterval = null;      // 发送ping的定时器
        this.pongTimeout = null;       // 等待pong的超时
        this.lastPongTime = null;      // 最后一次收到pong的时间
        this.pingSent = false;         // 是否已发送ping
    }
}

🔧 前端完整实现(JavaScript)

1. WebSocket管理器类

class WebSocketManager {
    constructor(url, options = {}) {
        this.url = url;
        this.ws = null;
        this.options = {
            reconnectInterval: 1000,      // 重连间隔
            maxReconnectInterval: 30000,  // 最大重连间隔
            reconnectDecay: 1.5,          // 重连间隔增长因子
            timeoutInterval: 5000,        // 心跳超时时间
            pingInterval: 25000,          // 心跳间隔
            maxReconnectAttempts: Infinity, // 最大重连次数
            ...options
        };
        
        this.reconnectAttempts = 0;
        this.reconnectTimer = null;
        this.heartbeatTimer = null;
        this.pongTimeoutTimer = null;
        
        this.eventHandlers = {
            onopen: [],
            onclose: [],
            onmessage: [],
            onerror: [],
            onreconnect: []
        };
        
        this.init();
    }
    
    // 初始化连接
    init() {
        try {
            console.log(`[WebSocket] 正在连接到: ${this.url}`);
            this.ws = new WebSocket(this.url);
            
            this.ws.onopen = (event) => this.handleOpen(event);
            this.ws.onclose = (event) => this.handleClose(event);
            this.ws.onmessage = (event) => this.handleMessage(event);
            this.ws.onerror = (event) => this.handleError(event);
        } catch (error) {
            console.error('[WebSocket] 连接创建失败:', error);
            this.scheduleReconnect();
        }
    }
    
    // 连接打开
    handleOpen(event) {
        console.log('[WebSocket] 连接已建立');
        this.reconnectAttempts = 0;
        this.startHeartbeat();
        this.triggerEvent('onopen', event);
    }
    
    // 连接关闭
    handleClose(event) {
        console.log(`[WebSocket] 连接关闭,代码: ${event.code}, 原因: ${event.reason}`);
        
        // 清理心跳检测
        this.stopHeartbeat();
        
        // 如果不是正常关闭,尝试重连
        if (event.code !== 1000 && event.code !== 1001) {
            console.log('[WebSocket] 非正常关闭,尝试重连...');
            this.scheduleReconnect();
        }
        
        this.triggerEvent('onclose', event);
    }
    
    // 接收消息
    handleMessage(event) {
        try {
            const data = JSON.parse(event.data);
            
            // 处理心跳响应
            if (data.type === 'pong') {
                this.handlePong();
                return;
            }
            
            // 处理业务消息
            this.triggerEvent('onmessage', data);
        } catch (error) {
            console.warn('[WebSocket] 消息解析失败:', error);
        }
    }
    
    // 错误处理
    handleError(event) {
        console.error('[WebSocket] 连接错误:', event);
        this.triggerEvent('onerror', event);
    }
    
    // 启动心跳检测
    startHeartbeat() {
        this.stopHeartbeat();
        
        this.heartbeatTimer = setInterval(() => {
            if (this.ws && this.ws.readyState === WebSocket.OPEN) {
                this.sendPing();
            }
        }, this.options.pingInterval);
    }
    
    // 停止心跳检测
    stopHeartbeat() {
        if (this.heartbeatTimer) {
            clearInterval(this.heartbeatTimer);
            this.heartbeatTimer = null;
        }
        if (this.pongTimeoutTimer) {
            clearTimeout(this.pongTimeoutTimer);
            this.pongTimeoutTimer = null;
        }
    }
    
    // 发送ping
    sendPing() {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            const pingData = {
                type: 'ping',
                timestamp: Date.now()
            };
            
            this.ws.send(JSON.stringify(pingData));
            console.log('[WebSocket] 发送ping');
            
            // 设置pong超时检测
            this.pongTimeoutTimer = setTimeout(() => {
                console.warn('[WebSocket] 心跳响应超时,连接可能已断开');
                this.handleHeartbeatTimeout();
            }, this.options.timeoutInterval);
        }
    }
    
    // 处理pong响应
    handlePong() {
        if (this.pongTimeoutTimer) {
            clearTimeout(this.pongTimeoutTimer);
            this.pongTimeoutTimer = null;
        }
        console.log('[WebSocket] 收到pong,连接正常');
    }
    
    // 心跳超时处理
    handleHeartbeatTimeout() {
        console.warn('[WebSocket] 心跳检测失败,主动关闭连接');
        if (this.ws) {
            this.ws.close(1000, '心跳超时');
        }
    }
    
    // 调度重连
    scheduleReconnect() {
        if (this.reconnectTimer) {
            clearTimeout(this.reconnectTimer);
        }
        
        // 检查重连次数限制
        if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
            console.error(`[WebSocket] 已达到最大重连次数: ${this.options.maxReconnectAttempts}`);
            return;
        }
        
        // 计算重连延迟(指数退避)
        const delay = Math.min(
            this.options.reconnectInterval * Math.pow(this.options.reconnectDecay, this.reconnectAttempts),
            this.options.maxReconnectInterval
        );
        
        this.reconnectAttempts++;
        console.log(`[WebSocket] 将在 ${delay}ms 后尝试第 ${this.reconnectAttempts} 次重连`);
        
        this.reconnectTimer = setTimeout(() => {
            console.log(`[WebSocket] 开始第 ${this.reconnectAttempts} 次重连`);
            this.triggerEvent('onreconnect', { attempt: this.reconnectAttempts });
            this.init();
        }, delay);
    }
    
    // 发送消息
    send(data) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            if (typeof data !== 'string') {
                data = JSON.stringify(data);
            }
            this.ws.send(data);
            return true;
        } else {
            console.warn('[WebSocket] 连接未就绪,无法发送消息');
            return false;
        }
    }
    
    // 关闭连接
    close(code = 1000, reason = '正常关闭') {
        console.log('[WebSocket] 主动关闭连接');
        this.stopHeartbeat();
        if (this.reconnectTimer) {
            clearTimeout(this.reconnectTimer);
            this.reconnectTimer = null;
        }
        if (this.ws) {
            this.ws.close(code, reason);
        }
    }
    
    // 事件监听
    on(event, handler) {
        if (this.eventHandlers[event]) {
            this.eventHandlers[event].push(handler);
        }
    }
    
    // 触发事件
    triggerEvent(event, data) {
        if (this.eventHandlers[event]) {
            this.eventHandlers[event].forEach(handler => {
                try {
                    handler(data);
                } catch (error) {
                    console.error(`[WebSocket] 事件处理器错误: ${event}`, error);
                }
            });
        }
    }
    
    // 获取连接状态
    getState() {
        if (!this.ws) return 'DISCONNECTED';
        
        const states = {
            0: 'CONNECTING',
            1: 'OPEN',
            2: 'CLOSING',
            3: 'CLOSED'
        };
        return states[this.ws.readyState] || 'UNKNOWN';
    }
}

2. 使用示例

// 创建WebSocket管理器
const wsManager = new WebSocketManager('wss://api.example.com/ws', {
    reconnectInterval: 1000,
    maxReconnectInterval: 30000,
    reconnectDecay: 1.5,
    timeoutInterval: 5000,
    pingInterval: 25000,
    maxReconnectAttempts: 10
});

// 事件监听
wsManager.on('onopen', (event) => {
    console.log('连接已打开');
    
    // 连接成功后发送认证信息
    wsManager.send({
        type: 'auth',
        token: 'your_jwt_token'
    });
});

wsManager.on('onmessage', (data) => {
    console.log('收到消息:', data);
    // 处理业务逻辑
    if (data.type === 'notification') {
        showNotification(data.content);
    }
});

wsManager.on('onclose', (event) => {
    console.log('连接关闭');
    if (event.code === 1006) {
        console.log('网络异常断开');
    }
});

wsManager.on('onerror', (error) => {
    console.error('连接错误:', error);
});

wsManager.on('onreconnect', (data) => {
    console.log(`重连尝试 ${data.attempt}`);
    showReconnectStatus(`正在重新连接... (${data.attempt}/10)`);
});

// 发送消息
document.getElementById('sendBtn').addEventListener('click', () => {
    const message = {
        type: 'chat',
        content: document.getElementById('messageInput').value,
        timestamp: Date.now()
    };
    
    if (wsManager.send(message)) {
        console.log('消息发送成功');
    } else {
        console.log('消息发送失败,已加入发送队列');
        // 可以将消息加入队列,等连接恢复后发送
    }
});

// 页面可见性变化处理
document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
        // 页面隐藏时,可以减少心跳频率
        console.log('页面隐藏,调整心跳频率');
    } else {
        // 页面显示时,恢复心跳
        console.log('页面显示,检查连接状态');
        if (wsManager.getState() !== 'OPEN') {
            wsManager.scheduleReconnect();
        }
    }
});

// 网络状态变化处理
window.addEventListener('online', () => {
    console.log('网络恢复,尝试重连');
    wsManager.scheduleReconnect();
});

window.addEventListener('offline', () => {
    console.log('网络断开');
    // 可以在这里显示离线提示
});

// 关闭连接
window.addEventListener('beforeunload', () => {
    wsManager.close(1000, '页面关闭');
});

🔧 后端实现示例(Node.js + ws库)

1. WebSocket服务器

const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid');

class WebSocketServer {
    constructor(server) {
        this.wss = new WebSocket.Server({ 
            server,
            clientTracking: true,
            perMessageDeflate: false
        });
        
        this.clients = new Map(); // 客户端连接映射
        this.pingInterval = 30000; // 30秒发送一次ping
        this.connectionTimeout = 10000; // 10秒连接超时
        
        this.init();
    }
    
    init() {
        this.wss.on('connection', (ws, request) => {
            this.handleConnection(ws, request);
        });
        
        // 定期清理无效连接
        setInterval(() => this.cleanupConnections(), 60000);
        
        console.log('WebSocket服务器已启动');
    }
    
    handleConnection(ws, request) {
        const clientId = uuidv4();
        const clientInfo = {
            id: clientId,
            ws,
            ip: request.socket.remoteAddress,
            connectedAt: Date.now(),
            lastPong: Date.now(),
            isAlive: true,
            auth: false,
            userId: null
        };
        
        this.clients.set(clientId, clientInfo);
        console.log(`[${clientId}] 新客户端连接,IP: ${clientInfo.ip}`);
        
        // 设置连接超时
        const authTimeout = setTimeout(() => {
            if (!clientInfo.auth) {
                console.log(`[${clientId}] 认证超时,关闭连接`);
                ws.close(1008, '认证超时');
            }
        }, this.connectionTimeout);
        
        // 心跳检测
        const heartbeatInterval = setInterval(() => {
            if (!clientInfo.isAlive) {
                console.log(`[${clientId}] 心跳检测失败,关闭连接`);
                ws.terminate();
                return;
            }
            
            clientInfo.isAlive = false;
            this.send(ws, { type: 'ping', timestamp: Date.now() });
        }, this.pingInterval);
        
        // 消息处理
        ws.on('message', (data) => {
            try {
                const message = JSON.parse(data);
                this.handleMessage(clientId, message, ws);
            } catch (error) {
                console.error(`[${clientId}] 消息解析错误:`, error);
                this.sendError(ws, '消息格式错误');
            }
        });
        
        // 关闭连接
        ws.on('close', (code, reason) => {
            clearTimeout(authTimeout);
            clearInterval(heartbeatInterval);
            this.handleDisconnect(clientId, code, reason);
        });
        
        // 错误处理
        ws.on('error', (error) => {
            console.error(`[${clientId}] 连接错误:`, error);
        });
        
        // 发送欢迎消息
        this.send(ws, { 
            type: 'welcome', 
            clientId, 
            timestamp: Date.now() 
        });
    }
    
    handleMessage(clientId, message, ws) {
        const client = this.clients.get(clientId);
        if (!client) return;
        
        console.log(`[${clientId}] 收到消息:`, message.type);
        
        switch (message.type) {
            case 'pong':
                client.isAlive = true;
                client.lastPong = Date.now();
                break;
                
            case 'auth':
                this.handleAuth(clientId, message, ws);
                break;
                
            case 'chat':
                this.handleChat(clientId, message, ws);
                break;
                
            default:
                console.warn(`[${clientId}] 未知消息类型: ${message.type}`);
        }
    }
    
    handleAuth(clientId, message, ws) {
        const client = this.clients.get(clientId);
        
        try {
            // 这里应该验证token
            const token = message.token;
            // 验证逻辑...
            
            client.auth = true;
            client.userId = message.userId;
            
            this.send(ws, { 
                type: 'auth_success', 
                userId: client.userId 
            });
            
            console.log(`[${clientId}] 认证成功,用户ID: ${client.userId}`);
        } catch (error) {
            console.error(`[${clientId}] 认证失败:`, error);
            this.sendError(ws, '认证失败');
            ws.close(1008, '认证失败');
        }
    }
    
    handleChat(clientId, message, ws) {
        const client = this.clients.get(clientId);
        if (!client.auth) {
            this.sendError(ws, '未认证');
            return;
        }
        
        // 广播消息给所有客户端
        this.broadcast({
            type: 'chat',
            from: client.userId,
            content: message.content,
            timestamp: Date.now()
        }, clientId); // 排除发送者
        
        console.log(`[${clientId}] 用户 ${client.userId} 发送了聊天消息`);
    }
    
    handleDisconnect(clientId, code, reason) {
        const client = this.clients.get(clientId);
        if (client) {
            console.log(`[${clientId}] 连接关闭,代码: ${code}, 原因: ${reason}`);
            
            // 通知其他用户该用户离线
            if (client.auth) {
                this.broadcast({
                    type: 'user_offline',
                    userId: client.userId,
                    timestamp: Date.now()
                });
            }
            
            this.clients.delete(clientId);
        }
    }
    
    // 发送消息
    send(ws, data) {
        if (ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify(data));
        }
    }
    
    // 发送错误消息
    sendError(ws, error) {
        this.send(ws, { 
            type: 'error', 
            error, 
            timestamp: Date.now() 
        });
    }
    
    // 广播消息
    broadcast(data, excludeClientId = null) {
        for (const [clientId, client] of this.clients) {
            if (clientId !== excludeClientId && client.auth) {
                this.send(client.ws, data);
            }
        }
    }
    
    // 清理无效连接
    cleanupConnections() {
        const now = Date.now();
        const timeout = 60000; // 60秒
        
        for (const [clientId, client] of this.clients) {
            if (now - client.lastPong > timeout) {
                console.log(`[${clientId}] 连接超时,清理`);
                client.ws.terminate();
                this.clients.delete(clientId);
            }
        }
    }
    
    // 获取连接统计
    getStats() {
        return {
            total: this.clients.size,
            authenticated: Array.from(this.clients.values()).filter(c => c.auth).length,
            connections: Array.from(this.clients.values()).map(c => ({
                id: c.id,
                userId: c.userId,
                ip: c.ip,
                connectedAt: c.connectedAt,
                auth: c.auth
            }))
        };
    }
}

// 使用示例
const http = require('http');
const server = http.createServer();
const wss = new WebSocketServer(server);

server.listen(8080, () => {
    console.log('服务器监听在 8080 端口');
});

// 添加管理接口
server.on('request', (req, res) => {
    if (req.url === '/admin/stats') {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(wss.getStats()));
    }
});

🔧 后端实现示例(Spring Boot)

@Component
@ServerEndpoint("/ws")
public class WebSocketEndpoint {
    private static final Logger logger = LoggerFactory.getLogger(WebSocketEndpoint.class);
    
    // 存储所有连接
    private static final Map<String, Session> sessions = new ConcurrentHashMap<>();
    private static final Map<String, UserSession> userSessions = new ConcurrentHashMap<>();
    
    // 心跳超时时间(毫秒)
    private static final long HEARTBEAT_TIMEOUT = 30000;
    
    @OnOpen
    public void onOpen(Session session) {
        String sessionId = session.getId();
        sessions.put(sessionId, session);
        
        logger.info("新连接: {}", sessionId);
        
        // 发送欢迎消息
        sendMessage(session, new WsMessage("welcome", sessionId));
        
        // 设置心跳检测
        setupHeartbeat(session);
    }
    
    @OnMessage
    public void onMessage(String message, Session session) {
        try {
            WsMessage wsMessage = JsonUtils.parse(message, WsMessage.class);
            
            switch (wsMessage.getType()) {
                case "ping":
                    handlePing(session);
                    break;
                case "auth":
                    handleAuth(wsMessage, session);
                    break;
                case "chat":
                    handleChat(wsMessage, session);
                    break;
                default:
                    logger.warn("未知消息类型: {}", wsMessage.getType());
            }
        } catch (Exception e) {
            logger.error("消息处理错误", e);
            sendError(session, "消息格式错误");
        }
    }
    
    @OnClose
    public void onClose(Session session) {
        String sessionId = session.getId();
        sessions.remove(sessionId);
        
        UserSession userSession = userSessions.remove(sessionId);
        if (userSession != null) {
            logger.info("用户断开连接: {}", userSession.getUserId());
            
            // 广播用户离线
            broadcast(new WsMessage("user_offline", userSession.getUserId()), sessionId);
        }
        
        logger.info("连接关闭: {}", sessionId);
    }
    
    @OnError
    public void onError(Session session, Throwable error) {
        logger.error("WebSocket错误: {}", session.getId(), error);
    }
    
    private void handlePing(Session session) {
        // 更新心跳时间
        UserSession userSession = userSessions.get(session.getId());
        if (userSession != null) {
            userSession.setLastHeartbeat(System.currentTimeMillis());
        }
        
        // 回复pong
        sendMessage(session, new WsMessage("pong", null));
    }
    
    private void handleAuth(WsMessage message, Session session) {
        try {
            String token = message.getData().toString();
            // 验证token逻辑...
            String userId = verifyToken(token);
            
            UserSession userSession = new UserSession();
            userSession.setSessionId(session.getId());
            userSession.setUserId(userId);
            userSession.setLastHeartbeat(System.currentTimeMillis());
            
            userSessions.put(session.getId(), userSession);
            
            logger.info("用户认证成功: {}", userId);
            sendMessage(session, new WsMessage("auth_success", userId));
            
            // 广播用户上线
            broadcast(new WsMessage("user_online", userId), session.getId());
            
        } catch (Exception e) {
            logger.error("认证失败", e);
            sendError(session, "认证失败");
            try {
                session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "认证失败"));
            } catch (IOException ex) {
                logger.error("关闭连接失败", ex);
            }
        }
    }
    
    private void handleChat(WsMessage message, Session session) {
        UserSession userSession = userSessions.get(session.getId());
        if (userSession == null) {
            sendError(session, "未认证");
            return;
        }
        
        ChatMessage chatMessage = new ChatMessage();
        chatMessage.setFrom(userSession.getUserId());
        chatMessage.setContent(message.getData().toString());
        chatMessage.setTimestamp(System.currentTimeMillis());
        
        // 广播消息
        broadcast(new WsMessage("chat", chatMessage), session.getId());
    }
    
    private void sendMessage(Session session, WsMessage message) {
        if (session.isOpen()) {
            try {
                session.getBasicRemote().sendText(JsonUtils.stringify(message));
            } catch (IOException e) {
                logger.error("发送消息失败", e);
            }
        }
    }
    
    private void broadcast(WsMessage message, String excludeSessionId) {
        String messageJson = JsonUtils.stringify(message);
        
        sessions.forEach((sessionId, session) -> {
            if (!sessionId.equals(excludeSessionId) && session.isOpen()) {
                try {
                    session.getBasicRemote().sendText(messageJson);
                } catch (IOException e) {
                    logger.error("广播消息失败", e);
                }
            }
        });
    }
    
    private void sendError(Session session, String error) {
        sendMessage(session, new WsMessage("error", error));
    }
    
    private void setupHeartbeat(Session session) {
        // 定时检查心跳
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.scheduleAtFixedRate(() -> {
            UserSession userSession = userSessions.get(session.getId());
            if (userSession != null) {
                long now = System.currentTimeMillis();
                if (now - userSession.getLastHeartbeat() > HEARTBEAT_TIMEOUT) {
                    logger.warn("心跳超时,关闭连接: {}", userSession.getUserId());
                    try {
                        session.close();
                    } catch (IOException e) {
                        logger.error("关闭连接失败", e);
                    }
                }
            }
        }, 0, 30, TimeUnit.SECONDS);
    }
    
    // 管理接口
    public static Map<String, Object> getStats() {
        Map<String, Object> stats = new HashMap<>();
        stats.put("totalConnections", sessions.size());
        stats.put("authenticatedUsers", userSessions.size());
        return stats;
    }
}

⚡ 性能优化与最佳实践

1. 网络优化策略

// 自适应心跳间隔
class AdaptiveHeartbeat {
    constructor() {
        this.baseInterval = 30000; // 基础间隔
        this.maxInterval = 120000; // 最大间隔
        this.minInterval = 5000;   // 最小间隔
        this.currentInterval = this.baseInterval;
        this.networkQuality = 1.0; // 网络质量因子
    }
    
    // 根据网络状况调整心跳间隔
    adjustInterval(latency) {
        if (latency < 100) {
            // 网络良好,可以适当延长心跳间隔
            this.networkQuality = Math.min(1.2, this.networkQuality * 1.1);
        } else if (latency > 1000) {
            // 网络较差,缩短心跳间隔
            this.networkQuality = Math.max(0.5, this.networkQuality * 0.9);
        }
        
        this.currentInterval = Math.max(
            this.minInterval,
            Math.min(
                this.maxInterval,
                this.baseInterval * this.networkQuality
            )
        );
        
        return this.currentInterval;
    }
}

2. 消息队列和重发机制

class MessageQueue {
    constructor(wsManager) {
        this.queue = [];
        this.maxSize = 100;
        this.wsManager = wsManager;
    }
    
    // 添加消息到队列
    add(message) {
        if (this.queue.length >= this.maxSize) {
            this.queue.shift(); // 移除最旧的消息
        }
        
        const messageWithId = {
            ...message,
            id: Date.now() + '_' + Math.random().toString(36).substr(2, 9),
            timestamp: Date.now(),
            retryCount: 0,
            maxRetries: 3
        };
        
        this.queue.push(messageWithId);
        this.trySend();
    }
    
    // 尝试发送队列中的消息
    trySend() {
        if (this.wsManager.getState() !== 'OPEN') {
            return;
        }
        
        const now = Date.now();
        for (let i = 0; i < this.queue.length; i++) {
            const message = this.queue[i];
            
            // 检查重试次数
            if (message.retryCount >= message.maxRetries) {
                this.queue.splice(i, 1);
                i--;
                console.warn(`消息 ${message.id} 达到最大重试次数,已丢弃`);
                continue;
            }
            
            // 检查消息是否过期(30秒)
            if (now - message.timestamp > 30000) {
                this.queue.splice(i, 1);
                i--;
                console.warn(`消息 ${message.id} 已过期,已丢弃`);
                continue;
            }
            
            // 尝试发送
            if (this.wsManager.send(message)) {
                this.queue.splice(i, 1);
                i--;
                console.log(`消息 ${message.id} 发送成功`);
            } else {
                message.retryCount++;
                message.lastRetry = now;
                console.log(`消息 ${message.id} 发送失败,重试次数: ${message.retryCount}`);
            }
        }
    }
}

3. 连接质量监控

class ConnectionMonitor {
    constructor(wsManager) {
        this.wsManager = wsManager;
        this.metrics = {
            latency: [],
            packetLoss: 0,
            reconnectCount: 0,
            totalBytesSent: 0,
            totalBytesReceived: 0
        };
        
        this.startMonitoring();
    }
    
    startMonitoring() {
        // 监控网络延迟
        setInterval(() => {
            this.measureLatency();
        }, 30000);
        
        // 监控连接状态
        this.wsManager.on('onreconnect', () => {
            this.metrics.reconnectCount++;
        });
    }
    
    measureLatency() {
        const startTime = Date.now();
        
        this.wsManager.send({
            type: 'ping',
            timestamp: startTime
        });
        
        // 假设我们有机制记录往返时间
    }
    
    getConnectionQuality() {
        const recentLatency = this.metrics.latency.slice(-10);
        const avgLatency = recentLatency.length > 0 
            ? recentLatency.reduce((a, b) => a + b, 0) / recentLatency.length
            : 0;
        
        if (avgLatency < 100) return 'excellent';
        if (avgLatency < 300) return 'good';
        if (avgLatency < 1000) return 'fair';
        return 'poor';
    }
}

🚨 错误处理与恢复

1. 错误分类处理

class ErrorHandler {
    static handleWebSocketError(error, wsManager) {
        switch (error.code) {
            case 1000:
                console.log('正常关闭');
                break;
            case 1006:
                console.log('连接异常断开');
                wsManager.scheduleReconnect();
                break;
            case 1008:
                console.log('认证失败,需要重新登录');
                this.handleAuthenticationFailure();
                break;
            case 1011:
                console.log('服务器内部错误');
                wsManager.scheduleReconnect();
                break;
            case 1012:
                console.log('服务重启');
                setTimeout(() => wsManager.scheduleReconnect(), 5000);
                break;
            default:
                console.warn('未知错误代码:', error.code);
                wsManager.scheduleReconnect();
        }
    }
    
    static handleAuthenticationFailure() {
        // 清除本地token
        localStorage.removeItem('auth_token');
        // 跳转到登录页
        window.location.href = '/login';
    }
}

2. 降级策略

class FallbackStrategy {
    constructor(url) {
        this.url = url;
        this.fallbackMode = false;
        this.pollingInterval = null;
    }
    
    // 切换到轮询模式
    enablePolling() {
        console.log('WebSocket不可用,切换到轮询模式');
        this.fallbackMode = true;
        
        this.pollingInterval = setInterval(() => {
            this.poll();
        }, 5000); // 每5秒轮询一次
    }
    
    // 禁用轮询,恢复WebSocket
    disablePolling() {
        console.log('恢复WebSocket连接');
        this.fallbackMode = false;
        
        if (this.pollingInterval) {
            clearInterval(this.pollingInterval);
            this.pollingInterval = null;
        }
    }
    
    async poll() {
        try {
            const response = await fetch(`${this.url}/poll`);
            const data = await response.json();
            
            // 处理轮询返回的数据
            this.handlePollData(data);
            
        } catch (error) {
            console.error('轮询失败:', error);
        }
    }
    
    handlePollData(data) {
        // 处理轮询数据
    }
}

💎 最佳实践总结

1. 心跳检测配置建议

场景

心跳间隔

超时时间

重连策略

实时聊天

20-30秒

5-10秒

立即重连,指数退避

股票行情

10-15秒

3-5秒

立即重连,固定间隔

IoT设备

60-120秒

30秒

指数退避,最大重试10次

游戏

5-10秒

2-3秒

立即重连,无退避

2. 安全考虑

// 1. 认证机制
const authPayload = {
    type: 'auth',
    token: getJWTToken(),
    timestamp: Date.now(),
    sign: generateSignature() // 防止重放攻击
};

// 2. 消息加密
function encryptMessage(message, secretKey) {
    // 使用AES加密消息体
    return CryptoJS.AES.encrypt(
        JSON.stringify(message), 
        secretKey
    ).toString();
}

// 3. 频率限制
class RateLimiter {
    constructor(maxMessages, timeWindow) {
        this.messageCount = 0;
        this.lastReset = Date.now();
        this.maxMessages = maxMessages;
        this.timeWindow = timeWindow;
    }
    
    checkLimit() {
        const now = Date.now();
        
        // 重置计数器
        if (now - this.lastReset > this.timeWindow) {
            this.messageCount = 0;
            this.lastReset = now;
        }
        
        if (this.messageCount >= this.maxMessages) {
            return false;
        }
        
        this.messageCount++;
        return true;
    }
}

3. 监控和日志

// 集成Sentry等监控工具
class WebSocketMonitor {
    constructor(wsManager) {
        this.wsManager = wsManager;
        this.setupMonitoring();
    }
    
    setupMonitoring() {
        this.wsManager.on('onopen', () => {
            Sentry.captureMessage('WebSocket连接成功', 'info');
        });
        
        this.wsManager.on('onclose', (event) => {
            Sentry.captureMessage(`WebSocket连接关闭: ${event.code}`, 'warning');
        });
        
        this.wsManager.on('onreconnect', (data) => {
            if (data.attempt > 5) {
                Sentry.captureMessage(`WebSocket重连次数过多: ${data.attempt}`, 'error');
            }
        });
    }
}

4. 测试策略

// 连接稳定性测试
class ConnectionTester {
    async testConnection(url, duration = 60000) {
        const results = {
            totalTests: 0,
            successful: 0,
            failed: 0,
            avgLatency: 0,
            maxLatency: 0
        };
        
        const startTime = Date.now();
        
        while (Date.now() - startTime < duration) {
            try {
                const latency = await this.ping(url);
                results.totalTests++;
                results.successful++;
                results.avgLatency = (results.avgLatency + latency) / results.totalTests;
                results.maxLatency = Math.max(results.maxLatency, latency);
            } catch (error) {
                results.totalTests++;
                results.failed++;
            }
            
            await this.delay(1000);
        }
        
        return results;
    }
    
    async ping(url) {
        return new Promise((resolve, reject) => {
            const start = Date.now();
            const ws = new WebSocket(url);
            
            ws.onopen = () => {
                const latency = Date.now() - start;
                ws.close();
                resolve(latency);
            };
            
            ws.onerror = () => {
                reject(new Error('连接失败'));
            };
            
            setTimeout(() => {
                reject(new Error('连接超时'));
            }, 5000);
        });
    }
}

通过实现完整的心跳检测和断线重连机制,可以显著提升WebSocket应用的稳定性和用户体验。关键是要根据具体业务场景调整参数,并配合完善的监控和错误处理。

❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐