使用原生websocket,在websocket中加入了心跳机制,断网重连等功能
ps:最开始使用的是uni的uni.connectSocket方法,然而在实际使用过程中,发现一旦断网或者服务器断开一会儿,做不到每隔几秒钟重连一次,他是第一次重连发送一个请求,第一百次就会发送100个请求,后来通过清除定时器的方式实现了,但这就导致第100次重连需要清除99个定时器,越来越多,不太放心,后来没办法,改用原生的websocket重新实现,没有出现之前的问题。有知道为啥的小伙伴欢迎指正。下面分别贴上原生的和uni.connectSocket两种方式的代码,以及服务端代码。
1.原生websocket:
class WebSocketClient {
constructor(url, reconnectInterval = 5000, heartbeatInterval = 10000, maxReconnectAttempts = 10, {
onOpen = () => {},
onMessage = () => {},
onClose = () => {},
onError = () => {}
} = {}) {
this.url = url;
this.reconnectInterval = reconnectInterval;
this.heartbeatInterval = heartbeatInterval;
this.reconnectTimer = null;
this.heartbeatTimer = null;
this.pongTimeout = null;
this.onOpen = onOpen;
this.onMessage = onMessage;
this.onClose = onClose;
this.onError = onError;
this.maxReconnectAttempts = maxReconnectAttempts;
this.reconnectAttempts = 0;
this.connect();
}
connect() {
this.socket = new WebSocket(this.url);
this.socket.addEventListener('open', (event) => {
console.log('已连接到服务器');
this.reconnectAttempts = 0;
if (this.reconnectTimer) {
clearInterval(this.reconnectTimer);
this.reconnectTimer = null;
}
this.startHeartbeat();
this.onOpen(event);
});
this.socket.addEventListener('message', (event) => {
if (event.data === 'PONG') {
clearTimeout(this.pongTimeout);
}
this.onMessage(event);
});
this.socket.addEventListener('close', (event) => {
this.stopHeartbeat();
console.log('与服务器的连接已关闭,尝试重新连接...');
console.log('this.reconnectAttempts:', this.reconnectAttempts);
if (this.reconnectAttempts < this.maxReconnectAttempts) {
if (!this.reconnectTimer) {
this.reconnectTimer = setInterval(() => {
this.reconnectAttempts++;
this.connect();
}, this.reconnectInterval);
}
} else {
// 清除重连定时器
if (this.reconnectTimer) {
clearInterval(this.reconnectTimer);
this.reconnectTimer = null;
}
console.log('达到最大重连次数,停止重连');
}
this.onClose(event);
});
this.socket.addEventListener('error', (event) => {
console.error('WebSocket 连接出错:', event);
this.stopHeartbeat();
if (this.reconnectAttempts < this.maxReconnectAttempts) {
if (!this.reconnectTimer) {
this.reconnectTimer = setInterval(() => {
this.reconnectAttempts++;
this.connect();
}, this.reconnectInterval);
}
} else {
// 清除重连定时器
if (this.reconnectTimer) {
clearInterval(this.reconnectTimer);
this.reconnectTimer = null;
}
console.log('达到最大重连次数,停止重连');
}
this.onError(event);
});
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.socket.readyState === WebSocket.OPEN) {
this.socket.send('PING');
this.pongTimeout = setTimeout(() => {
console.log('未收到 pong 响应,断开连接');
this.socket.close();
}, 5000);
}
}, this.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
if (this.pongTimeout) {
clearTimeout(this.pongTimeout);
this.pongTimeout = null;
}
}
sendMessage() {
const input = document.getElementById('input');
const message = input.value;
if (message) {
this.socket.send(message);
input.value = '';
}
}
}
export default WebSocketClient;
2.uni.connectSocket
interface Options {
url: string,
header?: {},
reconnectAttempts?: number,
maxReconnectAttempts?: number,
interval?:number,
success: (res: any) => void,
fail: (res: any) => void,
onOpen: (res: any) => void,
onMessage: (res: any) => void,
onError: (res: any) => void,
onClose: (res: any) => void,
}
export class WebSocketService {
private ws: UniApp.SocketTask | null = null
private isClosed = false
private reconnectAttempts = 0
private maxReconnectAttempts = 100
private readonly reconnectInterval = 1000
private heartbeatInterval: number | null = null
private heartbeatTimeout: number | null = null
private interval = 0
private options?: Options
private readonly heartbeatTimeoutDuration = 5000; // 心跳超时时间,单位毫秒
private setTimer: number | null = null
createConnect(options: Options) {
if(this.ws) {
console.log('ws 已经存在')
this.ws.close({})
this.ws = null
}
this.reconnectAttempts = options.reconnectAttempts || 0
this.maxReconnectAttempts = options.maxReconnectAttempts || 100
this.interval = options.interval || 10000
this.options = {
url: options.url,
header: options.header || {},
success: (res: any) => {
options.success(res)
},
fail: (res: any) => {
options.fail(res)
},
onOpen: (res: any) => {
options.onOpen(res)
this.reconnectAttempts = 0
this.startHeartbeat()
},
onMessage: (res: any) => {
this.handleHeartbeatResponse(res)
options.onMessage(res)
},
onError: (res: any) => {
options.onError(res)
},
onClose: (res: any) => {
options.onClose(res)
if(!this.isClosed) {
this.reconnect()
}
}
}
const that = this
if(this.options) {
this.ws = uni.connectSocket({
url: this.options.url,
header: this.options.header,
success: (res: any) => {
this.options!.success(res)
},
fail: (res: any) => {
this.options!.fail(res)
},
})
this.ws!.onOpen((res: any) => {
that.options!.onOpen(res)
})
this.ws!.onMessage((res: any) => {
that.options!.onMessage(res)
})
this.ws!.onClose((res: any) => {
that.options!.onClose(res)
})
this.ws!.onError((res: any) => {
that.options!.onError(res)
})
}
return this.ws
}
private startHeartbeat() {
// this.heartbeatInterval = setInterval(() => {
// if(this.ws?.readyState === WebSocket.OPEN) {
// this.ws!.send({
// data: 'PING'
// });
// this.startHeartbeatTimeout();
// } else {
// clearInterval(this.heartbeatInterval!);
// }
// }, this.interval)
}
private startHeartbeatTimeout() {
this.heartbeatTimeout = setTimeout(() => {
console.log('心跳超时,尝试重新连接');
this.ws?.close({});
this.reconnect();
}, this.heartbeatTimeoutDuration);
}
private handleHeartbeatResponse(res: any) {
if (res.data === 'PONG') {
if (this.heartbeatTimeout) {
clearTimeout(this.heartbeatTimeout);
}
}
}
private reconnect() {
if (this.isClosed) {
return
}
// if(this.setTimer) {
// console.log('清除定时器')
// clearTimeout(this.setTimer)
// }
if(this.reconnectAttempts < this.maxReconnectAttempts) {
console.log('重连次数',this.reconnectAttempts)
if(!this.setTimer){
this.setTimer = setTimeout(() => {
this.createConnect({...this.options,reconnectAttempts:this.reconnectAttempts} as Options)
this.reconnectAttempts ++
clearTimeout(this.setTimer)
}, this.reconnectInterval)
}
} else {
this.isClosed = true
}
}
close() {
this.isClosed = true
this.heartbeatInterval && clearInterval(this.heartbeatInterval)
if (this.heartbeatTimeout) {
clearTimeout(this.heartbeatTimeout);
}
this.ws?.close({
success: () => {
console.log('WebSocket disconnected successfully')
},
fail: () => {
console.log('WebSocket disconnected fail')
}
})
this.ws = null
}
}
export default new WebSocketService()
3.自己调试用的服务端代码
const WebSocket = require('ws');
// 创建 WebSocket 服务器,监听 8080 端口
const wss = new WebSocket.Server({ port: 8080 });
// 存储所有连接的客户端
const clients = new Set();
// 当有新的客户端连接时触发
wss.on('connection', (ws) => {
// 将新连接的客户端添加到集合中
clients.add(ws);
ws.send('连接成功');
// 当接收到客户端发送的消息时触发
ws.on('message', (message) => {
// 将 Buffer 对象转换为字符串
const messageStr = message.toString();
console.log('Received message:', message instanceof Buffer);
if (messageStr === 'PING') {
// 收到客户端的 ping 消息,回复 pong
ws.send('PONG');
return;
}
// 遍历所有客户端
clients.forEach((client) => {
// 确保消息不会发送回发送者
if (client!== ws && client.readyState === WebSocket.OPEN) {
// 向客户端发送消息
client.send(messageStr);
}
});
});
// 当客户端断开连接时触发
ws.on('close', () => {
// 从集合中移除断开连接的客户端
clients.delete(ws);
});
// 错误处理
ws.on('error', (error) => {
console.error('WebSocket error:', error);
clients.delete(ws);
});
});
console.log('WebSocket server is running on port 8080');