我正在使用一个网络库,该库提供了将其协程函数与asyncio
一起使用的包装器。当我编写一个随机关闭连接的测试(以查看我的程序在恶劣条件下是否具有弹性)时,我发现它会无限期挂起。
这似乎是我所使用的库提供的包装程序中的一个错误,因为程序挂起,等待来自loop.add_reader()
或loop.add_writer()
的回调,但是后来我找不到如何套接字关闭时收到通知。
这是一个最小的程序,它显示我的程序正在发生什么:
import asyncio
import socket
async def kill_later(c):
await asyncio.sleep(0.1)
c.close()
async def main():
loop = asyncio.get_running_loop()
c = socket.create_connection(('www.google.com', 80))
c.setblocking(0)
ev = asyncio.Event()
loop.add_reader(c, ev.set)
# Closes the socket after 0.1 ms:
asyncio.create_task(kill_later(c))
print("waiting...")
#### ↓ THIS WAITS FOREVER ↓ ####
await ev.wait()
asyncio.run(main())
我的问题:如何通过asyncio
循环关闭套接字通知?
编辑:由于流行的需求,使套接字成为非阻塞状态,但是没有什么区别,因为add_reader()
不会尝试对套接字执行任何IO,只是监视它何时准备就绪。 / p>
答案 0 :(得分:1)
您的测试程序有缺陷。调用c.close()
不会模拟套接字被另一端关闭,而是会关闭您自己的文件描述符并使其无法访问。您可以将close(fd)
视为断开数字 fd 与基础操作系统资源之间的链接。在那之后, fd 读和查询变得毫无意义,因为数字不再指任何东西。结果,epoll()
不能也不会将关闭的文件描述符报告为“可读”。
测试要测试条件的方法是使另一端关闭连接。最简单的方法是通过生成另一个进程或线程作为模拟服务器。例如:
import asyncio, threading, socket, time
def start_mock_server():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('localhost', 10000))
s.listen(1)
def serve():
conn, addr = s.accept()
time.sleep(1)
conn.close()
s.close()
threading.Thread(target=serve).start()
async def main():
loop = asyncio.get_running_loop()
start_mock_server()
c = socket.create_connection(('localhost', 10000))
c.setblocking(0)
ev = asyncio.Event()
loop.add_reader(c.fileno(), ev.set)
print("waiting...")
await ev.wait()
print("done")
asyncio.run(main())
答案 1 :(得分:0)
至少在您的示例中,问题在于您的套接字从未收到任何东西,因此该事件从未设置。文档在example中提到:
等待,直到文件描述符使用 loop.add_reader()方法,然后关闭事件循环
为了使您的示例正常工作,您必须先向Google发送请求:
c = socket.create_connection(('www.google.com', 80))
c.sendall("GET /\r\n".encode())
这将设置事件,您稍后await
会在您的main
主持下
epoll
认为如果文件上的数据可用或发出EOF信号,则文件描述符准备就绪,如this answer中所述。
add_writer()
在这种情况下不会阻塞,因为只要输入缓冲区上仍有可用空间,就认为文件描述符已准备就绪。
就像我在答案的上一版本中提到的那样,无需致电recv()
。