为什么 loop.sock_accept(sock) 会阻塞同一循环中的其他协程?

Why does loop.sock_accept(sock) block other co-oroutines in same loop?

在下面的例子中:

    loop = asyncio.get_event_loop()
    loop.create_task(client())  # Does no start until run_server ends
    await server()
    # await f()

...调用服务器导致客户端被阻塞。 特别是它卡在了对

的调用中
   await loop.sock_accept(sock)

并且在 server() 退出之前,client() 不会启动。 为什么?

用不同的异步函数替换 await server()

    #await server()
    await f()

允许客户端 运行()

Python 3.7 .. 3.10

的行为相同

同样,我们可以翻转哪个作为任务添加,哪个立即等待。任务在以下两个方面都运行失败:

    if 1:
        loop.create_task(server())  # Does no start until server ends
        await client()
    else:
        loop.create_task(client())  # Does no start until client ends
        await server()

完整示例:

import asyncio
import socket

host, port = ('localhost', 15555)
# host, port = ('127.0.0.1', 15555)
ACCEPT_TIMEOUT = 2

async def server():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print("Creating server")
    sock.bind((host, port))
    sock.listen(8)
    sock.setblocking(False)  # sock_accept asks for non-blockin
    sock.settimeout(ACCEPT_TIMEOUT)

    loop = asyncio.get_event_loop()

    while True:
        print("ACCEPTING CONNECTIONS")
        try:
            client, _ = await loop.sock_accept(sock)
        except socket.timeout:
            print("ACCEPT TIMEOUT")
            return

        print("123 5")
        loop.create_task(handle_client(client))
        print("123 6")

async def client():
    print("Sending .. ")
    while True:
        try:
            tcp_reader, tcp_writer = await asyncio.open_connection(host, port)
            break
        except ConnectionRefusedError:
            print("Refused")
            await asyncio.sleep(1)

    print("Sending .. OK")
    msg = b"Message"
    while True:
        tcp_writer.write(msg)
        tcp_writer.drain()
        r = tcp_reader.read()
        assert r == msg

async def handle_client(client):  # Never reached
    print("Handle client...")
    loop = asyncio.get_event_loop()
    request = None
    while request != 'quit':
        request = (await loop.sock_recv(client, 255))
        await loop.sock_sendall(client, request)
    client.close()

async def f():
    print("f..")
    asyncio.sleep(1)
    print("f..done")

async def main():
    loop = asyncio.get_event_loop()

    if 1:
        loop.create_task(client())  # Does no start until client ends
        await server()
        # await f()
    else:
        loop.create_task(server())  # Does no start until server ends
        await client()
    
if __name__=='__main__':
    asyncio.run(main())

似乎 Linux sock.settimeout() 否决了 sock.setblocking(false)

使您的代码对我有用的一些更改:

  • 删除 sock.settimeout(...) 并改为使用 asyncio 的超时功能
  • 使用上下文管理器正确关闭套接字,以避免留下未关闭的套接字
  • 添加REUSEADDR标志,避免套接字在运行示例重复
  • 时卡在TIME_WAIT状态
async def server():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        print("Creating server")
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind((host, port))
        sock.listen(8)
        sock.setblocking(False)  # sock_accept asks for non-blockin

        loop = asyncio.get_event_loop()

        while True:
            print("ACCEPTING CONNECTIONS")
            try:
                client, _ = await asyncio.wait_for(loop.sock_accept(sock), timeout=ACCEPT_TIMEOUT)
            except asyncio.TimeoutError:
                print("ACCEPT TIMEOUT")
                return

            print("123 5")
            loop.create_task(handle_client(client))
            print("123 6")