心跳機制在 WebSocket 通信中是一種常用的技術,用于維持連接的穩(wěn)定性、檢測連接是否正常。以下為你詳細介紹在前端使用 WebSocket 時如何實現(xiàn)心跳機制,以及相關代碼示例。
實現(xiàn)思路
- 發(fā)送心跳包:客戶端定期向服務器發(fā)送一個特定格式的消息(心跳包),以表明自己處于活躍狀態(tài)。
- 接收響應:服務器收到心跳包后,返回一個響應消息,客戶端通過檢查是否收到響應來判斷連接是否正常。
- 超時處理:如果客戶端在一定時間內沒有收到服務器的響應,認為連接可能出現(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 連接已關閉,代碼:', 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('連接關閉:', event);
});
代碼解釋
- 構造函數:除了初始化基本的 WebSocket 連接參數外,還初始化了心跳機制相關的參數,如心跳間隔時間
heartbeatInterval
、心跳超時時間 heartbeatTimeout
等。 startHeartbeat
方法:使用 setInterval
定時發(fā)送心跳包(heartbeat
),并啟動一個定時器 heartbeatTimeoutTimer
用于檢測心跳超時。onmessage
事件處理:當收到服務器的心跳響應消息(heartbeat_response
)時,更新最后一次收到響應的時間 lastHeartbeatResponseTime
,并重置心跳超時定時器。handleHeartbeatTimeout
方法:當心跳超時發(fā)生時,關閉當前的 WebSocket 連接,觸發(fā)重連機制。stopHeartbeat
方法:停止心跳定時器和超時定時器。
通過上述代碼,你可以在前端 WebSocket 通信中實現(xiàn)一個簡單的心跳機制,確保連接的穩(wěn)定性。同時,服務器端也需要相應地處理心跳包并返回響應消息。
分享
用ping\pong來實現(xiàn)
在 WebSocket 通信里,ping
和 pong
是標準的心跳機制實現(xiàn)方式。ping
是客戶端或服務器發(fā)送的探測消息,pong
則是對 ping
的響應。下面是一個使用 JavaScript 封裝 WebSocket 并利用 ping
/pong
實現(xiàn)心跳機制的詳細示例。
實現(xiàn)思路
- 客戶端:周期性地發(fā)送
ping
消息,同時監(jiān)聽 pong
響應,若超時未收到 pong
則認為連接異常。 - 服務器端:接收到
ping
消息后,立即返回 pong
響應。
前端代碼示例
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 連接已關閉,代碼:', 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 響應,嘗試重新連接...');
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('連接關閉:', event);
});
代碼解釋
- 構造函數:初始化 WebSocket 連接所需的參數,包括
ping
發(fā)送間隔、pong
超時時間、重連間隔等,并且調用 connect
方法開始建立連接。 connect
方法:創(chuàng)建 WebSocket 實例,為 onopen
、onmessage
、onerror
和 onclose
事件綁定處理函數。連接建立成功后,啟動 ping
發(fā)送機制。startPing
方法:使用 setInterval
周期性地發(fā)送 ping
消息,同時啟動一個 pong
超時定時器。onmessage
事件處理:當收到 pong
響應時,清除 pong
超時定時器;若收到其他消息,則調用注冊的消息處理函數。handlePongTimeout
方法:若在 pong
超時時間內未收到 pong
響應,關閉當前連接并觸發(fā)重連機制。- 其他方法:如
sendMessage
用于發(fā)送消息,onMessage
、onError
、onOpen
和 onClose
用于注冊相應的事件處理函數,close
用于關閉連接,stopPing
用于停止 ping
發(fā)送和 pong
超時檢測。
服務器端示例(使用 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 服務器已啟動,監(jiān)聽端口 8080');
服務器端代碼解釋
- 使用
ws
庫創(chuàng)建一個 WebSocket 服務器,監(jiān)聽 8080 端口。 - 當有客戶端連接時,監(jiān)聽
message
事件,若收到 ping
消息,立即返回 pong
響應;若收到其他消息,則進行相應處理。