Python WebSocket 实战:从零构建轻量级实时聊天应用

news2026/5/9 3:57:32
1. 项目概述一个轻量级聊天应用的诞生最近在GitHub上看到一个挺有意思的项目叫pymike00/tinychat。光看名字就能猜个大概——这应该是一个用Python实现的、主打轻量化的聊天应用。作为一个在后台开发和网络编程领域摸爬滚打了十多年的老码农我对这类“麻雀虽小五脏俱全”的项目总是特别有好感。它们往往不是为了解决什么宏大的商业问题而是开发者为了解决一个具体的小痛点或者纯粹为了技术探索和乐趣而诞生的。tinychat给我的第一印象就是如此它可能没有Slack、Discord那样丰富的功能也没有微信、QQ那样庞大的生态但它提供了一个干净、直接的起点让你能快速理解一个实时聊天应用的核心骨架是如何搭建起来的。这个项目非常适合几类朋友一是正在学习Python网络编程和Web开发的同学想找一个有完整流程、代码量又不至于吓人的实战项目来练手二是需要快速搭建一个内部团队沟通工具或者为某个小型活动比如线上游戏、小型研讨会提供临时聊天室的后端开发者三是对WebSocket、异步编程感兴趣想看看一个最小可行产品MVP如何落地的技术爱好者。通过拆解tinychat你不仅能学会如何让消息在浏览器和服务器之间实时“飞”起来更能理解一个看似简单的应用背后关于连接管理、状态同步、异常处理等一系列工程细节的思考。接下来我就带你一起从设计思路到代码实现把这个小项目里里外外摸个透。2. 核心架构与设计思路拆解2.1 技术栈选型为什么是Python WebSockettinychat的核心技术栈非常清晰后端用Python通信协议用WebSocket前端大概率是简单的HTML/JavaScript。这个组合是构建现代轻量级实时应用的黄金搭档。首先说Python。选择Python作为后端语言首要考虑的是开发效率和生态。对于tinychat这类原型或小型应用Python的简洁语法和丰富的库能让你快速实现想法。项目很可能使用了像Tornado、Sanic或原生的asynciowebsockets库。这些框架或库都原生支持异步I/O这对于需要同时维持大量持久连接的聊天服务至关重要。同步模型如传统的WSGI下每个连接都会阻塞一个线程当用户数上去后线程切换和内存开销会成为瓶颈。而异步模型下一个线程或协程就能处理成千上万个连接在I/O密集型聊天就是典型的I/O密集型大部分时间在等待网络消息的场景下资源利用率极高性能表现也更好。其次是WebSocket。这是实现全双工、低延迟实时通信的Web标准协议。相比于古老的“轮询”Polling或“长轮询”Long PollingWebSocket在建立连接后客户端和服务器可以随时主动向对方发送数据没有HTTP那种“请求-响应”的 overhead。对于聊天应用这意味着用户A发送一条消息服务器收到后可以立即通过已建立的WebSocket连接推送给在线的用户B、C、D延迟极低体验流畅。如果使用HTTP轮询要么需要客户端频繁地问“有新消息吗”浪费带宽和服务器资源要么让连接挂起等待长轮询实现复杂且连接管理麻烦。因此WebSocket是实时聊天功能的不二之选。前端部分为了极致的轻量化和易于部署tinychat大概率没有引入React、Vue等重型框架而是采用原生JavaScript配合简单的HTML/CSS。这样做的优点是零依赖一个HTML文件就能运行学习成本低也便于我们聚焦于核心的通信逻辑。前端通过浏览器的WebSocketAPI与后端服务建立连接并处理连接事件、消息收发和界面更新。注意技术选型没有银弹。这个组合适合tinychat的定位但如果你的应用需要复杂的单页面应用SPA体验、状态管理或更高的性能天花板可能需要考虑更专业的前端框架如Vue/React和后端语言如Go, Node.js。但对于学习和快速原型Python WebSocket的组合足够优秀。2.2 核心架构设计单服务器与房间模型从tinychat的名字和定位来看它的架构不会太复杂。我推测其核心是一个单服务器、多房间或单大厅的广播模型。连接管理服务器启动后会监听一个特定的端口例如8765。每个用户通过浏览器访问前端页面页面中的JavaScript会尝试与这个服务器端口建立WebSocket连接。服务器需要维护一个所有活跃连接的列表或集合。在Python中这通常是一个全局的列表list或更高效的集合set里面存放着每个连接对象比如websockets.WebSocketServerProtocol实例。当新连接建立时将其加入集合当连接断开用户关闭页面或网络异常时将其从集合中移除。这是最基础也是最重要的一步漏掉清理会导致内存泄漏。消息路由与广播这是聊天应用的核心逻辑。当服务器从某个连接代表用户A收到一条消息时它需要将这条消息“广播”给其他在线的连接。最简单的实现就是遍历当前维护的所有连接集合除了发送者自己然后调用每个连接的send()方法。这就是所谓的“大厅”或“全局广播”模式所有在线用户都能看到彼此的消息。房间/频道模型稍微复杂一点也是更实用的模型是引入“房间”Room或“频道”Channel的概念。用户可以选择加入某个特定的房间比如“技术交流”、“闲聊区”。服务器需要维护一个从房间名到连接集合的映射例如用字典dict键是房间名值是该房间内的连接集合。当用户发送消息时需要指明目标房间服务器只将该消息广播给同一房间内的其他连接。tinychat很可能实现了这个模型因为它更贴近真实的聊天场景也能有效隔离不同话题的讨论。状态与协议客户端和服务器之间需要约定一个简单的通信协议。消息通常以JSON格式传输因为其结构清晰、易于解析。一条消息的JSON结构可能包含几个字段{ type: message, // 消息类型如“message”、“join”、“leave” room: general, // 房间名 user: Alice, // 发送者用户名 content: 大家好 // 消息内容 }服务器根据type字段来决定如何处理这条消息如果是join就将该连接加入到room字段指定的房间集合中如果是message就将content广播给同房间的其他用户如果是leave则从房间集合中移除该连接。这种设计清晰地将业务逻辑加入/离开房间、发送消息与网络传输解耦使得代码易于理解和扩展。3. 核心模块与代码实现深度解析3.1 后端服务器核心逻辑实现我们以使用Python标准库asyncio和第三方库websockets为例来深入剖析后端服务器的实现细节。首先你需要安装websockets库pip install websockets。服务器启动与事件循环 服务器的入口点是一个异步函数它绑定到特定主机和端口并开始监听连接。import asyncio import websockets import json # 全局数据结构用于管理房间和连接 # 格式{“room_name”: set([ws_connection1, ws_connection2, ...])} rooms {} async def chat_server(websocket, path): 每个WebSocket连接建立后都会创建一个此协程的实例来处理。 :param websocket: 当前的WebSocket连接对象 :param path: 客户端连接的路径在URI中可用于区分不同功能 # 这里path可以用于提取房间名例如连接 ws://localhost:8765/room1 # 那么 path 就是 /room1 room_name path.strip(/) or lobby # 默认房间名为“lobby” user_name Anonymous # 初始匿名实际应由客户端首次消息提供 # 将当前连接加入到对应的房间 if room_name not in rooms: rooms[room_name] set() rooms[room_name].add(websocket) print(f[] {user_name} 加入了房间 {room_name}。当前房间人数{len(rooms[room_name])}) try: # 主循环持续监听该连接发来的消息 async for message in websocket: # 1. 解析客户端消息 try: data json.loads(message) msg_type data.get(type, message) user_name data.get(user, user_name) # 更新用户名 content data.get(content, ) # 2. 根据消息类型处理 if msg_type join: # 处理加入房间可能切换房间 pass # 简化处理这里我们假设连接时即确定房间 elif msg_type message: # 构建要广播的消息体 broadcast_msg json.dumps({ type: message, user: user_name, content: content, room: room_name }) # 3. 广播给同房间的其他所有连接 # 注意需要排除发送者自己避免自己收到自己的消息 tasks [] for client in rooms[room_name]: if client ! websocket and client.open: tasks.append(client.send(broadcast_msg)) # 并发发送提高效率 if tasks: await asyncio.gather(*tasks, return_exceptionsTrue) print(f[{room_name}] {user_name}: {content}) elif msg_type leave: # 处理离开房间 break # 跳出循环进入finally块进行清理 except json.JSONDecodeError: # 处理非JSON格式消息 error_msg json.dumps({type: error, content: Invalid message format}) await websocket.send(error_msg) except websockets.exceptions.ConnectionClosed: # 连接异常关闭如网络断开、页面关闭 print(f[-] {user_name} 的连接异常关闭。) finally: # 无论正常离开还是异常都要执行清理从房间中移除连接 rooms[room_name].discard(websocket) print(f[-] {user_name} 离开了房间 {room_name}。剩余人数{len(rooms[room_name])}) # 如果房间为空可以考虑删除该房间键值对以节省内存 if not rooms[room_name]: del rooms[room_name] async def main(): # 启动WebSocket服务器监听所有网络接口0.0.0.0的8765端口 async with websockets.serve(chat_server, 0.0.0.0, 8765): print(Tinychat 服务器已启动在 ws://0.0.0.0:8765) # 保持服务器运行直到被手动停止 await asyncio.Future() if __name__ __main__: asyncio.run(main())关键点解析异步事件循环asyncio.run(main())启动了Python的异步事件循环这是所有异步操作的基础。连接处理函数chat_server是一个异步函数每个新连接都会独立运行一个它的实例。async for message in websocket:这行代码是关键它使服务器可以持续、非阻塞地从这个特定连接读取消息。房间管理使用全局字典rooms来管理。set集合用于存储连接因为它提供了O(1)时间复杂度的添加、删除和成员检查非常适合这个场景。广播优化在广播消息时我们创建了一个任务列表tasks然后使用asyncio.gather并发执行所有发送操作。这比用for循环依次await client.send(...)要快得多因为后者是顺序等待而gather是并发执行。return_exceptionsTrue参数确保即使某个发送任务失败如客户端突然断开也不会影响其他任务的执行。异常处理与资源清理try...except...finally结构确保了无论连接是正常关闭客户端发送leave还是异常断开网络问题都会执行finally块中的代码将连接从房间集合中移除防止内存泄漏。这是服务器稳定性的基石。实操心得在管理全局状态如rooms字典时要特别注意多线程/多协程下的并发安全问题。虽然asyncio是单线程的但在一个协程修改rooms的同时另一个协程可能正在遍历它比如广播时这可能导致RuntimeError: Set changed size during iteration。更安全的做法是使用asyncio.Lock异步锁来保护对共享数据的修改或者使用专门的数据结构如asyncio.Queue。对于tinychat这种小规模应用由于连接处理协程在修改rooms添加/删除连接时不会同时进行遍历广播广播发生在该协程内部且用到了当前房间连接集合的快照所以风险较低。但在更复杂的场景下锁是必须的。3.2 前端客户端实现与交互前端的目标是提供一个简单的界面让用户输入用户名、选择房间、发送和接收消息。我们使用原生HTML、CSS和JavaScript来实现。HTML结构!DOCTYPE html html head titleTinyChat/title style body { font-family: sans-serif; max-width: 800px; margin: 20px auto; } #chat-box { border: 1px solid #ccc; height: 400px; overflow-y: auto; padding: 10px; margin-bottom: 10px; } .message { margin-bottom: 8px; } .message .user { font-weight: bold; color: #007bff; } .message .system { font-style: italic; color: #6c757d; } #input-area { display: flex; } #message-input { flex-grow: 1; padding: 8px; } #send-button { padding: 8px 15px; } #login-area { margin-bottom: 15px; } /style /head body h1 TinyChat/h1 div idlogin-area input typetext idusername-input placeholder请输入用户名 value游客 input typetext idroom-input placeholder请输入房间名 valuelobby button idconnect-button连接/button button iddisconnect-button disabled断开/button /div div idchat-box/div div idinput-area input typetext idmessage-input placeholder输入消息... disabled button idsend-button disabled发送/button /div script srctinychat.js/script /body /htmlJavaScript逻辑 (tinychat.js) 这是前端的核心负责建立WebSocket连接、处理用户交互和更新界面。let socket null; let currentRoom lobby; let currentUser 游客; const chatBox document.getElementById(chat-box); const messageInput document.getElementById(message-input); const sendButton document.getElementById(send-button); const connectButton document.getElementById(connect-button); const disconnectButton document.getElementById(disconnect-button); const usernameInput document.getElementById(username-input); const roomInput document.getElementById(room-input); // 工具函数向聊天框添加一条消息 function appendMessage(type, user, content, room null) { const msgDiv document.createElement(div); msgDiv.className message; let text ; if (type system) { msgDiv.classList.add(system); text [系统] ${content}; } else if (type message) { text span classuser${user}/span: ${content}; if (room room ! currentRoom) { text (来自房间: ${room}); // 如果未来支持接收其他房间消息的提示 } } else if (type error) { text [错误] ${content}; } msgDiv.innerHTML text; chatBox.appendChild(msgDiv); // 自动滚动到底部 chatBox.scrollTop chatBox.scrollHeight; } // 连接服务器 connectButton.onclick function() { currentUser usernameInput.value.trim() || Anonymous; currentRoom roomInput.value.trim() || lobby; const serverUrl ws://${window.location.hostname}:8765/${currentRoom}; // 假设服务器运行在8765端口 try { socket new WebSocket(serverUrl); appendMessage(system, null, 正在连接服务器 ${serverUrl}...); socket.onopen function(event) { appendMessage(system, null, 已连接到房间 ${currentRoom} 用户名: ${currentUser}); // 启用消息发送区域 messageInput.disabled false; sendButton.disabled false; disconnectButton.disabled false; connectButton.disabled true; // 可选发送一个“join”消息通知服务器用户名 socket.send(JSON.stringify({ type: join, user: currentUser, room: currentRoom })); }; socket.onmessage function(event) { try { const data JSON.parse(event.data); switch(data.type) { case message: appendMessage(message, data.user, data.content, data.room); break; case system: // 服务器可能发送的系统消息 case error: appendMessage(data.type, null, data.content); break; default: console.log(收到未知类型消息:, data); } } catch (e) { console.error(解析消息失败:, e, event.data); appendMessage(error, null, 收到无效消息格式); } }; socket.onclose function(event) { appendMessage(system, null, 连接已断开。); messageInput.disabled true; sendButton.disabled true; disconnectButton.disabled true; connectButton.disabled false; socket null; }; socket.onerror function(error) { console.error(WebSocket错误:, error); appendMessage(error, null, 网络连接出现错误); }; } catch (error) { appendMessage(error, null, 连接失败: ${error.message}); } }; // 发送消息 sendButton.onclick function() { sendMessage(); }; messageInput.addEventListener(keypress, function(e) { if (e.key Enter) { sendMessage(); } }); function sendMessage() { const content messageInput.value.trim(); if (!content || !socket || socket.readyState ! WebSocket.OPEN) { return; } const message { type: message, user: currentUser, room: currentRoom, content: content }; socket.send(JSON.stringify(message)); // 本地回显可选也可以等服务器广播回来 // appendMessage(message, currentUser, content); messageInput.value ; // 清空输入框 messageInput.focus(); } // 断开连接 disconnectButton.onclick function() { if (socket socket.readyState WebSocket.OPEN) { socket.send(JSON.stringify({ type: leave, user: currentUser })); socket.close(); // 触发 onclose 事件 } };前端关键点解析连接管理WebSocket对象是核心。通过new WebSocket(url)建立连接并通过onopen,onmessage,onclose,onerror四个事件回调来处理连接生命周期。状态同步前端需要维护当前用户(currentUser)、当前房间(currentRoom)和连接状态通过socket.readyState判断。UI控件的启用/禁用状态如输入框、按钮需要与连接状态同步防止在未连接时发送消息。消息收发发送消息时将JavaScript对象通过JSON.stringify()序列化成字符串再调用socket.send()。接收消息时在onmessage回调中用JSON.parse()解析数据并根据type字段更新UI。用户体验细节本地回显在sendMessage()函数中可以选择立即将用户自己发送的消息显示在聊天框本地回显以提供更快的响应。但更常见的做法是等待服务器广播回这条消息这样可以确认消息确实已送达服务器并被成功处理。如果做本地回显需要小心处理避免在弱网环境下用户看到自己发送成功但服务器实际没收到造成困惑。自动滚动在appendMessage函数末尾通过chatBox.scrollTop chatBox.scrollHeight;实现聊天内容自动滚动到最新消息这是聊天应用的标配体验。Enter键发送为输入框添加keypress事件监听当按下Enter键时触发发送符合用户习惯。4. 部署、运行与基础优化4.1 本地运行与测试要让tinychat跑起来你需要分别启动后端服务器和提供前端页面。准备环境确保你的Python环境已安装websockets库pip install websockets。启动后端将上面的Python服务器代码保存为server.py在终端运行python server.py你会看到输出Tinychat 服务器已启动在 ws://0.0.0.0:8765。提供前端你需要一个HTTP服务器来提供HTML和JS文件。最简单的方法是使用Python内置的HTTP模块。在与server.py和index.html、tinychat.js同目录下运行python -m http.server 8080这会在本地的8080端口启动一个简单的静态文件服务器。访问测试打开浏览器访问http://localhost:8080如果8080端口被占用命令会提示你也可以换成其他端口如8000。在页面中输入用户名和房间名或使用默认值点击“连接”。打开多个浏览器窗口或标签页分别用不同用户名连接同一个房间就可以互相发送消息了。注意事项这里后端WebSocket服务器运行在8765端口前端HTTP服务器运行在8080端口这是两个不同的端口。前端JavaScript中连接的是ws://localhost:8765。在生产环境中通常会将前后端部署在同一个域名下通过Nginx等反向代理将WebSocket请求/ws路径转发到后端应用服务器以解决跨域问题。本地开发时如果遇到跨域错误可以修改后端服务器代码在创建websockets.serve时添加origins参数允许的来源但这只是开发便捷手段生产环境务必使用反向代理。4.2 基础性能与扩展考量当前的tinychat实现是一个功能完整但非常基础的版本。如果用户量稍大或者对可靠性要求更高有几个方面需要考虑优化和扩展1. 连接状态与心跳机制WebSocket连接可能因为网络不稳定、代理超时、服务器重启等原因意外断开。为了及时发现死连接并进行清理需要实现心跳机制Heartbeat。客户端定期比如每30秒向服务器发送一个特定类型的消息如{type: ping}服务器收到后回复一个pong。如果服务器在超时时间内比如60秒没有收到某个连接的心跳则认为该连接已失效主动关闭并清理它。这可以通过在服务器端为每个连接维护一个“最后活跃时间戳”并启动一个后台定时任务来检查来实现。2. 消息持久化当前所有消息都是内存中的服务器重启或用户重连后历史消息就消失了。对于正式的聊天应用通常需要将消息保存到数据库如Redis、MongoDB或PostgreSQL。当用户加入房间时服务器可以从数据库拉取最近的N条历史消息推送给用户。发送消息时除了广播也要异步写入数据库。这引入了新的复杂度数据库选型、消息模型设计、分页查询等。3. 用户认证与授权目前用户只需输入一个用户名即可加入没有任何认证。在实际场景中可能需要登录系统。一个简单的方案是使用JWTJSON Web Token。用户先通过一个HTTP登录接口获取Token然后在建立WebSocket连接时将Token作为查询参数或首部部分WebSocket库支持传递给服务器服务器验证Token的有效性并获取用户身份信息。这能防止任意用户冒充他人也为后续的权限控制如禁言、踢人打下基础。4. 水平扩展挑战当前的架构是单服务器的。所有连接和房间状态都保存在该服务器的内存中。这意味着单点故障这台服务器挂了整个服务就不可用。容量瓶颈一台机器的连接数、内存和CPU是有限的。 要支持更多用户需要引入多服务器架构。这带来了状态共享的问题用户A连接到服务器1用户B连接到服务器2但他们可能在同一个逻辑房间。服务器1如何将A的消息广播给连接到服务器2的B 解决方案通常有两种使用中心化的消息队列/发布订阅系统如Redis Pub/Sub。每台服务器都订阅一个全局的频道。当服务器1收到用户A的消息时它不直接广播而是将消息发布到Redis的对应房间频道。所有服务器包括服务器1自己都会收到这个消息然后各自广播给自己所维护的该房间的连接。这样状态房间成员列表虽然还是分散在各服务器但消息通过中心枢纽路由了。使用专门的实时通信基础设施如使用Socket.IO的适配器Adapter机制或者直接采用像Apache Kafka、NATS这样的消息系统来在服务器集群间同步事件。对于tinychat这样的学习项目短期内无需考虑这些。但理解这些挑战和常见的解决思路对于构建更严肃的实时应用至关重要。5. 常见问题排查与调试技巧在实际运行和开发tinychat这类应用时你肯定会遇到各种各样的问题。下面我整理了一些典型问题及其排查思路很多都是我自己踩过的坑。5.1 连接建立失败症状前端点击“连接”后一直显示“正在连接...”最终超时或报错。排查步骤检查服务器是否运行在终端运行netstat -an | grep 8765Linux/macOS或netstat -ano | findstr :8765Windows看8765端口是否处于LISTEN状态。检查防火墙确保服务器防火墙如ufw,firewalld或Windows防火墙允许对8765端口的入站连接。本地开发时可以暂时关闭防火墙测试。检查地址和端口前端JavaScript中连接的WebSocket URLws://localhost:8765必须和后端服务器监听的地址端口完全一致。如果服务器运行在虚拟机或容器里localhost可能需要换成具体的IP地址。查看浏览器控制台按F12打开开发者工具切换到“Console”标签页。这里会有详细的WebSocket连接错误信息如WebSocket connection to ws://... failed:。常见的错误包括跨域问题CORS、协议错误如尝试用http://连接ws://端口等。查看服务器日志服务器启动时和连接建立时的打印信息是重要的调试依据。确保你的服务器代码在关键节点如启动成功、新连接建立、连接关闭都有日志输出。5.2 消息发送或接收不到症状连接成功但发送消息后自己或其他用户收不到。排查步骤检查服务器广播逻辑在服务器的广播代码处for client in rooms[room_name]:循环内添加日志打印出发送者、接收者数量和发送的消息内容。确认循环是否执行以及client.send()是否被调用。检查客户端onmessage回调在前端JavaScript的socket.onmessage函数开始处添加console.log(收到原始数据, event.data);确认是否收到了服务器发来的数据。如果没收到问题在服务器端或网络如果收到了但没显示问题在前端的消息解析或UI更新逻辑。检查JSON格式确保客户端发送和服务器广播的消息都是有效的JSON字符串。在服务器解析客户端消息时用try...except捕获json.JSONDecodeError在客户端解析服务器消息时也用try...catch。格式错误是常见原因。检查房间匹配确认发送消息的用户和接收消息的用户是否在同一个房间。在服务器广播时打印房间名和房间内的连接数。在前端检查连接时传入的房间名和发送消息时room字段是否一致。5.3 连接意外断开与重连症状聊天过程中用户突然掉线聊天界面停滞。原因与处理网络波动移动网络或Wi-Fi不稳定会导致TCP连接断开。WebSocket的onclose事件会被触发。服务器重启或崩溃同样会断开所有连接。客户端页面导航或刷新页面跳转会销毁WebSocket对象。浏览器标签页休眠一些浏览器为了省电会暂停非活动标签页的JavaScript执行可能导致心跳超时。解决方案实现自动重连。在前端代码中不要只在用户点击时才连接。在socket.onclose事件处理函数中可以加入重连逻辑socket.onclose function(event) { appendMessage(system, null, 连接断开${reconnectDelay/1000}秒后尝试重连...); messageInput.disabled true; sendButton.disabled true; // 设置一个延时然后尝试重新连接 setTimeout(connectToServer, reconnectDelay); // connectToServer是封装好的连接函数 // 可以指数退避增加重连间隔 reconnectDelay Math.min(reconnectDelay * 1.5, 30000); // 最大间隔30秒 };同时在socket.onerror中也可以触发重连。注意要避免过于频繁的重连通常采用指数退避策略。5.4 性能问题与内存泄漏症状随着运行时间增长或用户数增多服务器内存占用持续上升响应变慢。排查与解决连接泄漏这是最常见的原因。确保在任何连接关闭无论是正常close还是异常ConnectionClosed的情况下都执行了从rooms字典中移除连接对象的操作。检查你的finally块或异常处理块是否百分百覆盖了所有退出路径。大消息或消息风暴如果某个用户发送了非常大的消息如图片Base64或者短时间内发送大量消息可能导致服务器处理线程阻塞或内存激增。可以在服务器端对消息大小进行限制如if len(message) 65536: await websocket.close(1009, “Message too large”)并考虑对发送频率进行限流Rate Limiting。数据结构低效当单个房间人数非常多时比如上千人遍历集合进行广播会成为瓶颈。虽然asyncio.gather是并发发送但准备广播列表遍历房间集合本身是O(N)操作。对于超大规模房间可能需要更高级的数据结构或分发策略比如将用户分组。但对于tinychat的定位这通常不是问题。调试这类实时应用一个非常有效的方法是大量使用日志。在服务器端的连接建立、消息接收、广播开始、广播结束、连接关闭等关键节点都打印日志带上时间戳、连接ID、房间名、用户名等信息。这样当出现问题时你可以像看“电影回放”一样追溯整个系统的状态变化过程快速定位异常点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2596748.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;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…