如何等待错误/关闭事件与异步套接字?

时间:2019-05-29 19:25:29

标签: python python-asyncio python-3.7

我正在使用一个网络库,该库提供了将其协程函数与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>

2 个答案:

答案 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()