我如何重构以接受多个客户?

How can I refactor to accept multiple clients?

我不明白为什么 server.py 版本 1 允许客户端被键盘中断并重新启动,而 server.py 版本 2 却不允许:

server.py 版本 1:

import asyncio

async def handle_client(reader, writer):
    while True:
        request = (await reader.read(128)).decode()
        writer.write('Received ok.'.encode())
        await writer.drain()

async def main():
    loop.create_task(asyncio.start_server(handle_client, 'localhost', 15555))

loop = asyncio.new_event_loop()
loop.create_task(main())
loop.run_forever()

server.py 版本 2:

import asyncio

async def handle_client(reader, writer):
    while True:
        request = (await reader.read(128)).decode()
        if request == "hello":
            writer.write('Received ok.'.encode())
            await writer.drain()

async def main():
    loop.create_task(asyncio.start_server(handle_client, 'localhost', 15555))

loop = asyncio.new_event_loop()
loop.create_task(main())
loop.run_forever()

client.py:

import asyncio

async def make_connections():
    reader, writer = await asyncio.open_connection('localhost', 15555, loop=loop)
    loop.create_task(connect(reader, writer))


async def connect(reader, writer):
    writer.write("hello".encode())
    await writer.drain()
    result = await reader.read(128)
    print(result.decode())


loop = asyncio.new_event_loop()
loop.create_task(make_connections())
loop.run_forever()

版本 2 适用于单个客户端,但如果我向客户端发送键盘中断,则在重新启动客户端后我将无法再连接。每次我在客户端中更改代码时,ssh 和 kill/restart 服务器都很烦人。我不明白为什么第二个版本在第二次尝试连接时不接受客户端。

I don't understand why server.py Version 1 allows a client to be keyboard-interrupted and restarted, while server.py Version 2 doesn't

这两个版本都有一个错误,它们不能正确检查文件结束条件。当您中断客户端时,套接字将关闭并从中读取 returns EOF,而写入它会引发异常。版本 1 中的 Awaiting writer.drain() 传递异常并中断协程。 (这个异常应该是显示在服务器的标准错误上。)

但是,

版本 2 有一个问题:if request == "hello" 测试在 EOF 处为假,因为 reader.read() 保持 returning 一个空字节字符串来标记 EOF 条件。这会阻止 await writer.drain() 执行和传递异常,因此协程仍然陷入无限循环。一个简单的解决方法是在 read.

之后添加类似 if not request: break 的内容

为什么版本 2 卡住了

但是以上并没有完全解释为什么在版本2中整个服务器都坏了,新客户端无法连接。当然,人们会期望 await 要么 return 一个结果,要么将控制权交给其他协程。但是观察到的行为是,尽管在 while 循环中包含一个 await,协程不允许其他协程 运行!

问题是 await 并不像人们通常理解的那样 "pass control to the event loop"。这意味着 "request value from the provided awaitable object, yielding control to the event loop if the object indicates that it does not have a value ready." if 之后的部分是至关重要的:如果对象 确实 有一个值准备好,这个值将立即使用而不会延迟到事件环形。换句话说,await 不保证事件循环有机会 运行.

EOF 处的流总是有数据到 return - 标记 EOF 的空字符串。结果,它永远不会被挂起,并且循环最终完全阻塞了事件循环。为了保证其他任务有机会 运行,您可以在循环中添加 await asyncio.sleep(0) - 但在正确编写的代码中这不是必需的,在这种情况下请求 IO 数据很快就会导致等待,在事件循环将在哪一点开始。一旦 EOF 处理错误得到纠正,服务器将正常运行。