From 54f32eec5f4260501c75fa23459ecbcc82e17cb4 Mon Sep 17 00:00:00 2001 From: Dawn <2439646234@qq.com> Date: Mon, 2 Jun 2025 03:01:29 +0800 Subject: [PATCH] =?UTF-8?q?perf(component):=20:zap:=20=E4=BC=98=E5=8C=96we?= =?UTF-8?q?bSocket=E7=9A=84=E9=87=8D=E8=BF=9E=E6=80=A7=E8=83=BD=E5=92=8C?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E6=A3=80=E6=B5=8B=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/desktops/mod.rs | 1 - src-tauri/src/desktops/network_monitor.rs | 216 ------------------- src-tauri/src/lib.rs | 6 - src/components/rightBox/chatBox/ChatMain.vue | 18 +- src/hooks/useNetworkStatus.ts | 99 ++------- src/services/webSocket.ts | 116 ++++++++-- src/workers/timer.worker.ts | 26 ++- src/workers/webSocket.worker.ts | 46 ++-- 8 files changed, 163 insertions(+), 365 deletions(-) delete mode 100644 src-tauri/src/desktops/network_monitor.rs diff --git a/src-tauri/src/desktops/mod.rs b/src-tauri/src/desktops/mod.rs index e476df5a..88d11e06 100644 --- a/src-tauri/src/desktops/mod.rs +++ b/src-tauri/src/desktops/mod.rs @@ -1,4 +1,3 @@ pub mod common_cmd; pub mod init; -pub mod network_monitor; pub mod tray; diff --git a/src-tauri/src/desktops/network_monitor.rs b/src-tauri/src/desktops/network_monitor.rs deleted file mode 100644 index 9c0e01a5..00000000 --- a/src-tauri/src/desktops/network_monitor.rs +++ /dev/null @@ -1,216 +0,0 @@ -use serde::Serialize; -use std::net::IpAddr; -use std::sync::{Arc, Mutex}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use surge_ping::{Client, Config, PingIdentifier, PingSequence}; -use tauri::{AppHandle, Emitter, Manager, Runtime}; -use tokio::net::lookup_host; - -// 网络状态枚举 -#[derive(Debug, Clone, Copy, Serialize, PartialEq)] -pub enum NetworkStrength { - Strong, // 强网络 - Weak, // 弱网络 - Offline, // 离线 -} - -// 网络状态结构体 -#[derive(Debug, Clone, Serialize)] -pub struct NetworkStatus { - pub is_online: bool, - pub strength: NetworkStrength, - pub latency_ms: u64, // 延迟时间(毫秒) - pub last_check: u64, // 上次检查时间戳 -} - -impl Default for NetworkStatus { - fn default() -> Self { - NetworkStatus { - is_online: true, // 默认假设在线 - strength: NetworkStrength::Strong, - latency_ms: 0, - last_check: 0, - } - } -} - -// 全局网络状态 -lazy_static::lazy_static! { - static ref NETWORK_STATUS: Arc> = Arc::new(Mutex::new(NetworkStatus::default())); -} - -// 将域名解析为IP地址 -async fn resolve_host(host: &str) -> Option { - // 删除可能的协议前缀 - let host = host.replace("https://", "").replace("http://", ""); - // 删除可能的路径 - let host = host.split('/').next().unwrap_or(&host); - - // 使用标准端口进行DNS解析 - match lookup_host(format!("{host}:80")).await { - Ok(mut addrs) => addrs.next().map(|addr| addr.ip()), - Err(_) => None, - } -} - -// 对IP地址进行ping测试 -async fn ping_ip(ip: IpAddr, count: u8, timeout: Duration) -> Option { - // 注意:Config::builder()没有timeout方法,我们在pinger中设置timeout - let config = Config::default(); - let client = match Client::new(&config) { - Ok(client) => client, - Err(_) => return None, - }; - - let mut total_rtt = 0u64; - let mut success_count = 0u8; - - for i in 0..count { - let payload = vec![0; 56]; // 标准ping数据包大小 - let ping_id = PingIdentifier(rand::random()); - // 将u8转换为u16 - let seq_num = PingSequence(i as u16); - - // 创建pinger(这是一个Future,需要await) - // pinger直接返回Pinger对象,不是Result - let mut pinger = client.pinger(ip, ping_id).await; - // 设置超时(timeout方法返回&mut Pinger,已经修改了原来的pinger) - pinger.timeout(timeout); - - // 执行ping - match pinger.ping(seq_num, &payload).await { - Ok((_, rtt)) => { - total_rtt += rtt.as_millis() as u64; - success_count += 1; - } - Err(_) => continue, - } - } - - if success_count > 0 { - Some(total_rtt / success_count as u64) - } else { - None - } -} - -// 异步检查网络状态 -async fn check_network_status() -> NetworkStatus { - // 测试多个网站以提高准确性 - let test_hosts = vec!["www.baidu.com", "www.qq.com", "www.aliyun.com"]; - - // 200ms的超时时间用于判断弱网 - let weak_threshold = 200; - let ping_timeout = Duration::from_secs(1); // 单次ping超时时间 - let ping_count = 5; // 每个网站ping 5次 - - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); - - // 并发测试所有网站 - let mut futures = Vec::new(); - for host in test_hosts { - let host = host.to_string(); - futures.push(tokio::spawn(async move { - // 先解析域名 - if let Some(ip) = resolve_host(&host).await { - // 如果解析成功,进行ping测试 - ping_ip(ip, ping_count, ping_timeout).await - } else { - None - } - })); - } - - // 收集测试结果 - let mut successful_tests = 0; - let mut total_latency = 0u64; - - for future in futures { - if let Ok(result) = future.await { - if let Some(latency) = result { - successful_tests += 1; - total_latency += latency; - } - } - } - - // 根据测试结果判断网络状态 - if successful_tests == 0 { - // 所有测试都失败,认为离线 - NetworkStatus { - is_online: false, - strength: NetworkStrength::Offline, - latency_ms: 0, - last_check: now, - } - } else { - // 至少有一个测试成功,计算平均延迟 - let avg_latency = total_latency / successful_tests as u64; - let strength = if avg_latency > weak_threshold { - NetworkStrength::Weak - } else { - NetworkStrength::Strong - }; - - NetworkStatus { - is_online: true, - strength, - latency_ms: avg_latency, - last_check: now, - } - } -} - -// 更新网络状态并通知前端 -async fn update_network_status(app_handle: AppHandle) { - let status = check_network_status().await; - - // 获取旧状态进行比较 - let mut global_status = NETWORK_STATUS.lock().unwrap(); - let old_status = global_status.clone(); - - // 更新全局状态 - *global_status = status.clone(); - - // 如果状态有变化,通知前端 - if old_status.is_online != status.is_online || old_status.strength != status.strength { - // 沿用记忆中的错误处理方式,不抛出错误,而是尝试发送并忽略可能的错误 - if let Some(window) = app_handle.get_webview_window("home") { - let _ = window.emit("network-status-changed", &status); - } - } -} - -// 启动网络监控服务 -pub fn start_network_monitor(app_handle: AppHandle) { - let app_handle_clone = app_handle.clone(); - - // 立即进行一次检查 - tauri::async_runtime::spawn(async move { - update_network_status(app_handle).await; - }); - - // 创建后台任务定期检查 - tauri::async_runtime::spawn(async move { - loop { - tokio::time::sleep(Duration::from_secs(5)).await; // 每5秒检查一次 - update_network_status(app_handle_clone.clone()).await; - } - }); -} - -// 获取当前网络状态 -#[tauri::command] -pub fn get_network_status() -> NetworkStatus { - NETWORK_STATUS.lock().unwrap().clone() -} - -// 手动触发网络状态检查 -#[tauri::command] -pub async fn check_network(app_handle: AppHandle) -> NetworkStatus { - update_network_status(app_handle.clone()).await; - NETWORK_STATUS.lock().unwrap().clone() -} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6e351ed1..08398925 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -12,8 +12,6 @@ use desktops::common_cmd; #[cfg(desktop)] use desktops::init; #[cfg(desktop)] -use desktops::network_monitor::{check_network, get_network_status, start_network_monitor}; -#[cfg(desktop)] use desktops::tray; #[cfg(desktop)] use init::CustomInit; @@ -49,8 +47,6 @@ fn setup_desktop() { .init_window_event() .setup(move |app| { tray::create_tray(app.handle())?; - // 启动网络监测服务 - start_network_monitor(app.handle().clone()); Ok(()) }) .invoke_handler(tauri::generate_handler![ @@ -61,8 +57,6 @@ fn setup_desktop() { set_badge_count, test_api_proxy, test_ws_proxy, - get_network_status, - check_network, #[cfg(target_os = "macos")] hide_title_bar_buttons ]) diff --git a/src/components/rightBox/chatBox/ChatMain.vue b/src/components/rightBox/chatBox/ChatMain.vue index af87ebb0..dee9d881 100644 --- a/src/components/rightBox/chatBox/ChatMain.vue +++ b/src/components/rightBox/chatBox/ChatMain.vue @@ -11,18 +11,6 @@ 当前网络不可用,请检查你的网络设置 - - - - - - 当前网络信号较弱,可能影响聊天体验 (延迟: {{ networkStatus.latencyMs.value }}ms) - -
type() === 'macos') // 公告展示时需要减去的高度 const announcementHeight = computed(() => (isGroup.value && topAnnouncement.value ? 300 : 260)) diff --git a/src/hooks/useNetworkStatus.ts b/src/hooks/useNetworkStatus.ts index 1bf685ef..70fdbbe9 100644 --- a/src/hooks/useNetworkStatus.ts +++ b/src/hooks/useNetworkStatus.ts @@ -1,95 +1,42 @@ -import { ref, onMounted, onUnmounted } from 'vue' -import { listen } from '@tauri-apps/api/event' -import { invoke } from '@tauri-apps/api/core' - -// 网络状态类型 -export enum NetworkStrength { - Strong = 'Strong', - Weak = 'Weak', - Offline = 'Offline' -} - -// 网络状态接口 -export interface NetworkStatus { - is_online: boolean - strength: NetworkStrength - latency_ms: number - last_check: number -} - /** * 网络状态监测钩子 - * 提供网络是否在线、网络强度、延迟等信息 */ -export function useNetworkStatus() { - // 网络状态 - const isOnline = ref(true) - // 网络强度 - const networkStrength = ref(NetworkStrength.Strong) - // 网络延迟(毫秒) - const latencyMs = ref(0) - // 上次检查时间 - const lastCheck = ref(0) - // 网络是否为弱网 - const isWeakNetwork = ref(false) +export const useNetworkStatus = () => { + // 网络状态 - 基于浏览器navigator.onLine + const isOnline = ref(navigator.onLine) - // 存储监听器清理函数 - let unlisten: (() => void) | null = null - - // 初始化网络状态 - const initNetworkStatus = async () => { - try { - const status = await invoke('get_network_status') - updateNetworkStatus(status) - } catch (error) { - console.error('获取网络状态失败:', error) - } + // 监听浏览器网络状态变化 + const handleOnline = () => { + isOnline.value = true } - // 手动检查网络状态 - const checkNetwork = async () => { - try { - const status = await invoke('check_network') - updateNetworkStatus(status) - return status - } catch (error) { - console.error('检查网络状态失败:', error) - return null - } + const handleOffline = () => { + isOnline.value = false } - // 更新网络状态 - const updateNetworkStatus = (status: NetworkStatus) => { - isOnline.value = status.is_online - networkStrength.value = status.strength - latencyMs.value = status.latency_ms - lastCheck.value = status.last_check - isWeakNetwork.value = status.strength === NetworkStrength.Weak + // 初始化网络状态监听 + const initNetworkListener = () => { + window.addEventListener('online', handleOnline) + window.addEventListener('offline', handleOffline) } - onMounted(async () => { - // 初始化网络状态 - await initNetworkStatus() + // 清理网络状态监听 + const cleanupNetworkListener = () => { + window.removeEventListener('online', handleOnline) + window.removeEventListener('offline', handleOffline) + } - // 监听网络状态变化事件 - unlisten = await listen('network-status-changed', (event) => { - updateNetworkStatus(event.payload) - }) - }) + // 自动初始化监听器 + initNetworkListener() + // 在组件卸载时清理监听器 onUnmounted(() => { - // 清理监听器 - if (unlisten) { - unlisten() - } + cleanupNetworkListener() }) return { isOnline, - networkStrength, - latencyMs, - lastCheck, - isWeakNetwork, - checkNetwork + initNetworkListener, + cleanupNetworkListener } } diff --git a/src/services/webSocket.ts b/src/services/webSocket.ts index a8a6fe2c..5d6aad35 100644 --- a/src/services/webSocket.ts +++ b/src/services/webSocket.ts @@ -31,14 +31,48 @@ const timerWorker: Worker = new Worker(new URL('../workers/timer.worker.ts', imp // 添加一个标识是否是主窗口的变量 let isMainWindow = false +// LRU缓存实现 +class LRUCache { + private maxSize: number + private cache = new Map() + + constructor(maxSize: number = 1000) { + this.maxSize = maxSize + } + + set(key: K, value: V) { + if (this.cache.has(key)) { + this.cache.delete(key) + } else if (this.cache.size >= this.maxSize) { + const firstKey = this.cache.keys().next().value + if (firstKey) { + this.cache.delete(firstKey) + } + } + this.cache.set(key, value) + } + + has(key: K): boolean { + return this.cache.has(key) + } + + clear() { + this.cache.clear() + } + + get size(): number { + return this.cache.size + } +} + class WS { // 添加消息队列大小限制 - readonly #MAX_QUEUE_SIZE = 100 + readonly #MAX_QUEUE_SIZE = 50 // 减少队列大小 #tasks: WsReqMsgContentType[] = [] // 重连🔐 #connectReady = false // 使用LRU缓存替代简单的Set - #processedMsgCache = new Map() + #processedMsgCache = new LRUCache(1000) // 使用LRU缓存 #tauriListener: ReturnType | null = null @@ -112,13 +146,13 @@ class WS { }) ) - // 如果从不可见变为可见状态,并且网络重连工具已初始化,检查是否需要刷新数据 + // 优化的可见性恢复检查 if (isVisible && this.#networkReconnect?.isOnline?.value) { // 检查最后一次通信时间,如果太久没有通信,刷新数据 const now = Date.now() const lastPongTime = this.#connectionHealth.lastPongTime - if (lastPongTime && now - lastPongTime > 60000) { - // 如果超过1分钟没有心跳 + const heartbeatTimeout = 90000 // 增加到90秒,减少误触发 + if (lastPongTime && now - lastPongTime > heartbeatTimeout) { console.log('[Network] 应用从后台恢复且长时间无心跳,刷新数据') this.#networkReconnect?.refreshAllData() } @@ -384,10 +418,19 @@ class WS { if (this.#connectReady) { this.#send(params) } else { - // 队列限制 + // 优化的队列管理 if (this.#tasks.length >= this.#MAX_QUEUE_SIZE) { - console.warn('消息队列已满,正在丢弃最旧的消息') - this.#tasks.shift() + // 优先丢弃非关键消息 + const nonCriticalIndex = this.#tasks.findIndex( + (task) => typeof task === 'object' && task.type !== 1 && task.type !== 2 + ) + if (nonCriticalIndex !== -1) { + this.#tasks.splice(nonCriticalIndex, 1) + console.warn('消息队列已满,丢弃非关键消息') + } else { + this.#tasks.shift() + console.warn('消息队列已满,丢弃最旧消息') + } } this.#tasks.push(params) } @@ -597,21 +640,50 @@ class WS { } destroy() { - worker.postMessage(JSON.stringify({ type: 'clearReconnectTimer' })) - worker.terminate() - // 同时终止timer worker相关的心跳 - timerWorker.postMessage({ - type: 'stopPeriodicHeartbeat' - }) - this.#tasks = [] - this.#processedMsgCache.clear() - this.#connectReady = false - // 清理 Tauri 事件监听器 - this.#tauriListener?.cleanup() + try { + // 优化的资源清理顺序 + worker.postMessage(JSON.stringify({ type: 'clearReconnectTimer' })) + worker.postMessage(JSON.stringify({ type: 'stopHeartbeat' })) - // 清理所有watch - this.#unwatchFunctions.forEach((unwatch) => unwatch()) - this.#unwatchFunctions = [] + // 同时终止timer worker相关的心跳 + timerWorker.postMessage({ + type: 'stopPeriodicHeartbeat' + }) + + // 清理内存 + this.#tasks.length = 0 // 更高效的数组清空 + this.#processedMsgCache.clear() + this.#connectReady = false + + // 重置连接健康状态 + this.#connectionHealth = { + isHealthy: true, + lastPongTime: null, + timeSinceLastPong: null + } + + // 清理 Tauri 事件监听器 + this.#tauriListener?.cleanup() + this.#tauriListener = null + + // 清理所有watch + this.#unwatchFunctions.forEach((unwatch) => { + try { + unwatch() + } catch (error) { + console.warn('清理watch函数时出错:', error) + } + }) + this.#unwatchFunctions.length = 0 + + // 最后终止workers + setTimeout(() => { + worker.terminate() + timerWorker.terminate() + }, 100) // 给一点时间让消息处理完成 + } catch (error) { + console.error('销毁WebSocket时出错:', error) + } } } diff --git a/src/workers/timer.worker.ts b/src/workers/timer.worker.ts index 90a2e004..61b1719a 100644 --- a/src/workers/timer.worker.ts +++ b/src/workers/timer.worker.ts @@ -24,17 +24,20 @@ const checkAllTimersCompleted = () => { } } -// 添加调试信息打印函数 +// 优化的调试信息打印函数 const logDebugInfo = (msgId: number, remainingTime: number) => { - // 只有开启日志功能时才打印 - if (enableLogging) { + // 只有开启日志功能时才打印,且只在关键时间点打印 + if (enableLogging && (remainingTime <= 5000 || remainingTime % 10000 < 1000)) { console.log(`[Worker Debug] 消息ID: ${msgId}, 剩余时间: ${(remainingTime / 1000).toFixed(1)}秒`) - self.postMessage({ - type: 'debug', - msgId, - remainingTime, - timestamp: Date.now() - }) + // 减少向主线程发送调试消息的频率 + if (remainingTime <= 3000) { + self.postMessage({ + type: 'debug', + msgId, + remainingTime, + timestamp: Date.now() + }) + } } } @@ -103,7 +106,8 @@ self.onmessage = (e) => { // 立即打印一次初始状态 logDebugInfo(msgId, duration) - // 创建定时打印的间隔器,每1000ms打印一次 + // 优化的调试间隔器,减少打印频率 + const debugInterval = duration > 10000 ? 5000 : 1000 // 长任务每5秒打印,短任务每秒打印 const debugId = setInterval(() => { const elapsed = Date.now() - startTime const remaining = duration - elapsed @@ -112,7 +116,7 @@ self.onmessage = (e) => { } else { clearInterval(debugId) } - }, 1000) // 每秒打印一次 + }, debugInterval) const timerId = setTimeout(() => { clearInterval(debugId) diff --git a/src/workers/webSocket.worker.ts b/src/workers/webSocket.worker.ts index 734ddb72..a842ee68 100644 --- a/src/workers/webSocket.worker.ts +++ b/src/workers/webSocket.worker.ts @@ -129,27 +129,35 @@ const sendSingleHeartbeat = () => { return } - // 检测连接健康状态 + // 优化的连接健康检测机制 if (lastPongTime !== null) { const timeSinceLastPong = lastPingSent - lastPongTime - const isConnectionHealthy = timeSinceLastPong < HEARTBEAT_INTERVAL * 2 + const healthThreshold = HEARTBEAT_INTERVAL * 2.5 // 增加容错时间 + const isConnectionHealthy = timeSinceLastPong < healthThreshold // 如果连接不健康,通知主线程 if (!isConnectionHealthy) { consecutiveHeartbeatFailures++ - logHeartbeat('连接响应缓慢', { - consecutiveFailures: consecutiveHeartbeatFailures, - timeSinceLastPong - }) - postMsg({ - type: WorkerMsgEnum.ERROR, - value: { - msg: '连接响应较慢,可能存在网络问题', - timeSinceLastPong, - consecutiveFailures: consecutiveHeartbeatFailures - } - }) + // 只在关键阈值时记录日志,减少日志开销 + if (consecutiveHeartbeatFailures === 1 || consecutiveHeartbeatFailures % 3 === 0) { + logHeartbeat('连接响应缓慢', { + consecutiveFailures: consecutiveHeartbeatFailures, + timeSinceLastPong + }) + } + + // 延迟错误通知,避免频繁触发 + if (consecutiveHeartbeatFailures >= 2) { + postMsg({ + type: WorkerMsgEnum.ERROR, + value: { + msg: '连接响应较慢,可能存在网络问题', + timeSinceLastPong, + consecutiveFailures: consecutiveHeartbeatFailures + } + }) + } // 连续失败次数过多,尝试重连 if (consecutiveHeartbeatFailures >= MAX_HEARTBEAT_FAILURES) { @@ -244,11 +252,15 @@ const tryReconnect = () => { } } +// 优化的智能退避算法 const getBackoffDelay = (retryCount: number) => { const baseDelay = 1000 // 基础延迟1秒 - const maxDelay = 30000 // 最大延迟30秒 - const delay = Math.min(baseDelay * Math.pow(2, retryCount), maxDelay) - return delay + Math.random() * 1000 // 添加随机抖动 + const maxDelay = 15000 // 减少最大延迟到15秒 + const multiplier = Math.min(1.5, 2 - retryCount * 0.1) + const delay = Math.min(baseDelay * Math.pow(multiplier, retryCount), maxDelay) + + // 减少随机抖动范围 + return delay + Math.random() * 500 } const onCloseHandler = () => {