我怎样才能重构接受多个客户?

时间:2018-06-05 11:11:26

标签: python sockets python-asyncio

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

server.py Version1:

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并杀死/重启服务器都很烦人。我不明白为什么第二个版本在第二次尝试连接时不接受客户端。

1 个答案:

答案 0 :(得分:1)

  

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

两个版本都有一个错误,他们没有正确检查文件结束条件。当你中断客户端时,套接字被关闭并从中读取返回EOF,而写入它会引发异常。在版本1中等待writer.drain()会传递异常并中断协程。 (此异常可能显示在服务器的标准错误上。)

版本2有一个问题:if request == "hello"测试在EOF时为false,因为reader.read()不断返回空字节字符串以标记EOF条件。这可以防止await writer.drain()执行和传递异常,因此协程仍然停留在无限循环中。一个简单的解决方法是在if not request: break之后添加read之类的内容。

为什么版本2卡住了

但上述内容并未完全解释为什么在版本2中整个服务器坏了,新客户端无法连接。当然,人们会期望await将结果或控制权返回给其他协同程序。但观察到的行为是,尽管在await循环中包含while,协程也不允许其他协同程序运行!

问题是await并不意味着将控制传递给事件循环",因为它通常被理解。它意味着从提供的等待对象请求值,产生对事件循环的控制如果对象指示它没有准备好的值。" if之后的部分至关重要:如果对象 已准备好值,则此值将立即使用,而不会推迟到事件循环。换句话说,await 并不能保证事件循环有机会运行。

EOF处的流总是有要返回的数据 - 标记EOF的空字符串。因此,它永远不会被挂起,循环最终会完全阻止事件循环。为了保证其他任务有机会运行,您可以在循环中添加await asyncio.sleep(0) - 但这在正确编写的代码中不是必需的,其中请求IO数据很快将导致等待,此时事件循环将启动。一旦纠正了EOF处理错误,服务器将正常运行。