You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:33:38