WebSockets接收中断信号后无法通信的问题及解决咨询
WebSockets中断信号后的通信问题
服务端代码
import websockets async def handler(ws): async for message in ws: print(message) await ws.send('received' + message) server = await websockets.serve(handler, 'localhost', 22235)
客户端代码
import websockets import asyncio import json async def keep_recv(): ws = await websockets.connect('ws://localhost:22235') while True: try: response = await ws.recv() print(f"Received from server: {response}") await asyncio.create_task(asyncio.sleep(10000)) finally: print('cancelled') await ws.send('client cancelled') response = await ws.recv() print(f"Received from server: {response}") break asyncio.run(keep_recv())
预期输出
客户端预期输出
cancelled Received from server: client cancelled
服务端预期输出
client cancelled
实际输出
客户端实际输出
cancelled
随后客户端卡在ws.send操作,一段时间后抛出KeyboardInterrupt错误退出。
服务端实际输出
无即时输出,一段时间后打印错误信息:
websockets.exceptions.ConnectionClosedError: no close frame received or sent
之后服务端继续运行。
核心问题
- WebSockets在程序接收中断信号后是否会停止通信?该如何解决此问题?
- 尝试在客户端用
except Exception捕获异常时,为何不会打印asyncio.CancelledError,而是直接进入finally块?
问题原因与解决方法
问题1:捕获不到CancelledError的原因
asyncio.CancelledError继承自BaseException而非Exception,所以except Exception无法捕获它,会直接进入finally块。
问题2:中断后WebSocket通信失败的原因
按下Ctrl+C时,asyncio.run()会触发KeyboardInterrupt,进而引发任务的CancelledError。此时事件循环处于待关闭状态,finally块中的WebSocket操作(send/recv)会因连接状态异常或事件循环资源不足而阻塞,最终超时报错。
具体解决方案
1. 正确捕获CancelledError并处理WebSocket通信
修改客户端代码,直接捕获CancelledError,并在通信时添加超时和连接状态检查:
import websockets import asyncio async def keep_recv(): ws = await websockets.connect('ws://localhost:22235') while True: try: response = await ws.recv() print(f"Received from server: {response}") await asyncio.sleep(10000) # 无需用create_task,确保sleep能被中断 except asyncio.CancelledError: print("任务被取消") except Exception as e: print(f"其他异常: {e}") finally: print('cancelled') try: # 先检查连接是否仍处于打开状态 if ws.open: await ws.send('client cancelled') # 给recv设置超时,避免无限等待 response = await asyncio.wait_for(ws.recv(), timeout=5) print(f"Received from server: {response}") except (websockets.exceptions.ConnectionClosed, asyncio.TimeoutError): print("通信失败或超时") finally: await ws.close() break asyncio.run(keep_recv())
2. 优雅处理中断信号
通过注册信号处理器,确保收到Ctrl+C时能完成必要通信再退出:
import websockets import asyncio import signal async def keep_recv(): ws = await websockets.connect('ws://localhost:22235') loop = asyncio.get_running_loop() # 注册SIGINT和SIGTERM信号处理 for sig in (signal.SIGINT, signal.SIGTERM): loop.add_signal_handler(sig, lambda: asyncio.create_task(shutdown(ws))) try: while True: response = await ws.recv() print(f"Received from server: {response}") await asyncio.sleep(10000) except asyncio.CancelledError: pass async def shutdown(ws): print('cancelled') try: if ws.open: await ws.send('client cancelled') response = await asyncio.wait_for(ws.recv(), timeout=5) print(f"Received from server: {response}") except (websockets.exceptions.ConnectionClosed, asyncio.TimeoutError): print("通信失败或超时") finally: await ws.close() asyncio.get_running_loop().stop() asyncio.run(keep_recv())
3. 服务端添加异常处理
给服务端补充连接关闭的异常捕获,避免不必要的错误输出:
import websockets import asyncio async def handler(ws): try: async for message in ws: print(message) await ws.send('received' + message) except websockets.exceptions.ConnectionClosed: print("客户端连接已关闭") async def main(): server = await websockets.serve(handler, 'localhost', 22235) await server.wait_closed() asyncio.run(main())
内容的提问来源于stack exchange,提问作者Donny
相关产品推荐
相关产品推荐

