升级至Python3.12后异步服务器与客户端脚本停止工作的问题
问题描述
我有两个基于asyncio的通信脚本,工作流程是:服务器启动监听连接,客户端连接后,服务器停止接受新连接,同时保留reader和writer用于后续数据收发。
在Python 3.11.9中运行正常:客户端连接后,服务器触发asyncio.CancelledError退出监听逻辑,reader和writer保持可用。但升级到Python 3.12.6后,服务器在调用handle_connection时卡在await server.wait_closed()语句,只有关闭客户端writer后该语句才会返回,无法保留连接进行后续通信。
Server.py原代码:
import asyncio import sys class Server: def __init__(self): self.reader, self.writer = None, None self.connected = False async def listen(self, ip: str, port: int) -> None: """ Listens for incoming connections and handles the first connection. After accepting the first connection, it stops the server from accepting further connections. :param ip: IP address to listen on. :param port: Port number to listen on. """ async def handle_connection(reader, writer): print("Client connected!") # Assign the reader and writer to instance variables for later use self.reader, self.writer = reader, writer self.connected = True print("Shutting down server from accepting new connections") server.close() await server.wait_closed() print(f"Listening on {ip}:{port}") server = await asyncio.start_server(handle_connection, ip, port) try: async with server: await server.serve_forever() except KeyboardInterrupt: sys.exit(1) except asyncio.CancelledError: print("Connection canceled") except Exception as e: print(f"Unexpected error while trying to listen, Error: {e}") sys.exit(1) if __name__ == '__main__': server = Server() asyncio.run(server.listen('192.168.0.35', 9090))
Client.py原代码:
import asyncio class Client: def __init__(self): self.reader, self.writer = None, None self.connected = False async def connect(self, ip: str, port: int) -> None: """ Connects to a server at the specified IP address and port. :param ip: IP address of the server. :param port: Port number of the server. """ while not self.connected: try: self.reader, self.writer = await asyncio.wait_for( asyncio.open_connection(ip, port), 5 ) print(f"Connecting to {ip}:{port}") self.connected = True break except Exception as e: print( f"Failed to connect to {ip}:{port} retrying in 10 seconds." ) print(e) await asyncio.sleep(10) continue if __name__ == '__main__': Client = Client() asyncio.run(Client.connect('192.168.0.35', 9090))
原因分析
Python 3.12对asyncio.Server的wait_closed()方法行为做了调整:该方法现在会等待所有已建立的客户端连接关闭后才返回,而Python 3.11中仅等待服务器监听套接字关闭即可返回。原代码中在handle_connection里调用await server.wait_closed(),会导致服务器一直阻塞,直到客户端连接被关闭,与保留连接的需求冲突。
解决方案
核心思路是:仅停止服务器接受新连接,不等待现有连接关闭,同时让监听逻辑退出,进入后续通信流程。
具体修改点:
- 移除
await server.wait_closed()调用,仅保留server.close()来停止接受新连接。 - 通过主动取消
serve_forever()的任务,让监听逻辑退出,避免依赖CancelledError的被动触发。
修改后的Server.py代码
import asyncio import sys class Server: def __init__(self): self.reader, self.writer = None, None self.connected = False async def listen(self, ip: str, port: int) -> None: """ Listens for incoming connections and handles the first connection. After accepting the first connection, it stops the server from accepting further connections. :param ip: IP address to listen on. :param port: Port number to listen on. """ async def handle_connection(reader, writer): print("Client connected!") # Assign the reader and writer to instance variables for later use self.reader, self.writer = reader, writer self.connected = True print("Shutting down server from accepting new connections") server.close() # 主动取消serve_forever任务,让监听逻辑退出 serve_task.cancel() print(f"Listening on {ip}:{port}") server = await asyncio.start_server(handle_connection, ip, port) serve_task = asyncio.create_task(server.serve_forever()) try: await serve_task except asyncio.CancelledError: print("Server stopped accepting new connections") except KeyboardInterrupt: sys.exit(1) except Exception as e: print(f"Unexpected error while trying to listen, Error: {e}") sys.exit(1) # 这里添加后续与客户端的通信逻辑 if self.connected: print("Starting communication with client...") # 示例:接收客户端消息 data = await self.reader.read(100) message = data.decode() print(f"Received: {message!r}") # 示例:发送响应 response = "Hello from server!" self.writer.write(response.encode()) await self.writer.drain() # 按需关闭连接 self.writer.close() await self.writer.wait_closed() if __name__ == '__main__': server = Server() asyncio.run(server.listen('192.168.0.35', 9090))
说明
server.close()只会停止服务器监听新连接,已建立的客户端连接完全不受影响,可正常进行数据收发。- 主动取消
serve_forever()任务,能让监听逻辑平稳退出,进入后续的通信处理流程。 - 后续通信逻辑可根据需求自由扩展,无需担心服务器监听逻辑的阻塞问题。
内容的提问来源于stack exchange,提问作者LuckyCoder3607
相关产品推荐
相关产品推荐

