python - 如何重构以接受多个客户端?

标签 python sockets python-asyncio

我不明白为什么 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()

客户端.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 登录并终止/重新启动服务器,这很烦人。我不明白为什么第二个版本在第二次尝试连接时不接受客户端。

最佳答案

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

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

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

为什么版本 2 会卡住

但是上面并没有完全解释为什么在版本 2 中整个服务器崩溃并且新客户端无法连接。人们肯定会期望 await 返回结果或将控制权交给其他协程。但观察到的行为是,尽管在 while 循环中包含 await,协程不允许其他协程运行!

问题是 await 并不意味着“将控制权传递给事件循环”,正如人们通常理解的那样。它的意思是“从提供的可等待对象请求值,如果对象指示它没有准备好值,则将控制权交给事件循环。” if 之后的部分至关重要:如果对象确实有一个准备好的值,则该值将立即使用,而不会推迟到事件循环。换句话说,await 不能保证事件循环有机会运行。

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

关于python - 如何重构以接受多个客户端?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50698649/

相关文章:

php - php socket I/O 函数的区别

python-3.5 - python 3 asyncio : coroutines execution order using run_until_complete(asyncio. 等待(corutines_list))

python-3.x - 与协程的双向通信(asyncio 中的状态机)

python - 有没有比 PIL.ImageGrab.grab() 更好的截屏方式?

python - 将 fixture 传递给 PyTest 中的辅助函数?

python - 按特定键的降序对字典列表进行排序

python - 从私有(private) pypiserver 安装 python 包

C# Stream.Read 超时

javascript - net::ERR_NETWORK_CHANGED 在 Chrome 上的 Https 套接字连接

Python 请求流异步