黔西南布依族苗族自治州网站建设_网站建设公司_MySQL_seo优化
2026/1/18 21:42:28 网站建设 项目流程

一、核心概念与必要性

为什么需要心跳检测和重连?

  • 网络不稳定:移动网络、Wi-Fi切换、代理服务器可能导致连接断开

  • 服务器限制:Nginx/负载均衡器默认30-60秒超时

  • 浏览器限制:部分浏览器标签页休眠时暂停WebSocket

  • 资源清理:服务器需要清理僵尸连接

二、完整实现方案

1. WebSocket管理器类(TypeScript实现)

typescript

复制

下载

interface WebSocketConfig { url: string; protocols?: string | string[]; reconnectInterval?: number; // 重连间隔(ms) maxReconnectAttempts?: number; // 最大重连次数 heartbeatInterval?: number; // 心跳间隔(ms) heartbeatTimeout?: number; // 心跳超时(ms) } enum WebSocketState { CONNECTING = 0, OPEN = 1, CLOSING = 2, CLOSED = 3 } class WebSocketManager { private ws: WebSocket | null = null; private config: Required<WebSocketConfig>; private reconnectAttempts = 0; private heartbeatTimer: NodeJS.Timeout | null = null; private heartbeatTimeoutTimer: NodeJS.Timeout | null = null; private reconnectTimer: NodeJS.Timeout | null = null; private isManualClose = false; // 是否手动关闭 // 事件监听器 private listeners = { open: [] as ((event: Event) => void)[], message: [] as ((data: any) => void)[], close: [] as ((event: CloseEvent) => void)[], error: [] as ((event: Event) => void)[], reconnect: [] as ((attempt: number) => void)[], }; constructor(config: WebSocketConfig) { this.config = { url: config.url, protocols: config.protocols || [], reconnectInterval: config.reconnectInterval || 3000, maxReconnectAttempts: config.maxReconnectAttempts || 5, heartbeatInterval: config.heartbeatInterval || 30000, heartbeatTimeout: config.heartbeatTimeout || 10000, }; } /** * 连接WebSocket */ public connect(): void { if (this.ws?.readyState === WebSocketState.OPEN || this.ws?.readyState === WebSocketState.CONNECTING) { console.warn('WebSocket is already connecting or connected'); return; } this.isManualClose = false; try { this.ws = new WebSocket(this.config.url, this.config.protocols); this.setupEventListeners(); } catch (error) { console.error('WebSocket creation error:', error); this.handleReconnect(); } } /** * 设置事件监听器 */ private setupEventListeners(): void { if (!this.ws) return; this.ws.onopen = (event: Event) => { console.log('WebSocket connected'); this.reconnectAttempts = 0; this.startHeartbeat(); this.emit('open', event); }; this.ws.onmessage = (event: MessageEvent) => { try { const data = JSON.parse(event.data); // 处理心跳响应 if (data.type === 'pong' || data.type === 'heartbeat') { this.resetHeartbeat(); return; } this.emit('message', data); } catch (error) { // 非JSON数据直接传递 this.emit('message', event.data); } }; this.ws.onclose = (event: CloseEvent) => { console.log(`WebSocket closed: ${event.code} ${event.reason}`); this.cleanupHeartbeat(); // 非手动关闭才重连 if (!this.isManualClose) { this.handleReconnect(); } this.emit('close', event); }; this.ws.onerror = (event: Event) => { console.error('WebSocket error:', event); this.emit('error', event); // 错误时也尝试重连 if (!this.isManualClose) { this.handleReconnect(); } }; } /** * 开始心跳检测 */ private startHeartbeat(): void { this.cleanupHeartbeat(); this.heartbeatTimer = setInterval(() => { if (this.ws?.readyState === WebSocketState.OPEN) { this.sendHeartbeat(); this.startHeartbeatTimeout(); } }, this.config.heartbeatInterval); } /** * 发送心跳包 */ private sendHeartbeat(): void { if (this.ws?.readyState === WebSocketState.OPEN) { const heartbeatMsg = JSON.stringify({ type: 'ping', timestamp: Date.now(), }); this.ws.send(heartbeatMsg); } } /** * 开始心跳超时检测 */ private startHeartbeatTimeout(): void { this.cleanupHeartbeatTimeout(); this.heartbeatTimeoutTimer = setTimeout(() => { console.warn('Heartbeat timeout, reconnecting...'); this.close(); this.handleReconnect(); }, this.config.heartbeatTimeout); } /** * 重置心跳(收到响应时调用) */ private resetHeartbeat(): void { this.cleanupHeartbeatTimeout(); } /** * 清理心跳定时器 */ private cleanupHeartbeat(): void { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } this.cleanupHeartbeatTimeout(); } /** * 清理心跳超时定时器 */ private cleanupHeartbeatTimeout(): void { if (this.heartbeatTimeoutTimer) { clearTimeout(this.heartbeatTimeoutTimer); this.heartbeatTimeoutTimer = null; } } /** * 处理重连逻辑 */ private handleReconnect(): void { this.cleanupReconnectTimer(); // 达到最大重连次数 if (this.reconnectAttempts >= this.config.maxReconnectAttempts) { console.error('Max reconnection attempts reached'); return; } this.reconnectAttempts++; console.log(`Reconnecting... Attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts}`); this.emit('reconnect', this.reconnectAttempts); // 指数退避算法:重连间隔逐渐增加 const delay = Math.min( this.config.reconnectInterval * Math.pow(1.5, this.reconnectAttempts - 1), 30000 // 最大30秒 ); this.reconnectTimer = setTimeout(() => { this.connect(); }, delay); } /** * 清理重连定时器 */ private cleanupReconnectTimer(): void { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } } /** * 发送消息 */ public send(data: any): boolean { if (this.ws?.readyState !== WebSocketState.OPEN) { console.error('WebSocket is not connected'); return false; } try { const message = typeof data === 'string' ? data : JSON.stringify(data); this.ws.send(message); return true; } catch (error) { console.error('Send message error:', error); return false; } } /** * 手动关闭连接 */ public close(code?: number, reason?: string): void { this.isManualClose = true; this.cleanupHeartbeat(); this.cleanupReconnectTimer(); if (this.ws) { this.ws.close(code || 1000, reason); this.ws = null; } } /** * 获取连接状态 */ public getState(): WebSocketState { return this.ws?.readyState || WebSocketState.CLOSED; } /** * 添加事件监听 */ public on<T extends keyof typeof this.listeners>( event: T, callback: typeof this.listeners[T][number] ): void { this.listeners[event].push(callback as any); } /** * 移除事件监听 */ public off<T extends keyof typeof this.listeners>( event: T, callback: typeof this.listeners[T][number] ): void { const index = this.listeners[event].indexOf(callback as any); if (index > -1) { this.listeners[event].splice(index, 1); } } /** * 触发事件 */ private emit<T extends keyof typeof this.listeners>( event: T, ...args: Parameters<typeof this.listeners[T][number]> ): void { this.listeners[event].forEach(callback => { try { (callback as any)(...args); } catch (error) { console.error(`Error in ${event} event handler:`, error); } }); } /** * 销毁实例 */ public destroy(): void { this.close(); this.listeners = { open: [], message: [], close: [], error: [], reconnect: [], }; } }

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】​​​

2. React Hook封装(React + TypeScript)

typescript

复制

下载

import { useEffect, useRef, useCallback, useState } from 'react'; interface UseWebSocketOptions { onMessage?: (data: any) => void; onOpen?: (event: Event) => void; onClose?: (event: CloseEvent) => void; onError?: (event: Event) => void; onReconnect?: (attempt: number) => void; heartbeatInterval?: number; reconnectInterval?: number; } export const useWebSocket = ( url: string, options: UseWebSocketOptions = {} ) => { const wsManager = useRef<WebSocketManager | null>(null); const [isConnected, setIsConnected] = useState(false); const [reconnectCount, setReconnectCount] = useState(0); const connect = useCallback(() => { if (wsManager.current) { wsManager.current.destroy(); } wsManager.current = new WebSocketManager({ url, heartbeatInterval: options.heartbeatInterval, reconnectInterval: options.reconnectInterval, }); wsManager.current.on('open', (event) => { setIsConnected(true); setReconnectCount(0); options.onOpen?.(event); }); wsManager.current.on('message', (data) => { options.onMessage?.(data); }); wsManager.current.on('close', (event) => { setIsConnected(false); options.onClose?.(event); }); wsManager.current.on('error', (event) => { options.onError?.(event); }); wsManager.current.on('reconnect', (attempt) => { setReconnectCount(attempt); options.onReconnect?.(attempt); }); wsManager.current.connect(); }, [url, options]); const disconnect = useCallback(() => { wsManager.current?.close(); setIsConnected(false); }, []); const send = useCallback((data: any) => { return wsManager.current?.send(data) || false; }, []); // 自动连接和清理 useEffect(() => { connect(); return () => { wsManager.current?.destroy(); }; }, [connect]); // 网络状态变化监听 useEffect(() => { const handleOnline = () => { console.log('Network online, reconnecting...'); if (!isConnected) { connect(); } }; const handleOffline = () => { console.log('Network offline'); disconnect(); }; window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, [connect, disconnect, isConnected]); return { isConnected, reconnectCount, send, connect, disconnect, }; }; // 使用示例 export const ChatComponent: React.FC = () => { const [messages, setMessages] = useState<string[]>([]); const { isConnected, send } = useWebSocket('wss://api.example.com/ws', { onMessage: (data) => { setMessages(prev => [...prev, data.content]); }, onReconnect: (attempt) => { console.log(`Reconnection attempt ${attempt}`); }, }); const handleSend = () => { send({ type: 'chat', content: 'Hello' }); }; return ( <div> <div>Status: {isConnected ? 'Connected' : 'Disconnected'}</div> <button onClick={handleSend} disabled={!isConnected}> Send Message </button> <div> {messages.map((msg, index) => ( <div key={index}>{msg}</div> ))} </div> </div> ); };

3. 服务器端心跳处理(Node.js)

javascript

复制

下载

const WebSocket = require('ws'); class WebSocketServerWithHeartbeat { constructor(server) { this.wss = new WebSocket.Server({ server }); this.clients = new Map(); // clientId -> {ws, lastPing, isAlive} this.heartbeatInterval = 30000; this.maxMissedPings = 3; this.setup(); } setup() { this.wss.on('connection', (ws, request) => { const clientId = this.generateClientId(); console.log(`New connection: ${clientId}`); this.clients.set(clientId, { ws, lastPing: Date.now(), isAlive: true, }); // 发送欢迎消息 ws.send(JSON.stringify({ type: 'welcome', clientId, timestamp: Date.now(), })); // 消息处理 ws.on('message', (data) => { try { const message = JSON.parse(data); this.handleMessage(clientId, message); } catch (error) { console.error('Message parse error:', error); } }); // 心跳响应 ws.on('pong', () => { const client = this.clients.get(clientId); if (client) { client.isAlive = true; client.lastPing = Date.now(); } }); // 连接关闭 ws.on('close', () => { console.log(`Connection closed: ${clientId}`); this.clients.delete(clientId); }); // 错误处理 ws.on('error', (error) => { console.error(`WebSocket error for ${clientId}:`, error); this.clients.delete(clientId); }); }); // 定期心跳检测 setInterval(() => { this.checkHeartbeats(); }, this.heartbeatInterval); } handleMessage(clientId, message) { const client = this.clients.get(clientId); if (!client) return; switch (message.type) { case 'ping': // 心跳响应 client.ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now(), })); client.isAlive = true; client.lastPing = Date.now(); break; case 'chat': // 广播消息 this.broadcast({ type: 'chat', clientId, content: message.content, timestamp: Date.now(), }); break; default: console.log(`Unknown message type: ${message.type}`); } } checkHeartbeats() { const now = Date.now(); for (const [clientId, client] of this.clients.entries()) { const timeSinceLastPing = now - client.lastPing; // 超过最大时间没有心跳 if (timeSinceLastPing > this.heartbeatInterval * this.maxMissedPings) { console.log(`Client ${clientId} heartbeat timeout, closing connection`); client.ws.terminate(); this.clients.delete(clientId); continue; } // 发送心跳检测 if (!client.isAlive) { console.log(`Client ${clientId} not responding to pings`); client.ws.terminate(); this.clients.delete(clientId); continue; } // 标记为需要检测,发送ping client.isAlive = false; try { client.ws.ping(); } catch (error) { console.error(`Ping error for ${clientId}:`, error); this.clients.delete(clientId); } } } broadcast(message) { const data = JSON.stringify(message); this.wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(data); } }); } sendToClient(clientId, message) { const client = this.clients.get(clientId); if (client && client.ws.readyState === WebSocket.OPEN) { client.ws.send(JSON.stringify(message)); } } generateClientId() { return `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } getConnectedCount() { return this.clients.size; } } // 使用示例 const http = require('http'); const server = http.createServer(); const wssWithHeartbeat = new WebSocketServerWithHeartbeat(server); server.listen(8080, () => { console.log('WebSocket server with heartbeat listening on port 8080'); });

4. 高级特性:连接质量监控

typescript

复制

下载

interface ConnectionMetrics { latency: number; // 延迟(ms) jitter: number; // 抖动(ms) packetLoss: number; // 丢包率(%) bandwidth: number; // 带宽(kbps) uptime: number; // 连接时长(ms) } class ConnectionMonitor { private metrics: ConnectionMetrics = { latency: 0, jitter: 0, packetLoss: 0, bandwidth: 0, uptime: 0, }; private latencySamples: number[] = []; private startTime: number = Date.now(); private sentPackets = 0; private receivedPackets = 0; recordLatency(latency: number) { this.latencySamples.push(latency); // 保留最近100个样本 if (this.latencySamples.length > 100) { this.latencySamples.shift(); } // 计算平均延迟 this.metrics.latency = this.latencySamples.reduce((a, b) => a + b, 0) / this.latencySamples.length; // 计算抖动(延迟的标准差) const variance = this.latencySamples.reduce((sum, sample) => { return sum + Math.pow(sample - this.metrics.latency, 2); }, 0) / this.latencySamples.length; this.metrics.jitter = Math.sqrt(variance); } recordPacket(sent: boolean) { if (sent) { this.sentPackets++; } else { this.receivedPackets++; } if (this.sentPackets > 0) { this.metrics.packetLoss = ((this.sentPackets - this.receivedPackets) / this.sentPackets) * 100; } } updateUptime() { this.metrics.uptime = Date.now() - this.startTime; } getMetrics(): ConnectionMetrics { this.updateUptime(); return { ...this.metrics }; } reset() { this.startTime = Date.now(); this.sentPackets = 0; this.receivedPackets = 0; this.latencySamples = []; this.metrics = { latency: 0, jitter: 0, packetLoss: 0, bandwidth: 0, uptime: 0, }; } } // 集成到WebSocketManager class EnhancedWebSocketManager extends WebSocketManager { private connectionMonitor = new ConnectionMonitor(); private metricsTimer: NodeJS.Timeout | null = null; // 重写发送心跳方法 protected sendHeartbeat(): void { if (this.ws?.readyState === WebSocketState.OPEN) { const sendTime = Date.now(); const heartbeatMsg = JSON.stringify({ type: 'ping', timestamp: sendTime, }); this.connectionMonitor.recordPacket(true); this.ws.send(heartbeatMsg); this.startHeartbeatTimeout(); } } // 处理心跳响应时记录延迟 protected resetHeartbeat(): void { super.resetHeartbeat(); // 假设在消息处理中记录了接收时间 const receiveTime = Date.now(); // 这里需要存储发送时间来计算延迟 } // 开始监控 startMetricsCollection() { this.metricsTimer = setInterval(() => { const metrics = this.connectionMonitor.getMetrics(); console.log('Connection metrics:', metrics); // 根据指标调整策略 if (metrics.packetLoss > 20) { console.warn('High packet loss, consider reducing message frequency'); } if (metrics.latency > 1000) { console.warn('High latency, adjusting heartbeat interval'); } }, 5000); } stopMetricsCollection() { if (this.metricsTimer) { clearInterval(this.metricsTimer); this.metricsTimer = null; } } }

三、最佳实践与优化建议

1. 心跳参数配置建议

javascript

复制

下载

// 不同场景下的推荐配置 const configs = { // 实时聊天应用 CHAT: { heartbeatInterval: 25000, // 25秒 heartbeatTimeout: 10000, // 10秒 reconnectInterval: 2000, // 2秒 maxReconnectAttempts: 10, // 最多重连10次 }, // 实时数据监控 MONITORING: { heartbeatInterval: 15000, // 15秒 heartbeatTimeout: 5000, // 5秒 reconnectInterval: 1000, // 1秒 maxReconnectAttempts: 20, // 最多重连20次 }, // 游戏应用 GAMING: { heartbeatInterval: 10000, // 10秒 heartbeatTimeout: 3000, // 3秒 reconnectInterval: 500, // 0.5秒 maxReconnectAttempts: 5, // 最多重连5次 }, };

2. 重连策略优化

typescript

复制

下载

// 智能重连策略 class SmartReconnectStrategy { private failures = 0; private lastFailureTime = 0; shouldReconnect(): boolean { const now = Date.now(); // 短时间内连续失败,可能是网络问题 if (now - this.lastFailureTime < 10000 && this.failures > 3) { console.log('Too many failures in short time, waiting...'); return false; } // 失败次数太多,可能是服务器问题 if (this.failures > 10) { console.log('Too many failures, giving up'); return false; } return true; } recordFailure() { this.failures++; this.lastFailureTime = Date.now(); // 逐渐重置失败计数 setTimeout(() => { if (this.failures > 0) { this.failures--; } }, 60000); // 每分钟减少一次失败计数 } recordSuccess() { this.failures = 0; } }

3. 离线消息队列

typescript

复制

下载

class OfflineMessageQueue { private queue: Array<{data: any, timestamp: number}> = []; private maxQueueSize = 100; private maxAge = 5 * 60 * 1000; // 5分钟 addMessage(data: any) { // 清理过期消息 this.cleanup(); // 队列满时移除最旧的消息 if (this.queue.length >= this.maxQueueSize) { this.queue.shift(); } this.queue.push({ data, timestamp: Date.now(), }); } getMessages(): any[] { this.cleanup(); return this.queue.map(item => item.data); } clear() { this.queue = []; } private cleanup() { const now = Date.now(); this.queue = this.queue.filter(item => now - item.timestamp <= this.maxAge ); } } // 在WebSocketManager中使用 class WebSocketManagerWithQueue extends WebSocketManager { private offlineQueue = new OfflineMessageQueue(); send(data: any): boolean { const success = super.send(data); if (!success) { // 发送失败,存储到离线队列 this.offlineQueue.addMessage(data); return false; } return true; } protected onOpen(event: Event): void { super.onOpen(event); // 连接成功后发送离线消息 const offlineMessages = this.offlineQueue.getMessages(); offlineMessages.forEach(message => { super.send(message); }); this.offlineQueue.clear(); } }

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】​​​

四、常见问题与解决方案

问题1:心跳包增加了流量开销

解决方案

  • 使用二进制ping/pong帧(1字节)

  • 动态调整心跳频率(根据网络质量)

  • 允许客户端协商心跳间隔

问题2:重连风暴导致服务器压力

解决方案

  • 使用随机延迟避免同时重连

  • 实现退避算法(exponential backoff)

  • 服务器端限制重连频率

问题3:移动端网络切换

解决方案

  • 监听网络状态变化事件

  • 网络恢复后延迟重连

  • 使用短连接心跳确认网络质量

问题4:多标签页重复连接

解决方案

typescript

复制

下载

// 使用BroadcastChannel或SharedWorker协调多标签页 class MultiTabWebSocketManager { private channel: BroadcastChannel; constructor() { this.channel = new BroadcastChannel('websocket_control'); this.channel.addEventListener('message', (event) => { if (event.data.type === 'tab_connected') { // 其他标签页已连接,本标签页保持只读 this.setReadonlyMode(); } }); // 通知其他标签页 this.channel.postMessage({ type: 'tab_connected' }); } }

五、测试方案

typescript

复制

下载

// 模拟网络不稳定的测试工具 class NetworkSimulator { static async testWebSocket(wsManager: WebSocketManager) { // 测试正常连接 console.log('Test 1: Normal connection'); wsManager.connect(); await this.delay(2000); // 模拟网络中断 console.log('Test 2: Simulating network loss'); this.simulateNetworkLoss(); await this.delay(5000); // 恢复网络 console.log('Test 3: Network recovery'); this.restoreNetwork(); await this.delay(10000); // 模拟服务器重启 console.log('Test 4: Simulating server restart'); wsManager.close(1001, 'Server restart'); await this.delay(3000); // 验证自动重连 console.log('Test 5: Verifying auto-reconnect'); await this.delay(15000); } private static delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } private static simulateNetworkLoss() { // 实际项目中可能需要修改网络配置 console.log('Network loss simulated'); } private static restoreNetwork() { console.log('Network restored'); } }

六、总结

关键要点:

  1. 心跳检测:防止连接被中间设备断开,及时检测连接状态

  2. 自动重连:提供无缝的用户体验,减少手动干预

  3. 智能策略:根据网络质量动态调整参数

  4. 资源管理:及时清理无效连接和定时器

  5. 错误处理:优雅处理各种网络异常情况

推荐库:

  • 客户端:reconnecting-websocket@gamestdio/websocket

  • 服务器端:ws(Node.js)、gorilla/websocket(Go)、Spring WebSocket(Java)

通过以上实现,可以构建一个健壮、可靠的WebSocket连接,能够应对各种网络异常情况,提供良好的用户体验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询