You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Aiohttp服务端遇客户端突然断开时出现致命错误求助

这个问题我之前在Windows环境下用旧版本aiohttp和Python时也碰到过,本质是Windows IOCP Proactor异步模型的一个已知bug——当客户端大量连接后突然强制断开,底层的accept操作抛出的WinError 64异常没有被asyncio或aiohttp的默认逻辑捕获,最终导致整个事件循环崩溃。

下面给你两种解决方案,从简单到进阶:

方案1:升级依赖(最推荐)

这个问题在后续的Python和aiohttp版本中已经被修复了:

  • Python 3.9+ 对Windows IOCP的异常处理做了针对性优化
  • aiohttp 3.7+ 也修复了相关的底层兼容问题

直接升级你的Python到3.9或更高版本,同时把aiohttp升级到最新稳定版(比如3.8.x以上),大概率就能彻底解决这个问题,不需要修改代码。

方案2:手动捕获底层异常(无法升级时)

如果因为环境限制不能升级依赖,我们可以给asyncio事件循环添加全局异常处理器,过滤掉这个预期内的客户端断开错误,避免服务崩溃;或者自定义服务启动逻辑,在异常发生后自动重启服务。

代码示例1:添加全局异常处理器

修改你的服务端代码如下,让事件循环忽略WinError64的错误:

from aiohttp import web
import asyncio

async def page(request):
    try:
        return web.Response(text='OK')
    except Exception as ex:
        print('Exception in request handler:', ex)

def handle_loop_exception(loop, context):
    # 提取异常信息
    exception = context.get('exception')
    # 过滤掉WinError 64的客户端断开错误
    if isinstance(exception, OSError) and getattr(exception, 'winerror', None) == 64:
        print(f"Ignoring expected client disconnect error: {exception}")
        return
    # 其他未处理异常正常打印日志
    print(f"Unhandled loop exception: {context}")

async def run_server():
    app = web.Application()
    app.router.add_route('*', '/', page)
    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, '0.0.0.0', 80)
    await site.start()
    print("Server started on port 80")
    # 保持服务运行,直到收到中断信号
    await asyncio.Event().wait()

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    # 设置全局异常处理器
    loop.set_exception_handler(handle_loop_exception)
    try:
        loop.run_until_complete(run_server())
    except KeyboardInterrupt:
        print("Server stopped by user")
    finally:
        loop.close()

代码示例2:异常自动重启服务

如果担心还有其他未知异常导致服务崩溃,可以在外层加一个重启循环,确保服务意外退出后自动恢复:

from aiohttp import web
import asyncio

async def page(request):
    try:
        return web.Response(text='OK')
    except Exception as ex:
        print('Exception in request handler:', ex)

def handle_loop_exception(loop, context):
    exception = context.get('exception')
    if isinstance(exception, OSError) and getattr(exception, 'winerror', None) == 64:
        print(f"Ignoring expected client disconnect error: {exception}")
        return
    print(f"Unhandled loop exception: {context}")

async def run_server():
    app = web.Application()
    app.router.add_route('*', '/', page)
    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, '0.0.0.0', 80)
    await site.start()
    print("Server started on port 80")
    await asyncio.Event().wait()

if __name__ == "__main__":
    while True:
        try:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            loop.set_exception_handler(handle_loop_exception)
            loop.run_until_complete(run_server())
        except (KeyboardInterrupt, SystemExit):
            print("Server exiting...")
            break
        except Exception as ex:
            print(f"Server crashed with error: {ex}, restarting in 2 seconds...")
            loop.close()
            asyncio.run(asyncio.sleep(2))

为什么原来的try-except没用?

你之前在page函数里加的try-except只能捕获请求处理阶段的异常,而这个WinError 64是发生在asyncio底层的accept协程中(也就是服务端等待新连接的阶段),属于事件循环的未处理task异常,所以必须在事件循环层面去捕获处理。

内容的提问来源于stack exchange,提问作者Dr3d

火山引擎 最新活动