Python aiohttp服务器如何检测客户端异常退出时的WebSocket关闭
嘿,这个问题我之前也碰到过!aiohttp的WebSocket在客户端异常退出(比如直接杀进程、断网)时,不会主动触发“正常关闭”的消息,所以服务器得靠两种方式来检测:一是捕获连接断开的异常,二是主动发心跳包确认状态。我给你一步步拆解解决方案:
1. 捕获WebSocket断开异常,自动清理连接
首先,你需要在处理WebSocket连接的协程里,用try/except/finally块来捕获客户端断开的异常——不管是正常退出还是异常崩溃,都会触发对应的错误(比如aiohttp.WebSocketDisconnectedError)。然后在finally里把这个客户端从连接列表里移除,确保后续不会再向它发消息。
举个代码例子,假设你用一个集合clients来存所有在线客户端:
import aiohttp from aiohttp import web import asyncio # 存储在线客户端:(客户端标识, WebSocket连接) clients = set() async def websocket_handler(request): ws = web.WebSocketResponse() await ws.prepare(request) # 用客户端的IP作为标识,你也可以改成自定义的client ID client_id = request.remote clients.add((client_id, ws)) print(f"Client {client_id} connected") try: # 监听客户端发来的消息 async for msg in ws: if msg.type == web.WSMsgType.TEXT: # 这里处理客户端的消息,比如update_client.py发的状态 print(f"Received from {client_id}: {msg.data}") elif msg.type == web.WSMsgType.ERROR: print(f"WebSocket error from {client_id}: {ws.exception()}") # 捕获异常断开的情况:比如客户端直接崩溃、断网 except aiohttp.WebSocketDisconnectedError: print(f"Client {client_id} disconnected unexpectedly") # 捕获协程被取消的情况(比如服务器重启) except asyncio.CancelledError: print(f"Connection to {client_id} was cancelled") finally: # 不管是正常还是异常退出,都从集合里移除客户端 clients.discard((client_id, ws)) print(f"Client {client_id} removed from active list") return ws
2. 加心跳机制,主动检测无响应客户端
有些极端情况(比如客户端突然断电、网络彻底中断),服务器可能很久都收不到断开的异常。这时候就得主动发心跳包,定期确认客户端是否还在线。
你可以在服务器启动时加一个定时任务,每隔一段时间给所有在线客户端发PING消息,如果发送失败,就把这个客户端标记为离线并清理:
async def heartbeat_task(): # 每10秒发一次心跳,时间可以自己调 while True: await asyncio.sleep(10) # 收集需要移除的失效连接 dead_clients = [] for client_id, ws in clients: try: # 发心跳包,这里用TEXT类型的PING,也可以用专门的PING帧 await ws.send_str("PING") # 如果你需要确认客户端响应,可以加个超时等待PONG,不过简单场景下发了就行 except (aiohttp.WebSocketDisconnectedError, asyncio.TimeoutError): dead_clients.append((client_id, ws)) # 清理失效连接 for client in dead_clients: clients.discard(client) print(f"Removed unresponsive client {client[0]}")
然后在服务器启动函数里注册这个任务:
async def main(): app = web.Application() # 注册WebSocket路由和客户端列表路由 app.add_routes([ web.get('/ws', websocket_handler), web.get('/', lambda r: web.json_response([cid for cid, _ in clients])) ]) # 启动心跳任务 app.on_startup.append(lambda app: asyncio.create_task(heartbeat_task())) runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, '0.0.0.0', 8080) await site.start() print("Server running at http://0.0.0.0:8080") # 让服务器一直运行 await asyncio.Event().wait() if __name__ == "__main__": asyncio.run(main())
3. 发送命令时加容错处理
最后,在update_commander.py调用服务器发命令的地方,也要加异常捕获——万一在发消息的时候才发现连接已经断了,直接清理掉这个客户端,避免后续报错:
async def send_command(request): # 假设从请求里拿到client_id和command client_id = request.query.get('client_id') command = request.query.get('command') for cid, ws in clients: if cid == client_id: try: await ws.send_str(command) return web.json_response({"status": "success", "message": f"Sent command to {client_id}"}) except aiohttp.WebSocketDisconnectedError: clients.discard((cid, ws)) return web.json_response({"status": "error", "message": f"Client {client_id} is disconnected"}, status=400) return web.json_response({"status": "error", "message": f"Client {client_id} not found"}, status=404)
这样一套组合拳下来,不管客户端是正常退出还是异常崩溃,服务器都能及时检测到并清理连接,后续发命令就不会出错啦!
内容的提问来源于stack exchange,提问作者Tim Hughes
相关产品推荐
相关产品推荐

