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

升级至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(),会导致服务器一直阻塞,直到客户端连接被关闭,与保留连接的需求冲突。

解决方案

核心思路是:仅停止服务器接受新连接,不等待现有连接关闭,同时让监听逻辑退出,进入后续通信流程。

具体修改点:

  1. 移除await server.wait_closed()调用,仅保留server.close()来停止接受新连接。
  2. 通过主动取消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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 06:05:20