心跳機制在 WebSocket 通信中是一種常用的技術(shù),用于維持連接的穩(wěn)定性、檢測連接是否正常。以下為你詳細(xì)介紹在前端使用 WebSocket 時如何實現(xiàn)心跳機制,以及相關(guān)代碼示例。
實現(xiàn)思路
- 發(fā)送心跳包:客戶端定期向服務(wù)器發(fā)送一個特定格式的消息(心跳包),以表明自己處于活躍狀態(tài)。
- 接收響應(yīng):服務(wù)器收到心跳包后,返回一個響應(yīng)消息,客戶端通過檢查是否收到響應(yīng)來判斷連接是否正常。
- 超時處理:如果客戶端在一定時間內(nèi)沒有收到服務(wù)器的響應(yīng),認(rèn)為連接可能出現(xiàn)問題,嘗試重新連接。
示例代碼
class WebSocketClient {
constructor(url) {
this.url = url;
this.socket = null;
this.reconnectInterval = 5000;
this.reconnectTimer = null;
this.messageHandlers = [];
this.errorHandlers = [];
this.openHandlers = [];
this.closeHandlers = [];
this.heartbeatInterval = 3000;
this.heartbeatTimer = null;
this.lastHeartbeatResponseTime = null;
this.heartbeatTimeout = 5000;
this.heartbeatTimeoutTimer = null;
this.tryConnect();
}
tryConnect() {
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log('WebSocket 連接已建立');
clearInterval(this.reconnectTimer);
this.openHandlers.forEach(handler => handler());
this.startHeartbeat();
};
this.socket.onmessage = (event) => {
if (event.data === 'heartbeat_response') {
this.lastHeartbeatResponseTime = Date.now();
clearTimeout(this.heartbeatTimeoutTimer);
this.heartbeatTimeoutTimer = setTimeout(() => {
this.handleHeartbeatTimeout();
}, this.heartbeatTimeout);
} else {
this.messageHandlers.forEach(handler => handler(event.data));
}
};
this.socket.onerror = (error) => {
console.error('WebSocket 連接出錯:', error);
this.errorHandlers.forEach(handler => handler(error));
this.reconnect();
};
this.socket.onclose = (event) => {
console.log('WebSocket 連接已關(guān)閉,代碼:', event.code, '原因:', event.reason);
this.closeHandlers.forEach(handler => handler(event));
this.reconnect();
this.stopHeartbeat();
};
}
reconnect() {
if (!this.reconnectTimer) {
this.reconnectTimer = setInterval(() => {
console.log('嘗試重新連接 WebSocket...');
this.tryConnect();
}, this.reconnectInterval);
}
}
sendMessage(message) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(message);
} else {
console.error('無法發(fā)送消息,WebSocket 未連接');
}
}
onMessage(handler) {
this.messageHandlers.push(handler);
}
onError(handler) {
this.errorHandlers.push(handler);
}
onOpen(handler) {
this.openHandlers.push(handler);
}
onClose(handler) {
this.closeHandlers.push(handler);
}
close() {
if (this.socket) {
clearInterval(this.reconnectTimer);
this.socket.close();
this.stopHeartbeat();
}
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.sendMessage('heartbeat');
this.heartbeatTimeoutTimer = setTimeout(() => {
this.handleHeartbeatTimeout();
}, this.heartbeatTimeout);
}, this.heartbeatInterval);
}
stopHeartbeat() {
clearInterval(this.heartbeatTimer);
clearTimeout(this.heartbeatTimeoutTimer);
}
handleHeartbeatTimeout() {
console.log('心跳超時,嘗試重新連接...');
this.socket.close();
}
}
const socketClient = new WebSocketClient('ws://echo.websocket.org');
socketClient.onMessage((message) => {
console.log('收到消息:', message);
});
socketClient.onOpen(() => {
console.log('連接已打開');
});
socketClient.onError((error) => {
console.error('發(fā)生錯誤:', error);
});
socketClient.onClose((event) => {
console.log('連接關(guān)閉:', event);
});
代碼解釋
- 構(gòu)造函數(shù):除了初始化基本的 WebSocket 連接參數(shù)外,還初始化了心跳機制相關(guān)的參數(shù),如心跳間隔時間
heartbeatInterval
、心跳超時時間 heartbeatTimeout
等。 startHeartbeat
方法:使用 setInterval
定時發(fā)送心跳包(heartbeat
),并啟動一個定時器 heartbeatTimeoutTimer
用于檢測心跳超時。onmessage
事件處理:當(dāng)收到服務(wù)器的心跳響應(yīng)消息(heartbeat_response
)時,更新最后一次收到響應(yīng)的時間 lastHeartbeatResponseTime
,并重置心跳超時定時器。handleHeartbeatTimeout
方法:當(dāng)心跳超時發(fā)生時,關(guān)閉當(dāng)前的 WebSocket 連接,觸發(fā)重連機制。stopHeartbeat
方法:停止心跳定時器和超時定時器。
通過上述代碼,你可以在前端 WebSocket 通信中實現(xiàn)一個簡單的心跳機制,確保連接的穩(wěn)定性。同時,服務(wù)器端也需要相應(yīng)地處理心跳包并返回響應(yīng)消息。
分享
用ping\pong來實現(xiàn)
在 WebSocket 通信里,ping
和 pong
是標(biāo)準(zhǔn)的心跳機制實現(xiàn)方式。ping
是客戶端或服務(wù)器發(fā)送的探測消息,pong
則是對 ping
的響應(yīng)。下面是一個使用 JavaScript 封裝 WebSocket 并利用 ping
/pong
實現(xiàn)心跳機制的詳細(xì)示例。
實現(xiàn)思路
- 客戶端:周期性地發(fā)送
ping
消息,同時監(jiān)聽 pong
響應(yīng),若超時未收到 pong
則認(rèn)為連接異常。 - 服務(wù)器端:接收到
ping
消息后,立即返回 pong
響應(yīng)。
前端代碼示例
class WebSocketWithPingPong {
constructor(url) {
this.url = url;
this.socket = null;
this.pingInterval = 5000;
this.pongTimeout = 3000;
this.pingTimer = null;
this.pongTimeoutTimer = null;
this.reconnectInterval = 5000;
this.reconnectTimer = null;
this.messageHandlers = [];
this.errorHandlers = [];
this.openHandlers = [];
this.closeHandlers = [];
this.connect();
}
connect() {
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log('WebSocket 連接已建立');
clearInterval(this.reconnectTimer);
this.openHandlers.forEach(handler => handler());
this.startPing();
};
this.socket.onmessage = (event) => {
if (event.data === 'pong') {
clearTimeout(this.pongTimeoutTimer);
} else {
this.messageHandlers.forEach(handler => handler(event.data));
}
};
this.socket.onerror = (error) => {
console.error('WebSocket 連接出錯:', error);
this.errorHandlers.forEach(handler => handler(error));
this.reconnect();
};
this.socket.onclose = (event) => {
console.log('WebSocket 連接已關(guān)閉,代碼:', event.code, '原因:', event.reason);
this.closeHandlers.forEach(handler => handler(event));
this.reconnect();
this.stopPing();
};
}
reconnect() {
if (!this.reconnectTimer) {
this.reconnectTimer = setInterval(() => {
console.log('嘗試重新連接 WebSocket...');
this.connect();
}, this.reconnectInterval);
}
}
sendMessage(message) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(message);
} else {
console.error('無法發(fā)送消息,WebSocket 未連接');
}
}
onMessage(handler) {
this.messageHandlers.push(handler);
}
onError(handler) {
this.errorHandlers.push(handler);
}
onOpen(handler) {
this.openHandlers.push(handler);
}
onClose(handler) {
this.closeHandlers.push(handler);
}
close() {
if (this.socket) {
clearInterval(this.reconnectTimer);
this.socket.close();
this.stopPing();
}
}
startPing() {
this.pingTimer = setInterval(() => {
this.sendMessage('ping');
this.pongTimeoutTimer = setTimeout(() => {
this.handlePongTimeout();
}, this.pongTimeout);
}, this.pingInterval);
}
stopPing() {
clearInterval(this.pingTimer);
clearTimeout(this.pongTimeoutTimer);
}
handlePongTimeout() {
console.log('未收到 pong 響應(yīng),嘗試重新連接...');
this.socket.close();
}
}
const socket = new WebSocketWithPingPong('ws://your-websocket-server-url');
socket.onOpen(() => {
console.log('連接已打開');
});
socket.onMessage((message) => {
console.log('收到消息:', message);
});
socket.onError((error) => {
console.error('發(fā)生錯誤:', error);
});
socket.onClose((event) => {
console.log('連接關(guān)閉:', event);
});
代碼解釋
- 構(gòu)造函數(shù):初始化 WebSocket 連接所需的參數(shù),包括
ping
發(fā)送間隔、pong
超時時間、重連間隔等,并且調(diào)用 connect
方法開始建立連接。 connect
方法:創(chuàng)建 WebSocket 實例,為 onopen
、onmessage
、onerror
和 onclose
事件綁定處理函數(shù)。連接建立成功后,啟動 ping
發(fā)送機制。startPing
方法:使用 setInterval
周期性地發(fā)送 ping
消息,同時啟動一個 pong
超時定時器。onmessage
事件處理:當(dāng)收到 pong
響應(yīng)時,清除 pong
超時定時器;若收到其他消息,則調(diào)用注冊的消息處理函數(shù)。handlePongTimeout
方法:若在 pong
超時時間內(nèi)未收到 pong
響應(yīng),關(guān)閉當(dāng)前連接并觸發(fā)重連機制。- 其他方法:如
sendMessage
用于發(fā)送消息,onMessage
、onError
、onOpen
和 onClose
用于注冊相應(yīng)的事件處理函數(shù),close
用于關(guān)閉連接,stopPing
用于停止 ping
發(fā)送和 pong
超時檢測。
服務(wù)器端示例(使用 Node.js 和 ws
庫)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (message) => {
if (message === 'ping') {
ws.send('pong');
} else {
console.log('收到消息:', message);
}
});
});
console.log('WebSocket 服務(wù)器已啟動,監(jiān)聽端口 8080');
服務(wù)器端代碼解釋
- 使用
ws
庫創(chuàng)建一個 WebSocket 服務(wù)器,監(jiān)聽 8080 端口。 - 當(dāng)有客戶端連接時,監(jiān)聽
message
事件,若收到 ping
消息,立即返回 pong
響應(yīng);若收到其他消息,則進行相應(yīng)處理。