Vue2与WebSocket实战:构建高效实时聊天室的全流程解析

news2026/3/14 4:06:26
1. 为什么需要WebSocket从“轮询”到“长连接”的进化想象一下你正在和一个朋友用微信聊天。如果微信用的是传统的HTTP协议那会是什么场景你发一句“在吗”然后你的手机就得不停地、每隔一秒就问一次服务器“他回我了吗他回我了吗他回我了吗” 直到朋友回复“在的”你才能看到。这个过程不仅浪费你的手机电量频繁请求还浪费服务器资源最关键的是你看到回复会有明显的延迟体验非常糟糕。这种技术就叫“轮询”。WebSocket的出现就是为了彻底解决这个问题。它就像在你和服务器之间拉了一根“电话线”。一旦接通建立连接你们就可以随时互相说话服务器有新消息可以立刻“喊”你你再也不用傻傻地反复去问了。这就是所谓的“全双工通信”。对于聊天室、实时协作文档、股票行情、在线游戏这些需要即时反馈的场景WebSocket是唯一正确的选择。在Vue2项目中我们直接使用浏览器原生的WebSocket API无需引入复杂的第三方库比如Socket.io就能构建出稳定高效的实时应用。原生API足够轻量、可控对于理解底层原理和进行深度定制非常有帮助。接下来我就带你从零开始手把手搭建一个功能完整的聊天室。2. 项目初始化与WebSocket连接建立首先我们创建一个标准的Vue2项目。这里我假设你已经配置好了Vue CLI环境。vue create vue2-chat-demo cd vue2-chat-demo我们的核心逻辑会放在一个独立的Vue组件里比如ChatRoom.vue。第一步也是最关键的一步就是建立WebSocket连接。连接地址的讲究WebSocket协议分为ws://非加密和wss://加密相当于HTTPS。本地开发通常用ws://localhost:你的后端端口而上线生产环境必须使用wss://否则现代浏览器会因安全策略阻止连接。在组件的data中我们定义连接所需的核心数据并在created或mounted生命周期钩子中初始化连接// ChatRoom.vue - script部分 export default { data() { return { websocket: null, // WebSocket实例 messageList: [], // 消息列表 inputMessage: , // 输入框内容 onlineUsers: [], // 在线用户列表 connectionStatus: 连接中..., // 连接状态提示 // 连接配置实际项目中应从环境变量或配置中心读取 wsUrl: process.env.NODE_ENV production ? wss://你的生产服务器域名/chat : ws://localhost:8080/chat, reconnectAttempts: 0, // 重连次数 maxReconnectAttempts: 5, // 最大重连次数 }; }, created() { this.initWebSocket(); }, beforeDestroy() { // 组件销毁前务必关闭连接防止内存泄漏 this.closeWebSocket(); }, methods: { initWebSocket() { try { // 1. 创建WebSocket实例 this.websocket new WebSocket(this.wsUrl); // 2. 监听连接打开事件 this.websocket.onopen this.handleWebSocketOpen; // 3. 监听消息接收事件 this.websocket.onmessage this.handleWebSocketMessage; // 4. 监听错误事件 this.websocket.onerror this.handleWebSocketError; // 5. 监听连接关闭事件 this.websocket.onclose this.handleWebSocketClose; } catch (error) { console.error(WebSocket初始化失败:, error); this.connectionStatus 连接初始化失败; } }, handleWebSocketOpen(event) { console.log(WebSocket连接成功建立, event); this.connectionStatus 已连接; this.reconnectAttempts 0; // 连接成功重置重连计数 // 连接建立后可以发送一个身份认证消息如果需要 // 例如发送登录后的token const authMsg { type: auth, token: localStorage.getItem(user_token) // 假设token存在localStorage }; this.sendMessage(authMsg); }, // ... 其他处理方法将在后面展开 } };这里有几个我踩过的坑要提醒你连接时机不要在mounted里盲目连接。如果组件需要用户登录后才能使用确保先获取到token再连接。URL协议务必区分开发和生产环境wss://是线上标配。错误处理onerror事件必须监听网络波动、服务器重启都可能导致连接异常。2.1 处理连接异常与自动重连真实的网络环境是不稳定的。用户切换Wi-Fi/4G、服务器短暂重启都会导致连接断开。一个健壮的聊天室必须具备自动重连能力。methods: { handleWebSocketError(error) { console.error(WebSocket发生错误:, error); this.connectionStatus 连接出错; // 错误事件触发后通常很快会触发 onclose所以我们主要在close事件里处理重连 }, handleWebSocketClose(event) { console.log(WebSocket连接关闭代码: ${event.code}, 原因: ${event.reason}); this.connectionStatus 连接已断开; this.websocket null; // 非正常关闭非主动关闭且未超过最大重连次数则尝试重连 if (event.code ! 1000 this.reconnectAttempts this.maxReconnectAttempts) { this.reconnectAttempts; const delay Math.min(1000 * Math.pow(2, this.reconnectAttempts), 10000); // 指数退避最大10秒 console.log(${delay/1000}秒后尝试第${this.reconnectAttempts}次重连...); setTimeout(() { if (this.$options.beforeDestroy) return; // 防止组件已销毁还执行重连 this.initWebSocket(); }, delay); } else if (this.reconnectAttempts this.maxReconnectAttempts) { this.connectionStatus 连接失败请刷新页面重试; } }, closeWebSocket() { if (this.websocket this.websocket.readyState WebSocket.OPEN) { // 1000是正常关闭的状态码 this.websocket.close(1000, 用户主动离开); } } }指数退避算法是个小技巧第一次重连等2秒第二次等4秒第三次等8秒……这样避免在服务器短暂故障时所有客户端同时疯狂重连给服务器造成“惊群”效应。3. 消息收发心跳、协议设计与数据解析连接建立后核心就是收发消息。但直接收发改字符串太原始了我们需要设计一个简单的应用层协议。3.1 定义消息格式我建议前后端约定使用JSON格式并包含一个type字段来区分消息类型。// 前端发送的消息格式示例 const messageTemplates { // 文本消息 text: { type: chat, senderId: user_123, senderName: 小明, content: 你好世界, timestamp: Date.now(), receiverId: user_456 // 如果是私聊 }, // 系统消息如加入/离开房间 system: { type: system, event: user_join, // 或 user_leave, room_notice userId: user_123, userName: 小明, timestamp: Date.now() }, // 心跳包用于保持连接活跃和检测存活 heartbeat: { type: heartbeat, timestamp: Date.now() }, // 消息已读回执 readReceipt: { type: read_receipt, messageId: msg_789, readerId: user_456 } };3.2 实现消息发送与接收发送消息很简单调用WebSocket.send()方法但记得要把对象转成字符串。methods: { sendMessage(payload) { // 检查连接状态 if (!this.websocket || this.websocket.readyState ! WebSocket.OPEN) { this.$message.error(连接未就绪无法发送消息); return false; } try { const messageString JSON.stringify(payload); this.websocket.send(messageString); console.log(消息已发送:, payload); return true; } catch (error) { console.error(消息发送失败:, error); this.$message.error(消息发送失败请检查网络); return false; } }, // 发送文本消息供UI调用 sendTextMessage() { if (!this.inputMessage.trim()) return; const textMsg { type: chat, senderId: this.currentUser.id, senderName: this.currentUser.name, content: this.inputMessage.trim(), timestamp: Date.now(), // 如果是群聊可能还有 roomId roomId: this.currentRoomId }; if (this.sendMessage(textMsg)) { // 发送成功可以立即在本地界面显示一条“发送中”的消息提升体验 const localMsg { ...textMsg, status: sending, // 发送中状态 localId: local_${Date.now()} // 本地临时ID用于后续更新状态 }; this.messageList.push(localMsg); this.inputMessage ; // 清空输入框 this.scrollToBottom(); // 滚动到底部 } }, handleWebSocketMessage(event) { try { const rawData event.data; const message JSON.parse(rawData); console.log(收到服务器消息:, message); // 根据消息类型进行分发处理 switch (message.type) { case chat: this.handleChatMessage(message); break; case system: this.handleSystemMessage(message); break; case heartbeat_ack: // 服务器对心跳的回应 this.lastHeartbeatAck Date.now(); break; case online_users: this.onlineUsers message.userList; break; case message_status: // 消息状态更新如发送成功、已读 this.updateMessageStatus(message); break; default: console.warn(未知的消息类型:, message.type); } } catch (error) { console.error(消息解析失败:, error, 原始数据:, event.data); } }, handleChatMessage(msg) { // 如果是自己刚发送的消息且服务器返回了正式ID则更新本地消息状态 const localMsgIndex this.messageList.findIndex(m m.localId m.content msg.content); if (localMsgIndex -1) { // 用服务器返回的消息替换本地临时消息 msg.status sent; // 发送成功 this.messageList.splice(localMsgIndex, 1, msg); } else { // 收到他人的消息 msg.status received; this.messageList.push(msg); } // 如果当前聊天窗口正是发送者可以发送已读回执 if (this.isActiveChat(msg.senderId)) { this.sendReadReceipt(msg.id); } this.scrollToBottom(); // 可以在这里触发新消息提示音 this.playNotificationSound(); }, handleSystemMessage(msg) { // 将系统消息也加入消息列表但用不同样式展示 const systemMsg { ...msg, isSystem: true }; this.messageList.push(systemMsg); this.$notify({ title: 系统通知, message: ${msg.userName} ${msg.event user_join ? 加入了 : 离开了}聊天室, type: info }); } }3.3 实现心跳机制长时间空闲的连接可能会被防火墙或代理服务器断开。为了保持连接活跃我们需要定期发送“心跳包”。data() { return { // ... 其他数据 heartbeatInterval: null, // 心跳定时器ID heartbeatIntervalTime: 30000, // 30秒发送一次心跳 lastHeartbeatAck: null, // 最后一次收到心跳ACK的时间 heartbeatCheckInterval: null // 检查心跳响应的定时器 }; }, methods: { startHeartbeat() { // 停止可能存在的旧定时器 this.stopHeartbeat(); // 定时发送心跳 this.heartbeatInterval setInterval(() { if (this.websocket this.websocket.readyState WebSocket.OPEN) { const heartbeatMsg { type: heartbeat, timestamp: Date.now() }; this.websocket.send(JSON.stringify(heartbeatMsg)); console.log(心跳已发送); } }, this.heartbeatIntervalTime); // 定时检查心跳ACK如果超过一定时间没收到认为连接已死触发重连 this.heartbeatCheckInterval setInterval(() { if (this.lastHeartbeatAck Date.now() - this.lastHeartbeatAck this.heartbeatIntervalTime * 2) { console.warn(心跳ACK超时连接可能已断开); if (this.websocket) { this.websocket.close(); // 手动触发close事件进入重连逻辑 } } }, this.heartbeatIntervalTime); }, stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval null; } if (this.heartbeatCheckInterval) { clearInterval(this.heartbeatCheckInterval); this.heartbeatCheckInterval null; } } }, // 在连接成功时启动心跳 handleWebSocketOpen() { // ... 其他逻辑 this.startHeartbeat(); }, // 在连接关闭时停止心跳 handleWebSocketClose() { // ... 其他逻辑 this.stopHeartbeat(); }4. Vue2中的状态管理与组件设计当聊天功能变得复杂比如有多个聊天室、好友列表、未读消息计数时把所有状态和逻辑都塞在一个ChatRoom.vue里会变得难以维护。我们需要合理的状态管理和组件拆分。4.1 使用Vuex进行集中状态管理对于跨组件的状态如当前用户信息、所有会话列表、总未读消息数使用Vuex是明智的选择。// store/modules/chat.js const state { currentUser: null, sessions: [], // 所有会话 {id, name, lastMessage, unreadCount, avatar} currentSessionId: null, messages: {}, // 以sessionId为key存储各会话的消息列表 onlineUsers: [] }; const mutations { SET_CURRENT_USER(state, user) { state.currentUser user; }, ADD_SESSION(state, session) { const exists state.sessions.find(s s.id session.id); if (!exists) { state.sessions.push(session); } }, UPDATE_SESSION_LAST_MSG(state, { sessionId, lastMessage, timestamp }) { const session state.sessions.find(s s.id sessionId); if (session) { session.lastMessage lastMessage; session.lastMessageTime timestamp; } }, INCREMENT_UNREAD(state, sessionId) { const session state.sessions.find(s s.id sessionId); if (session sessionId ! state.currentSessionId) { // 只有不在当前活跃会话时才增加未读 session.unreadCount (session.unreadCount || 0) 1; } }, CLEAR_UNREAD(state, sessionId) { const session state.sessions.find(s s.id sessionId); if (session) { session.unreadCount 0; } }, ADD_MESSAGE(state, { sessionId, message }) { if (!state.messages[sessionId]) { state.messages[sessionId] []; } state.messages[sessionId].push(message); // 限制每个会话最多保存200条消息防止内存溢出 if (state.messages[sessionId].length 200) { state.messages[sessionId].shift(); } } }; const actions { // 发送消息的Action会先提交本地再通过WebSocket发送 async sendMessage({ commit, state }, { sessionId, content }) { const localMsg { id: local_${Date.now()}, type: text, senderId: state.currentUser.id, content, timestamp: Date.now(), status: sending }; commit(ADD_MESSAGE, { sessionId, message: localMsg }); commit(UPDATE_SESSION_LAST_MSG, { sessionId, lastMessage: content, timestamp: localMsg.timestamp }); // 调用WebSocket服务发送 await this._vm.$ws.send({ type: chat, sessionId, content, // ... 其他字段 }); // 注意消息发送成功的状态更新应在WebSocket的onmessage回调中通过另一个mutation来更新 }, // WebSocket收到新消息时调用 onNewMessage({ commit, state }, message) { const { sessionId } message; commit(ADD_MESSAGE, { sessionId, message }); commit(UPDATE_SESSION_LAST_MSG, { sessionId, lastMessage: message.content, timestamp: message.timestamp }); commit(INCREMENT_UNREAD, sessionId); } }; export default { namespaced: true, state, mutations, actions };4.2 组件拆分与通信将庞大的聊天界面拆分成几个职责单一的组件会让代码清晰很多。ChatRoom.vue (主容器) ├── SessionList.vue (左侧会话列表) ├── ChatWindow.vue (中间聊天窗口) │ ├── MessageList.vue (消息列表) │ └── MessageInput.vue (底部输入框) └── UserPanel.vue (右侧在线用户面板)如何让所有组件都能访问WebSocket实例我推荐使用Vue插件或全局事件总线Event Bus但更优雅的方式是创建一个独立的WebSocket服务模块在Vue原型上注入。// services/websocket.js class WebSocketService { constructor(url) { this.url url; this.socket null; this.messageHandlers new Map(); // 存储不同类型消息的回调 this.reconnectTimer null; } connect() { // ... 连接逻辑同上文 } send(data) { // ... 发送逻辑 } on(type, handler) { // 注册消息处理器 if (!this.messageHandlers.has(type)) { this.messageHandlers.set(type, []); } this.messageHandlers.get(type).push(handler); } off(type, handler) { // 移除消息处理器 const handlers this.messageHandlers.get(type); if (handlers) { const index handlers.indexOf(handler); if (index -1) handlers.splice(index, 1); } } // 内部方法收到消息时分发给所有注册的处理器 _dispatchMessage(message) { const handlers this.messageHandlers.get(message.type) || []; handlers.forEach(handler handler(message)); } } // main.js import WebSocketService from ./services/websocket; const wsService new WebSocketService(process.env.VUE_APP_WS_URL); Vue.prototype.$ws wsService; // 在组件中使用 export default { mounted() { // 注册处理聊天消息 this.$ws.on(chat, this.handleIncomingChat); // 注册处理系统消息 this.$ws.on(system, this.handleSystemNotice); }, beforeDestroy() { // 组件销毁时务必移除监听防止内存泄漏 this.$ws.off(chat, this.handleIncomingChat); this.$ws.off(system, this.handleSystemNotice); }, methods: { handleIncomingChat(message) { // 处理聊天消息 this.$store.dispatch(chat/onNewMessage, message); } } }5. 性能优化与用户体验打磨功能实现后性能和使用体验是决定产品好坏的关键。这里分享几个我实战中总结的优化点。5.1 消息列表的虚拟滚动当聊天记录积累到几千条时一次性渲染所有DOM节点会导致页面严重卡顿。解决方案是虚拟滚动只渲染可视区域及附近的消息。我们可以使用成熟的库如vue-virtual-scroller也可以自己实现一个简化版。核心思路是计算滚动位置动态截取需要显示的消息片段。!-- MessageList.vue 简化示例 -- template div classmessage-container refcontainer scrollhandleScroll div classscroll-placeholder :style{ height: ${totalHeight}px } !-- 这个占位div撑开滚动条 -- /div div classmessage-viewport :style{ transform: translateY(${offsetY}px) } div v-formsg in visibleMessages :keymsg.id classmessage-item !-- 渲染单条消息 -- {{ msg.content }} /div /div /div /template script export default { props: [messages], // 所有消息 data() { return { containerHeight: 0, scrollTop: 0, itemHeight: 60, // 预估每条消息高度 buffer: 5 // 上下缓冲条数 }; }, computed: { totalHeight() { return this.messages.length * this.itemHeight; }, startIndex() { // 计算起始索引 let index Math.floor(this.scrollTop / this.itemHeight) - this.buffer; return Math.max(0, index); }, endIndex() { // 计算结束索引 let index this.startIndex Math.ceil(this.containerHeight / this.itemHeight) this.buffer * 2; return Math.min(this.messages.length, index); }, visibleMessages() { return this.messages.slice(this.startIndex, this.endIndex); }, offsetY() { return this.startIndex * this.itemHeight; } }, mounted() { this.containerHeight this.$refs.container.clientHeight; window.addEventListener(resize, this.updateContainerHeight); }, methods: { handleScroll() { this.scrollTop this.$refs.container.scrollTop; }, updateContainerHeight() { this.containerHeight this.$refs.container.clientHeight; }, // 收到新消息时如果已在底部自动滚动到底部 scrollToBottomIfNeeded() { const container this.$refs.container; // 判断是否在底部距离底部小于50px视为在底部 if (container.scrollHeight - container.scrollTop - container.clientHeight 50) { this.$nextTick(() { container.scrollTop container.scrollHeight; }); } } }, watch: { messages: { handler() { this.scrollToBottomIfNeeded(); }, deep: true } } }; /script5.2 消息的本地存储与同步用户不希望每次刷新页面聊天记录就清空了。我们可以将消息缓存到localStorage或IndexedDB。// utils/messageStorage.js const STORAGE_KEY_PREFIX chat_messages_; export default { // 保存某个会话的消息 saveMessages(sessionId, messages) { try { // 只保存最近100条避免localStorage超出容量通常5MB const toSave messages.slice(-100); localStorage.setItem(${STORAGE_KEY_PREFIX}${sessionId}, JSON.stringify(toSave)); } catch (e) { console.error(保存消息到本地存储失败:, e); // 如果超出容量可以尝试清理更早的会话 this.clearOldSessions(); } }, // 读取某个会话的消息 loadMessages(sessionId) { try { const data localStorage.getItem(${STORAGE_KEY_PREFIX}${sessionId}); return data ? JSON.parse(data) : []; } catch (e) { console.error(从本地存储读取消息失败:, e); return []; } }, // 清理超过7天的会话数据 clearOldSessions() { const oneWeekAgo Date.now() - 7 * 24 * 60 * 60 * 1000; for (let i 0; i localStorage.length; i) { const key localStorage.key(i); if (key.startsWith(STORAGE_KEY_PREFIX)) { try { const data JSON.parse(localStorage.getItem(key)); if (data.length 0 data[data.length-1].timestamp oneWeekAgo) { localStorage.removeItem(key); } } catch (e) { // 解析失败直接删除 localStorage.removeItem(key); } } } } };在组件中我们可以在created时从本地存储加载历史消息并在每次收到新消息后保存。5.3 断线重连时的消息补发与防重复网络恢复重连后我们可能需要获取断开期间错过的消息。这里有个常见问题如何避免消息重复解决方案每条消息都有一个服务器生成的唯一ID或时间戳序列号。客户端记录已收到的最后一条消息的ID。重连后向服务器请求这个ID之后的消息。// 在WebSocket连接成功后的处理 handleWebSocketOpen() { this.connectionStatus 已连接; // 发送一个同步请求获取上次断开后遗漏的消息 const lastMsgId this.getLastMessageId(); // 从本地存储获取 const syncMsg { type: sync, lastMessageId: lastMsgId, timestamp: Date.now() }; this.websocket.send(JSON.stringify(syncMsg)); } // 服务器应能处理这种sync请求返回遗漏的消息对于发送中的消息重连后需要检查其状态。如果还是sending可以尝试重新发送但要注意给消息加上重试次数限制避免无限循环。5.4 输入体验优化提及、表情与图片发送一个好用的聊天室输入框的体验至关重要。提及功能监听输入框的字符弹出在线用户列表供选择。template div classinput-area div classmention-popover v-ifshowMentionList div v-foruser in filteredUsers :keyuser.id clickinsertMention(user) {{ user.name }} /div /div textarea v-modelinputText inputhandleInput keydown.enter.preventsendMessage placeholder输入消息提及某人 /textarea button clicksendMessage发送/button /div /template script export default { data() { return { inputText: , showMentionList: false, mentionStartIndex: -1, onlineUsers: [] // 从Vuex获取 }; }, computed: { filteredUsers() { if (this.mentionStartIndex -1) return []; const searchText this.inputText.slice(this.mentionStartIndex 1).toLowerCase(); return this.onlineUsers.filter(user user.name.toLowerCase().includes(searchText) ).slice(0, 5); // 最多显示5个 } }, methods: { handleInput(event) { const cursorPos event.target.selectionStart; const textBeforeCursor this.inputText.slice(0, cursorPos); // 查找光标前最近的符号 const lastAtIndex textBeforeCursor.lastIndexOf(); if (lastAtIndex -1 /^[\s]?$/.test(textBeforeCursor.slice(lastAtIndex 1, cursorPos))) { // 后面是空格或直接是光标显示提及列表 this.showMentionList true; this.mentionStartIndex lastAtIndex; } else { this.showMentionList false; this.mentionStartIndex -1; } }, insertMention(user) { const beforeMention this.inputText.slice(0, this.mentionStartIndex); const afterMention this.inputText.slice(this.inputText.indexOf( , this.mentionStartIndex) || this.inputText.length); this.inputText ${beforeMention}${user.name} ${afterMention}; this.showMentionList false; this.$nextTick(() { this.$refs.textarea.focus(); }); }, sendMessage() { // 发送前解析inputText中的提及转换为服务器能识别的格式 const message this.parseMentions(this.inputText); this.$emit(send, message); this.inputText ; }, parseMentions(text) { // 简单实现查找用户名替换为特殊标记如 mention id123用户名/mention // 实际项目需要更健壮的解析 return text; } } }; /script图片发送可以使用input[typefile]选择图片通过FileReader读取为Base64或直接通过FormData上传到文件服务器然后将得到的URL作为消息内容发送。注意Base64数据量很大不适合直接通过WebSocket传输最好先上传。6. 部署上线与生产环境注意事项开发完成准备上线时还有几个关键点需要注意。1. Nginx反向代理WebSocket如果你的后端WebSocket服务运行在某个端口如3000而前端通过80或443端口访问你需要配置Nginx来代理WebSocket连接。# Nginx配置示例 server { listen 443 ssl; server_name yourdomain.com; ssl_certificate /path/to/your/cert.pem; ssl_certificate_key /path/to/your/key.pem; location / { root /path/to/your/vue/dist; index index.html; try_files $uri $uri/ /index.html; } # WebSocket代理配置 location /chat/ { proxy_pass http://localhost:3000; # 你的WebSocket后端地址 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 86400s; # WebSocket长连接超时时间 proxy_send_timeout 86400s; } }2. 使用WSS协议生产环境必须使用wss://。你需要为你的域名配置SSL证书Lets Encrypt提供免费的。Nginx配置如上所示同时前端连接地址也要改为wss://yourdomain.com/chat。3. 连接数限制与扩容单个服务器的WebSocket连接数是有上限的受内存和文件描述符限制。当用户量增长时你需要考虑水平扩展使用多个WebSocket服务器通过负载均衡器如Nginx分发连接。会话粘滞确保同一个用户的请求总是落到同一台后端服务器因为WebSocket连接是有状态的。Nginx可以通过ip_hash或hash $cookie_xxx实现。使用Redis等中间件在多服务器环境下广播消息需要借助Redis的Pub/Sub功能让所有服务器都能收到通知并转发给其连接的客户端。4. 监控与日志上线后监控是必不可少的。前端监控捕获并上报WebSocket的连接错误、重连次数、消息发送失败等。后端监控监控每个服务器的连接数、内存使用、消息吞吐量。关键日志记录连接建立/断开、异常消息格式、认证失败等便于排查问题。5. 优雅降级虽然现代浏览器都支持WebSocket但极端情况下如某些企业防火墙会阻断WebSocket我们需要有降级方案。可以尝试以下策略首先尝试WebSocket连接。如果失败尝试降级到HTTP长轮询Long Polling。可以引入像Socket.io这样的库它内置了多种传输方式WebSocket、轮询等并自动选择最佳方案但会显著增加客户端体积。在实际项目中我从零搭建过好几个基于Vue2和原生WebSocket的实时应用从简单的客服系统到复杂的在线协作工具。最大的体会是稳定性高于一切。网络是不稳定的代码要足够健壮来处理各种异常用户体验是核心消息的送达反馈、断线重连的提示、历史记录的保存这些细节决定了用户是否愿意持续使用你的产品。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2409865.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…