首页 > 解决方案 > Asyncios await reader.read() 一直在等待

问题描述

我有一个这样写的小型服务器:

async def handle_client(reader, writer):
    request = (await reader.read()).decode('utf8') # should read until end of msg
    print(request)
    response = "thx"
    writer.write(response.encode('utf8'))
    await writer.drain()
    writer.close()


loop = asyncio.get_event_loop()
loop.create_task(asyncio.start_server(handle_client, socket.gethostname(), 8886))
loop.run_forever()

和一个这样写的小客户:

async def tcp_echo_client(message):
    reader, writer = await asyncio.open_connection(
        my_ip, 8886)

    print(f'Send: {message!r}')
    writer.write(message.encode())
    await writer.drain()
    data = await reader.read() # should read until end of msg
    print(f'Received: {data.decode()!r}')
    print('Close the connection')
    writer.close()
    await writer.wait_closed()

asyncio.run(tcp_echo_client(f"Hello World!"))

客户端和服务器开始通信,但从未完成。为什么读者无法识别味精的结尾?

如果我写

request = (await reader.read(1024)).decode('utf8')

相反,它可以工作,但我需要接收未定义的大量数据。

我试图像这样修改服务器的代码:

    while True:
        request = (await reader.read(1024)).decode('utf8')
        if not request:
            break

它接收所有数据块,但在最后一个块之后仍然永远等待。为什么?我如何告诉服务器的读者停止监听并继续在代码中发送答案?

标签: python-3.xsocketspython-asyncio

解决方案


TCP 连接是基于流的,这意味着当您将“消息”写入套接字时,字节将被发送到对等方,而不包括消息之间的分隔符。连接另一端的对等方可以检索字节,但它需要自己弄清楚如何将它们切成“消息”。这就是读取最后一个块似乎挂起的原因:read()只是等待对等方发送更多数据。

为了能够检索单个消息,发送者必须对每条消息进行框架或定界。例如,发送方可以在发送消息后关闭连接,这将允许另一方读取消息,因为它后面会跟着文件结束指示符。但是,这将允许发件人仅发送一条消息而无法读取响应,因为套接字将被关闭。

更好的选择是编写者只关闭套接字的写入端,这种部分关闭有时被称为shutdown)。在 asyncio 中,这是通过调用write_eof

writer.write(message.encode())
await writer.drain()
writer.write_eof()

像这样发送,消息后面会跟着文件结尾,服务器端的读取不会挂起。虽然客户端将能够读取响应,但仍将仅限于发送一条消息,因为在写入端已关闭的套接字上将无法进行进一步的写入。

要实现由任意数量的请求和响应组成的通信,您需要对每条消息进行框架化。一种简单的方法是在每条消息前加上消息长度:

writer.write(struct.pack('<L', len(request)))
writer.write(request)

接收方首先读取消息大小,然后读取消息本身:

size, = struct.unpack('<L', await reader.readexactly(4))
request = await reader.readexactly(size)

推荐阅读