实现一个健壮的WebSocket连接管理器
在现代Web应用中,实时通信变得越来越重要。WebSocket作为一种全双工通信协议,为前端应用提供了强大的实时数据交互能力。本文将介绍如何实现一个功能完善的WebSocket连接管理器,包含自动重连、心跳检测等关键特性。
WebSocket管理器的核心功能
实现的useWebSocket函数提供了以下核心功能:
- 自动连接:初始化时自动建立WebSocket连接
- 事件监听:通过事件机制处理消息、连接状态变化
- 心跳检测:定期发送心跳包保持连接活跃
- 自动重连:连接断开时自动尝试重新连接
- 手动控制:提供手动关闭连接的接口
实现解析
初始化与连接建立
// 生成 digits 位随机数
const randomNumber = (digits = 8) =>{
const min = Math.pow(10, digits - 1); // 10^(digits-1) 例如 8 位数最小是 10000000
const max = Math.pow(10, digits) - 1; // 10^digits - 1 例如 8 位数最大是 99999999
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export const useWebSocket = ({url = null, content, time = 10, heartbeat = 10, protocols = null}) => {
const info = {
// 存储各种状态和配置
heartbeatInterval: null,
time: time || 10, // 重连间隔(秒)
heartbeat: heartbeat || 10, // 心跳间隔(秒)
TIMEID: null, // 重连定时器ID
socket: null, // WebSocket实例
protocols: protocols || null,
url: url || null,
content: content || 'ping',
eventTarget: new EventTarget(), // 事件系统
isManuallyClosed: false, // 是否手动关闭
isConnected: false // 连接状态
}
const initWebSocket = () => {
if (info.isManuallyClosed) return;
info.socket?.close();
info.socket = null;
const protocol = location.protocol === 'https' ? 'wss' : 'ws';
// 根据实际需求可以在后面拼接随机数
const adder = info.url || import.meta.env.VITE_SOKET_URL;
info.socket = info.protocols
? new WebSocket(`${protocol}://${adder}`, info.protocols)
: new WebSocket(`${protocol}://${adder}`);
setupEventHandlers();
}
}
事件处理
const setupEventHandlers = () => {
// 连接成功
info.socket.onopen = () => {
info.eventTarget.dispatchEvent(new CustomEvent('open', {detail: "onopen"}));
info.isConnected = true;
send(info.content);
startHeartbeat();
}
// 接收消息
info.socket.onmessage = (evt) => {
info.eventTarget.dispatchEvent(new CustomEvent('message', {detail: evt.data}));
}
// 连接关闭
info.socket.onclose = (e) => {
info.isConnected = false;
info.eventTarget.dispatchEvent(new CustomEvent('close', {detail: e}));
if (!info.isManuallyClosed) attemptReconnect();
}
// 连接错误
info.socket.onerror = (e) => {
info.isConnected = false;
info.eventTarget.dispatchEvent(new CustomEvent('error', {detail: e}));
attemptReconnect();
}
}
心跳机制
// 开始心跳检测
const startHeartbeat = () => {
stopHeartbeat();
info.heartbeatInterval = setInterval(
() => send(info.content),
info.heartbeat * 1000
);
}
// 停止心跳检测
const stopHeartbeat = () => {
clearInterval(info.heartbeatInterval);
info.heartbeatInterval = null;
};
自动重连机制
// 尝试重新连接
const attemptReconnect = () => {
stopHeartbeat();
clearTimeout(info.TIMEID);
info.TIMEID = setTimeout(initWebSocket, info.time * 1000);
}
公共API
return {
// 发送消息
send: (msg) => info.socket?.send(msg),
// 监听事件
on: (event, handler) => info.eventTarget?.addEventListener(event, handler),
// 关闭连接
close: (code = 1000, msg = '正常手动关闭') => {
info.isManuallyClosed = true;
clearTimeout(info.TIMEID);
stopHeartbeat();
info.socket?.close(code, msg);
info.socket = null;
info.isConnected = false;
removeEventListeners();
}
}
使用示例
// 创建WebSocket连接
const ws = useWebSocket({
url: '192.168.3.21:9001/websoket/user',
time: 5, // 5秒重连间隔
heartbeat: 15 // 15秒心跳间隔
});
// 监听消息
ws.on('message', (event) => {// 断线重连后收到消息依旧会触发
console.log('收到消息:', event.detail);
});
// 监听连接打开
ws.on('open', () => {
console.log('连接已建立');
});
// 监听连接关闭
ws.on('close', () => {
console.log('连接已关闭');
});
// 发送消息
ws.send('Hello WebSocket!');
// 手动关闭连接
ws.close();
整体代码
// 生成 digits 位随机数
const randomNumber = (digits = 8) =>{
const min = Math.pow(10, digits - 1); // 10^(digits-1) 例如 8 位数最小是 10000000
const max = Math.pow(10, digits) - 1; // 10^digits - 1 例如 8 位数最大是 99999999
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// 使用方式
// const ws = useWebSocket({url: '192.168.3.21:9001/websoket/user'})
// ws.on('message',(event)=>{// 接受到消息
// console.log(event.detail)
// })
// ws.close()//关闭
/**
* @method useWebSocket
* @description WebSocket
* @param Object url 连接地址默认import.meta.env.VITE_SOKET_URL content 发送心跳内容 默认ping ,time 断线重连时间默认10s hbt 心跳时间默认10s
* @return WebSocket
*/
export const useWebSocket = ({url = null, content,time = 10,heartbeat = 10,protocols= null})=>{
const info = {
heartbeatInterval: null,
time: time || 10,
heartbeat: heartbeat || 10,
TIMEID: null,
socket: null,
protocols: protocols || null,
url: url || null,
content: content || 'ping',
eventTarget: new EventTarget(),
isManuallyClosed: false,
isConnected: false
}
const initWebSocket = ()=>{
if (info.isManuallyClosed){// 手动关闭时不再重连
return
}
info.socket?.close()
info.socket = null;
const protocol = location.protocol === 'https' ? 'wss' : 'ws'
const adder = info.url || import.meta.env.VITE_SOKET_URL
info.socket = info.protocols ? new WebSocket(`${protocol}://${adder}`,info.protocols) : new WebSocket(`${protocol}://${adder}`)
onopen()
onmessage()
onerror()
onclose()
}
const onopen = ()=>{
info.socket.onopen = () => {
info.eventTarget.dispatchEvent(new CustomEvent('open', { detail: "onopen" })); // 触发事件
info.isConnected = true
send(info.content)
startHeartbeat()
}
}
const send = (msg)=> {
info.socket?.send(msg)
}
const onmessage =()=> {
info.socket.onmessage = (evt) => {
const data = evt.data
info.eventTarget.dispatchEvent(new CustomEvent('message', { detail: data })); // 触发事件
}
}
const onclose = ()=>{
info.socket.onclose = (e)=>{
info.isConnected = false;
info.eventTarget?.dispatchEvent(new CustomEvent('close', { detail: e })); // 触发事件
if (!info.isManuallyClosed) {
attemptReconnect();
}
}
}
const onerror =()=> {
info.socket.onerror = (e) => {//连接失败重新连接
info.isConnected = false
info.eventTarget?.dispatchEvent(new CustomEvent('error', { detail: e })); // 触发事件
attemptReconnect();
}
}
// 发送心跳
const startHeartbeat = ()=>{
stopHeartbeat()
info.heartbeatInterval = setInterval(() => send(info.content), info.heartbeat * 1000);
}
// 清除心跳包定时器
const stopHeartbeat = () => {
clearInterval(info.heartbeatInterval)
info.heartbeatInterval = null
};
// 尝试重连
const attemptReconnect = ()=>{
clearInterval(info.heartbeatInterval)
info.heartbeatInterval = null;
clearTimeout(info.TIMEID)
info.TIMEID = null
info.TIMEID = setTimeout(initWebSocket, info.time * 1000);
}
//关闭soket
const closeSocket =(code = 1000,mesg = '正常手动关闭')=> {
info.isManuallyClosed = true;
clearTimeout(info.TIMEID)
clearInterval(info.heartbeatInterval)
info.heartbeatInterval = null;
info.TIMEID = null;
info.socket?.close(code,mesg)
info.socket = null;
info.isConnected = false
remove()
}
const remove = ()=>{
info.eventTarget = null
info.eventTarget = new EventTarget();
}
const on = (event,handler)=>{
info.eventTarget?.addEventListener(event, handler);
}
initWebSocket()
return {
send,
on,
close: closeSocket
}
}