Python3.6+asyncio Windows10简易服务器无法读取数据排查
我之前也碰到过一模一样的问题——Windows 10上用Python 3.6+asyncio搭的简易服务器,死活读不到nc发过来的数据,但用原生socket写的同功能服务器却完全正常。试了基于协议和协程API的两种asyncio实现都不行?别慌,这大概率和Windows下asyncio的事件循环限制、数据接收逻辑,或者Python 3.6的版本特性有关,我给你一步步拆解解决方案:
1. 先解决Windows asyncio的核心限制
Python 3.6在Windows上默认用的是SelectorEventLoop,这个事件循环是基于select模型模拟的,对TCP套接字的某些IO场景支持很拉胯。换成Windows原生的ProactorEventLoop(基于IOCP完成端口),很多问题直接就消失了。
怎么改?
在你的代码开头显式设置事件循环策略:
import asyncio # 针对Windows切换到Proactor事件循环 if asyncio.get_event_loop_policy().get_name() == 'WindowsSelectorEventLoopPolicy': asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
2. 协程版服务器的接收逻辑要避坑
很多人踩坑是因为用reader.readline()但nc发的数据没带换行符,导致一直阻塞;或者用reader.read()不指定长度,等着EOF但nc没断开连接,也会卡住。
给你一个能正常工作的协程版示例:
import asyncio async def handle_client(reader, writer): try: # 读取最多1024字节,设置5秒超时防止无限阻塞 data = await asyncio.wait_for(reader.read(1024), timeout=5.0) message = data.decode('utf-8') addr = writer.get_extra_info('peername') print(f"收到来自 {addr} 的数据: {message!r}") # 回显数据给客户端 writer.write(data) await writer.drain() print(f"已回显数据给 {addr}") except asyncio.TimeoutError: print("读取客户端数据超时") finally: # 确保关闭连接 writer.close() await writer.wait_closed() async def main(): # 先处理Windows事件循环问题 if asyncio.get_event_loop_policy().get_name() == 'WindowsSelectorEventLoopPolicy': asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) server = await asyncio.start_server(handle_client, '127.0.0.1', 8888) addr = server.sockets[0].getsockname() print(f"服务器启动在 {addr}") async with server: await server.serve_forever() if __name__ == '__main__': asyncio.run(main())
3. 基于协议的服务器要注意数据拼接
用asyncio.Protocol实现时,data_received可能会分批收到数据(比如大一点的数据包),不过你用nc测试的话一般不会有这个问题,但要确保回调逻辑正确处理每一次收到的数据。
正常工作的协议版示例:
import asyncio class EchoProtocol(asyncio.Protocol): def connection_made(self, transport): self.transport = transport self.addr = transport.get_extra_info('peername') print(f"客户端 {self.addr} 已连接") def data_received(self, data): message = data.decode('utf-8') print(f"收到来自 {self.addr} 的数据: {message!r}") # 直接回显数据 self.transport.write(data) def connection_lost(self, exc): if exc: print(f"客户端 {self.addr} 断开连接,原因: {exc}") else: print(f"客户端 {self.addr} 正常断开连接") async def main(): # 切换到Proactor事件循环 if asyncio.get_event_loop_policy().get_name() == 'WindowsSelectorEventLoopPolicy': asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) loop = asyncio.get_running_loop() server = await loop.create_server(EchoProtocol, '127.0.0.1', 8888) addr = server.sockets[0].getsockname() print(f"服务器启动在 {addr}") async with server: await server.serve_forever() if __name__ == '__main__': asyncio.run(main())
4. 确认你的nc测试方式没问题
Windows下的nc(比如Git Bash里的或者Nmap附带的)要确保数据确实发出去了:
- 交互式测试:运行
nc 127.0.0.1 8888,输入内容后按回车发送 - 批量测试:用
echo "测试数据" | nc 127.0.0.1 8888直接管道发送
为什么原生socket能正常工作?
原生socket直接调用Windows的套接字API,而Python 3.6默认的SelectorEventLoop是用select模型模拟的多路复用,在Windows下对TCP套接字的支持有缺陷。换成ProactorEventLoop后,asyncio会直接用Windows的IOCP机制,和原生socket的IO处理逻辑一致,自然就兼容了。
内容的提问来源于stack exchange,提问作者lgwest

