Python 3.11 Asyncio Streamer如何检测对等端关闭的Socket?
Python 3.11 Asyncio Streamer 对等端Socket关闭检测异常问题
我在测试Python 3.11 Asyncio Streamer官方文档的TCP回显客户端示例脚本时,遇到了无法检测对等端关闭Socket的问题。
客户端代码如下:
import socket import asyncio async def echo_client(addr,port): r,w = await asyncio.open_connection(addr,port) # send data normally & receive echoed back string w.write("hello".encode()) await w.drain() data = await r.read(100) print("received: {}".format(data)) await asyncio.sleep(1) # tell the server side to close socket connection w.write("exit".encode()) await w.drain() await asyncio.sleep(1) # the underlying socket is already closed at this point, but # streamer won't react unless uncommenting following 2 lines: # w.close() # await w.wait_closed() # write() only send data to kernel, OK I get that res = w.write("hello again".encode()) print(f"write: {res}") # but why drain() neither rise except nor return any error ? res = await w.drain() print(f"drain: {res}") # read() returned immediately with 0 bytes read, as expected data = await r.read(100) print("received: {}".format(data)) asyncio.run(echo_client("127.0.0.1",8000))
服务器端逻辑为:回显收到的内容,若收到以"exit"开头的字符串则直接关闭连接(不回显),因此上述代码中,连接在第17行执行后已被对等端关闭。
核心问题
后续第26行的write()、第29行的drain()和第32行的read()为何既不抛出异常,也不返回任何错误信息?
运行结果如下:
received: b'hello' write: None drain: None received: b''
只有取消注释w.close()和await w.wait_closed()这两行,Streamer才会识别到Socket不可用并抛出异常。
我理解只有与Socket交互才能判断其状态,且write()仅将数据发送到内核缓冲区,但没想到连drain()都不会给出Socket已关闭的有效信号。
我的问题是:除了检查r.read()的返回值外,如何检测Asyncio Streamer的底层Socket是否被对等端关闭?
补充测试发现
经过测试,我成功让read()抛出异常,但触发规则十分怪异。触发异常的最简代码如下:
import asyncio async def echo_client(addr,port): r,w = await asyncio.open_connection(addr,port) w.write("exit".encode()) await w.drain() await asyncio.sleep(1) # socket is closed by peer at this point w.write(b'0') await asyncio.sleep(0.1) w.write(b'0') await asyncio.sleep(0.1) # will rise BrokenPipeError await r.read(100) asyncio.run(echo_client("127.0.0.1",8000))
第16行的read()调用最终会抛出BrokenPipeError,符合预期。总结触发异常的关键规则:
- 必须在Socket被对等端关闭后至少调用两次
write(),每次至少写入1字节数据,Asyncio才会识别到Socket无效。 - 必须给Asyncio时间处理第一次
write()的系统错误信息,第二次write()后才会标记Socket已损坏。 write()永远不会抛出异常,只有read()和drain()会抛出异常(drain()抛出ConnectionResetError)。
这种行为不一致且不尽如人意,让我重新考虑是否继续使用Asyncio方案。如果继续使用,我可能会改用底层的Transports & Protocols模型。
内容的提问来源于stack exchange,提问作者RichardLiu
相关产品推荐
相关产品推荐

