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




