增加WebSocketClient
This commit is contained in:
252
Web/Vue/WebSocketClient/WebSocketClient.vue
Normal file
252
Web/Vue/WebSocketClient/WebSocketClient.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<script setup>import { ref, onUnmounted, watch } from 'vue';
|
||||
|
||||
// 定义组件的props
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
default: 'ws://localhost:8805'
|
||||
},
|
||||
protocols: {
|
||||
type: Array,
|
||||
default: () => ['device-web']
|
||||
},
|
||||
autoConnect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maxReconnectAttempts: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
initialReconnectDelay: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
maxReconnectDelay: {
|
||||
type: Number,
|
||||
default: 30000
|
||||
}
|
||||
});
|
||||
|
||||
// 定义组件的emits
|
||||
const emit = defineEmits([
|
||||
'connect', // 连接成功时触发
|
||||
'disconnect', // 断开连接时触发
|
||||
'message', // 收到消息时触发
|
||||
'error', // 发生错误时触发
|
||||
'status-change' // 连接状态改变时触发
|
||||
]);
|
||||
|
||||
// 内部状态管理
|
||||
const ws = ref(null);
|
||||
const isConnected = ref(false);
|
||||
const reconnectAttempts = ref(0);
|
||||
const reconnectDelay = ref(props.initialReconnectDelay);
|
||||
const reconnectTimer = ref(null);
|
||||
const manualDisconnect = ref(false);
|
||||
const currentUrl = ref(props.url);
|
||||
|
||||
// 调试日志函数
|
||||
const debugLog = (...args) => {
|
||||
if (window.DEBUG) {
|
||||
console.log('[WebSocketClient]', ...args);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新连接状态并发出状态改变事件
|
||||
const updateConnectionStatus = (connected) => {
|
||||
if (isConnected.value !== connected) {
|
||||
isConnected.value = connected;
|
||||
emit('status-change', connected);
|
||||
if (connected) {
|
||||
// 连接成功时发出连接事件
|
||||
emit('connect', currentUrl.value);
|
||||
// 重置重连状态
|
||||
reconnectAttempts.value = 0;
|
||||
reconnectDelay.value = props.initialReconnectDelay;
|
||||
} else {
|
||||
ws.value = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 取消当前的重连尝试
|
||||
const cancelReconnect = () => {
|
||||
if (reconnectTimer.value) {
|
||||
clearTimeout(reconnectTimer.value);
|
||||
reconnectTimer.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 智能重连策略
|
||||
const reconnect = () => {
|
||||
// 如果是手动断开,不进行重连
|
||||
if (manualDisconnect.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 达到最大重连次数且最大重连次数不为0时,停止重连
|
||||
// 当maxReconnectAttempts为0时表示无限重试
|
||||
if (props.maxReconnectAttempts > 0 && reconnectAttempts.value >= props.maxReconnectAttempts) {
|
||||
debugLog('已达到最大重连次数');
|
||||
emit('error', '已达到最大重连次数,请手动尝试重新连接');
|
||||
return;
|
||||
}
|
||||
|
||||
// 指数退避重连延迟
|
||||
const delay = Math.min(reconnectDelay.value * Math.pow(2, reconnectAttempts.value), props.maxReconnectDelay);
|
||||
|
||||
// 调整日志显示:当maxReconnectAttempts为0时显示"无限重试"模式
|
||||
if (props.maxReconnectAttempts === 0) {
|
||||
debugLog(`无限重试模式:将在${Math.round(delay/1000)}秒后尝试第${reconnectAttempts.value + 1}次重连`);
|
||||
} else {
|
||||
debugLog(`将在${Math.round(delay/1000)}秒后尝试第${reconnectAttempts.value + 1}/${props.maxReconnectAttempts}次重连`);
|
||||
}
|
||||
|
||||
reconnectTimer.value = setTimeout(() => {
|
||||
reconnectAttempts.value++;
|
||||
connect();
|
||||
}, delay);
|
||||
};
|
||||
|
||||
// 连接WebSocket服务器
|
||||
const connect = (url = null) => {
|
||||
// 如果提供了新的URL,则更新当前URL
|
||||
if (url) {
|
||||
currentUrl.value = url;
|
||||
}
|
||||
|
||||
manualDisconnect.value = false;
|
||||
cancelReconnect();
|
||||
|
||||
try {
|
||||
debugLog(`尝试连接到: ${currentUrl.value}`);
|
||||
|
||||
ws.value = new WebSocket(currentUrl.value, props.protocols);
|
||||
|
||||
// 连接成功
|
||||
ws.value.onopen = () => {
|
||||
debugLog('连接成功');
|
||||
updateConnectionStatus(true);
|
||||
};
|
||||
|
||||
// 接收消息
|
||||
ws.value.onmessage = (event) => {
|
||||
debugLog('收到消息:', event.data);
|
||||
emit('message', event.data);
|
||||
};
|
||||
|
||||
// 连接关闭
|
||||
ws.value.onclose = (event) => {
|
||||
debugLog(`连接关闭: 代码=${event.code}, 原因=${event.reason}`);
|
||||
updateConnectionStatus(false);
|
||||
emit('disconnect', event.code, event.reason);
|
||||
|
||||
// 异常断开时自动重连
|
||||
if (event.code === 1006) {
|
||||
reconnect();
|
||||
} else if (!manualDisconnect.value) {
|
||||
// 非手动断开且非1006错误时也尝试重连
|
||||
reconnect();
|
||||
}
|
||||
};
|
||||
|
||||
// 连接错误
|
||||
ws.value.onerror = (error) => {
|
||||
debugLog('WebSocket错误:', error);
|
||||
emit('error', '连接错误,请检查服务器是否运行');
|
||||
updateConnectionStatus(false);
|
||||
// 错误时也尝试重连
|
||||
if (!manualDisconnect.value) {
|
||||
reconnect();
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
debugLog('连接失败:', error);
|
||||
emit('error', '连接失败,请重试');
|
||||
// 连接失败也尝试重连
|
||||
if (!manualDisconnect.value) {
|
||||
reconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 断开WebSocket连接
|
||||
const disconnect = () => {
|
||||
if (ws.value) {
|
||||
manualDisconnect.value = true;
|
||||
cancelReconnect();
|
||||
ws.value.close();
|
||||
updateConnectionStatus(false);
|
||||
debugLog('已断开连接');
|
||||
}
|
||||
};
|
||||
|
||||
// 发送消息
|
||||
const send = (message) => {
|
||||
if (!message || !ws.value || !isConnected.value) {
|
||||
debugLog('发送消息失败: 连接未建立或消息为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
ws.value.send(message);
|
||||
debugLog('消息发送成功:', message);
|
||||
return true;
|
||||
} catch (error) {
|
||||
debugLog('消息发送失败:', error);
|
||||
emit('error', '消息发送失败');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 切换连接状态
|
||||
const toggleConnection = () => {
|
||||
if (isConnected.value) {
|
||||
disconnect();
|
||||
} else {
|
||||
connect();
|
||||
}
|
||||
};
|
||||
|
||||
// 监听props.url的变化
|
||||
watch(() => props.url, (newUrl) => {
|
||||
if (newUrl && newUrl !== currentUrl.value) {
|
||||
// 如果当前已连接,则先断开连接
|
||||
if (isConnected.value) {
|
||||
disconnect();
|
||||
// 延迟重新连接,确保旧连接完全关闭
|
||||
setTimeout(() => {
|
||||
connect(newUrl);
|
||||
}, 500);
|
||||
} else {
|
||||
// 如果当前未连接,直接更新URL
|
||||
currentUrl.value = newUrl;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 组件挂载时,如果设置了autoConnect为true,则自动连接
|
||||
if (props.autoConnect) {
|
||||
connect();
|
||||
}
|
||||
|
||||
// 组件卸载时清理资源
|
||||
onUnmounted(() => {
|
||||
disconnect();
|
||||
});
|
||||
|
||||
// 暴露方法给父组件使用
|
||||
defineExpose({
|
||||
connect,
|
||||
disconnect,
|
||||
send,
|
||||
toggleConnection,
|
||||
isConnected,
|
||||
currentUrl
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 这个组件没有UI界面元素 -->
|
||||
</template>
|
||||
Reference in New Issue
Block a user