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

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

之后服务端继续运行。

核心问题

  1. WebSockets在程序接收中断信号后是否会停止通信?该如何解决此问题?
  2. 尝试在客户端用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 05:43:20