一、基于 React 的 WebSocket
下面是一个React版本的WebSocket连接代码示例,展示了如何在React组件中实现WebSocket连接、心跳机制以及自动重连功能。
WebSocketManager.js
首先,我们可以创建一个 WebSocketManager
类来封装WebSocket的逻辑,包括连接、心跳和重连。
javascript
// WebSocketManager.js
class WebSocketManager {
constructor(url) {
this.url = url;
this.ws = null;
// Ping和Pong是websocket里的心跳,用来保证客户端是在线的,一般来说只有服务端给客户端发送Ping,然后客户端发送Pong来回应,表明自己仍然在线
this.pingInterval = null;
this.pongTimeout = null;
this.reconnectDelay = 5000; // 5秒后重新连接
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected');
this.startHeartbeat();
};
this.ws.onclose = (event) => {
console.log(`Connection closed: ${event.reason}`);
clearInterval(this.pingInterval);
clearTimeout(this.pongTimeout);
setTimeout(() => this.connect(), this.reconnectDelay);
};
this.ws.onerror = (error) => {
console.error(`WebSocket error: ${error.message}`);
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'pong') {
console.log('Received pong');
clearTimeout(this.pongTimeout);
} else {
this.handleMessage(message);
}
};
}
startHeartbeat() {
this.pingInterval = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'ping' }));
this.pongTimeout = setTimeout(() => {
console.warn('Pong timeout, closing connection');
this.ws.close();
}, 5000);
}
}, 30000);
}
handleMessage(message) {
// 处理接收到的其他消息
console.log('Received message:', message);
}
send(data) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
}
export default WebSocketManager;
App.js
然后,在React组件中使用这个 WebSocketManager
。
javascript
// App.js
import React, { useEffect } from 'react';
import WebSocketManager from './WebSocketManager';
const App = () => {
useEffect(() => {
const wsManager = new WebSocketManager('ws://example.com/socket');
wsManager.connect();
// 清理函数,组件卸载时关闭 WebSocket 连接
return () => {
if (wsManager.ws) {
clearInterval(wsManager.pingInterval);
clearTimeout(wsManager.pongTimeout);
wsManager.ws.close();
}
};
}, []);
return (
<div className="App">
<h1>WebSocket Demo</h1>
{/* 其他组件或内容 */}
</div>
);
};
export default App;
解释
-
WebSocketManager 类
- 封装 WebSocket 连接的逻辑,包含连接、心跳和重连功能。
connect
方法用于建立 WebSocket 连接并设置相应的事件处理器。startHeartbeat
方法实现心跳机制,每隔30秒发送一次 ping 消息,如果5秒内没有收到 pong 响应,则关闭连接并触发重连。handleMessage
方法处理接收到的其他消息,可以根据需要进行扩展。send
方法用于发送消息。
-
App 组件
- 使用
useEffect
在组件挂载时创建WebSocketManager
实例并建立连接,在组件卸载时清理定时器和关闭连接。
- 使用
通过这种方式,可以在 React 项目中实现稳定的 WebSocket 连接,并具备自动重连和心跳检测功能。
注意:避免无限制重连
上面的示例中,客户端会一直尝试重连。每当连接关闭时,都会在5秒后重新尝试连接。这样可以确保在网络暂时中断或服务器暂时不可用的情况下,客户端能够自动恢复连接。
如果希望更精细地控制重连行为,例如限制重连次数或逐步增加重连间隔,可以进行以下改进:
-
限制重连次数
- 可以增加一个变量来记录重连次数,并在超过最大重连次数后停止重连。
-
逐步增加重连间隔
- 可以使用指数退避(exponential backoff)算法来逐步增加重连间隔,以避免频繁重连。
下面是改进后的 WebSocketManager
类:
javascript
// WebSocketManager.js
class WebSocketManager {
constructor(url) {
this.url = url;
this.ws = null;
this.pingInterval = null;
this.pongTimeout = null;
this.reconnectDelay = 5000; // 初始重连延迟
this.maxReconnectDelay = 30000; // 最大重连延迟
this.reconnectAttempts = 0; // 重连次数
this.maxReconnectAttempts = 10; // 最大重连次数
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected');
this.reconnectAttempts = 0; // 重置重连次数
this.reconnectDelay = 5000; // 重置重连延迟
this.startHeartbeat();
};
this.ws.onclose = (event) => {
console.log(`Connection closed: ${event.reason}`);
clearInterval(this.pingInterval);
clearTimeout(this.pongTimeout);
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
setTimeout(() => this.connect(), this.reconnectDelay);
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay); // 指数退避
} else {
console.log('Max reconnect attempts reached. Giving up.');
}
};
this.ws.onerror = (error) => {
console.error(`WebSocket error: ${error.message}`);
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'pong') {
console.log('Received pong');
clearTimeout(this.pongTimeout);
} else {
this.handleMessage(message);
}
};
}
startHeartbeat() {
this.pingInterval = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'ping' }));
this.pongTimeout = setTimeout(() => {
console.warn('Pong timeout, closing connection');
this.ws.close();
}, 5000);
}
}, 30000);
}
handleMessage(message) {
// 处理接收到的其他消息
console.log('Received message:', message);
}
send(data) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
}
export default WebSocketManager;
解释:
-
限制重连次数
- 增加了
reconnectAttempts
和maxReconnectAttempts
变量,用于记录和限制重连次数。 - 在
onclose
事件中,检查reconnectAttempts
是否小于maxReconnectAttempts
,如果是则继续重连,否则停止重连。
- 增加了
-
逐步增加重连间隔
- 使用
reconnectDelay
和maxReconnectDelay
变量控制重连延迟。 - 在每次重连后,使用
Math.min(this.reconnectDelay * 2, this.maxReconnectDelay)
增加重连延迟,直到达到最大重连延迟。
- 使用
通过这种改进,可以更好地控制重连行为,避免客户端无限制地尝试重连,并在重连失败后逐步增加重连间隔。
当然,这个时间间隔应该最好可以比「服务端重启 Websocket」来的久,这样就可以在服务端出现故障重启时正常重连。
二、为什么会 ping 不通
当客户端一直无法 ping 通服务器时,可能会发生以下几种情况:
-
连接断开
- 如果客户端无法接收到服务器的 pong 响应,可能意味着连接已经断开或存在网络问题。在这种情况下,客户端需要采取措施进行重连。
-
服务器无响应
- 如果服务器因故未能回复 pong 消息,客户端会认为连接已失效。同样需要尝试重连。
-
连接超时
- 为了防止无限等待,客户端可以设置一个超时时间。如果在指定时间内没有收到 pong 响应,就认为连接失效并进行重连。
三、保证持续正常连接的最佳实践
保证 WebSocket 持续正常连接可以通过以下几个步骤和最佳实践:
2.1 定时发送心跳消息 (Heartbeat/Ping-Pong)
定时发送心跳消息来保持连接活跃。服务器和客户端可以定期发送 Ping 和 Pong 消息,以确保连接未被中断。
javascript
const ws = new WebSocket('ws://example.com/socket');
const interval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000); // 每30秒发送一次心跳消息
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'pong') {
// 处理心跳回复
}
};
2.2 自动重连机制
当连接断开时,自动尝试重新连接。
javascript
let ws;
function connect() {
ws = new WebSocket('ws://example.com/socket');
ws.onopen = () => {
console.log('Connected');
};
ws.onclose = (event) => {
console.log(`Connection closed: ${event.reason}`);
setTimeout(connect, 5000); // 5秒后重新连接
};
ws.onerror = (error) => {
console.error(`WebSocket error: ${error.message}`);
};
ws.onmessage = (event) => {
// 处理接收到的消息
};
}
connect();
2.3 检测网络状态
通过监听网络状态变化来处理网络断开和恢复。
javascript
window.addEventListener('online', () => {
console.log('Network is online. Attempting to reconnect...');
connect();
});
window.addEventListener('offline', () => {
console.log('Network is offline. Closing WebSocket connection...');
ws.close();
});
2.4 处理服务器端断开
服务器端可以通过发送特定消息通知客户端即将断开,客户端收到后可进行相应处理。
javascript
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'server-close') {
console.log('Server is closing the connection');
ws.close();
}
};
2.5 增加异常处理
在连接的生命周期内,添加对各种异常情况的处理,以确保程序不会因为未处理的异常而崩溃。
javascript
ws.onerror = (error) => {
console.error(`WebSocket error: ${error.message}`);
// 可以根据错误类型进行不同的处理,例如重连、提示用户等
};
通过上述方法,可以提高 WebSocket 连接的稳定性和持续性,确保在网络状况良好时,连接可以保持正常。如果遇到网络波动或其他异常情况,也能及时进行恢复和重连。