代码之家  ›  专栏  ›  技术社区  ›  jsstuball

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

  •  1
  • jsstuball  · 技术社区  · 6 年前

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

    客户:

    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 回复  |  直到 6 年前
        1
  •  1
  •   user4815162342    6 年前

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

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

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

    为什么版本2会卡住

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

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

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