概覽
本文將手把手教你如何從零編寫一個可用于直播或在線聊天的 WSocket 類,依次實現連接建立、心跳檢測、斷線重連、消息收發以及資源清理等功能。我們將結合 WebSocket API 的標準用法、心跳保持 和 重連策略,并充分運用現代 JavaScript 語法(如類、解構、箭頭函數)來確保代碼既高效又易讀。
1. 創建基礎 WebSocket 連接
1.1 引入與初始化
首先,新建一個 WSocket
類,接受目標 URL 和回調函數作為構造參數:
class WSocket {constructor({ url, onMessage, onDisconnect }) {this.url = url;this.onMessage = onMessage;this.onDisconnect = onDisconnect;this.ws = null;this.reconnectCount = 0;}
}
-
url
:WebSocket 服務器地址。 -
onMessage
/onDisconnect
:消息與斷開回調。
1.2 建立連接
在類中添加 connect()
方法,使用原生 API 建立并綁定事件:
connect() {this.ws = new WebSocket(this.url);this.ws.binaryType = 'arraybuffer'; // 二進制類型 this.ws.onopen = () => this.handleOpen(); // 連接成功:contentReference[oaicite:0]{index=0} this.ws.onmessage = e => this.handleMessage(e); // 收到消息 this.ws.onclose = e => this.handleClose(e); // 連接關閉 this.ws.onerror = e => this.handleError(e); // 錯誤處理
}
2. 實現心跳機制
2.1 為什么要心跳
許多網絡設備會在連接空閑一定時長后斷開,心跳可保持連接活躍并及時發現斷線MDN Web Docs。
2.2 心跳代碼
const DEFAULTS = { HEART_INTERVAL: 10000, STATUS_CHECK: 3000 };handleOpen() {this.clearTimers();this.startHeartbeat();
}startHeartbeat() {this.heartbeatTimer = setInterval(() => {if (this.ws.readyState === WebSocket.OPEN) {this.ws.send(JSON.stringify({ type: 'PING' })); // 心跳包 }}, DEFAULTS.HEART_INTERVAL);this.statusCheckTimer = setInterval(() => {if (this.ws.readyState !== WebSocket.OPEN) {this.reconnect(); // 狀態檢查:contentReference[oaicite:3]{index=3} }}, DEFAULTS.STATUS_CHECK);
}clearTimers() {clearInterval(this.heartbeatTimer);clearInterval(this.statusCheckTimer);
}
3. 自動重連策略
3.1 基本重連
在斷線或錯誤時,調用 reconnect()
:
reconnect() {if (this.reconnecting) return;this.reconnecting = true;this.clearTimers();setTimeout(() => {this.reconnectCount++;if (this.reconnectCount <= 5) { // 最多重連5次 this.connect();} else {this.onDisconnect();}this.reconnecting = false;}, 2000); // 延遲2秒重連:contentReference[oaicite:5]{index=5}
}handleClose(e) {console.warn('WebSocket closed:', e);this.reconnect();
}handleError(e) {console.error('WebSocket error:', e);this.reconnect();
}
4. 消息接收與分發
在 handleMessage
中解析并交給用戶回調:
handleMessage(event) {try {const data = event.data instanceof ArrayBuffer? JSON.parse(new TextDecoder().decode(event.data)): JSON.parse(event.data);this.onMessage(data);} catch (err) {console.error('Message parse error:', err);}
}
-
兼容二進制/文本:用
TextDecoder
解碼二進制MDN Web Docs。 -
異常捕獲:防止單條消息解析失敗導致整個連接中斷Stack Overflow。
5. 完整示例代碼
class WSocket {constructor({ url, onMessage, onDisconnect }) {this.url = url;this.onMessage = onMessage;this.onDisconnect = onDisconnect;this.ws = null;this.heartbeatTimer = null;this.statusCheckTimer = null;this.reconnecting = false;this.reconnectCount = 0;}connect() {this.ws = new WebSocket(this.url);this.ws.binaryType = 'arraybuffer';this.ws.onopen = () => this.handleOpen();this.ws.onmessage = e => this.handleMessage(e);this.ws.onclose = e => this.handleClose(e);this.ws.onerror = e => this.handleError(e);}handleOpen() {this.reconnectCount = 0;this.clearTimers();this.startHeartbeat();console.log('WebSocket connected');}startHeartbeat() {this.heartbeatTimer = setInterval(() => {if (this.ws.readyState === WebSocket.OPEN) {this.ws.send(JSON.stringify({ type: 'PING' }));}}, DEFAULTS.HEART_INTERVAL);this.statusCheckTimer = setInterval(() => {if (this.ws.readyState !== WebSocket.OPEN) this.reconnect();}, DEFAULTS.STATUS_CHECK);}clearTimers() {clearInterval(this.heartbeatTimer);clearInterval(this.statusCheckTimer);}reconnect() {if (this.reconnecting) return;this.reconnecting = true;this.clearTimers();setTimeout(() => {this.reconnectCount++;if (this.reconnectCount <= 5) {this.connect();} else {this.onDisconnect();}this.reconnecting = false;}, 2000);}handleMessage(event) {try {const raw = event.data instanceof ArrayBuffer? new TextDecoder().decode(event.data): event.data;const data = JSON.parse(raw);this.onMessage(data);} catch (err) {console.error('Message parse error:', err);}}handleClose(e) {console.warn('WebSocket closed:', e);this.reconnect();}handleError(e) {console.error('WebSocket error:', e);this.reconnect();}send(data) {if (this.ws?.readyState === WebSocket.OPEN) {this.ws.send(typeof data === 'string' ? data : JSON.stringify(data));}}disconnect() {this.clearTimers();this.ws?.close();}
}
6. 使用示例
const ws = new WSocket({url: 'wss://example.com/live',onMessage: msg => console.log('Recv:', msg),onDisconnect: () => alert('Connection lost')
});ws.connect();// 發送消息
ws.send({ type: 'CHAT', content: 'Hello world' });// 手動斷開
// ws.disconnect();