使用python asyncio.create_server实例提示用户输入

2022-03-25 00:00:00 python python-asyncio

问题描述

我正在学习有关python3asyncio库的知识,我遇到了一个小问题。我正在尝试修改Python文档中的EchoServer示例,以提示用户输入,而不仅仅是回显客户端发送的内容。

我以为这和添加对input()的调用一样简单,但是input()当然会挡路,直到出现导致问题的用户输入。

理想情况下,即使服务器没有什么可"说"的时候,我也希望继续从客户端接收数据。有点像聊天客户端,每个连接都在与服务器聊天。我希望能够在每个单独的连接之间来回切换,并根据需要从标准输入中发送输入。几乎就像P2P聊天客户端。

考虑以下修改后的EchoServer代码:

import asyncio

class EchoServerClientProtocol(asyncio.Protocol):
    def connection_made(self, transport):
        peername = transport.get_extra_info('peername')
        print('Connection from {}'.format(peername))
        self.transport = transport

    def data_received(self, data):
        message = data.decode()
        print('Data received: {!r}'.format(message))

        reply = input()
        print('Send: {!r}'.format(reply))
        self.transport.write(reply.encode())

        #print('Close the client socket')
        #self.transport.close()

loop = asyncio.get_event_loop()
# Each client connection will create a new protocol instance
coro = loop.create_server(EchoServerClientProtocol, '127.0.0.1', 8888)
server = loop.run_until_complete(coro)

# Serve requests until CTRL+c is pressed
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()

如何在服务器端获取输入表单stdin并指定在仍从连接的客户端接收输入的同时将其发送到哪个连接?


解决方案

您可以使用loop.add_reader安排一个回调在sys.stdin上可用时运行,然后使用asyncio.Queue将接收到的标准输入数据传递给您的data_received方法:

import sys
import asyncio


def got_stdin_data(q):
    asyncio.ensure_future(q.put(sys.stdin.readline()))

class EchoServerClientProtocol(asyncio.Protocol):
   def connection_made(self, transport):
       peername = transport.get_extra_info('peername')
       print('Connection from {}'.format(peername))
       self.transport = transport

   def data_received(self, data):
       message = data.decode()
       print('Data received: {!r}'.format(message))
       fut = asyncio.ensure_future(q.get())
       fut.add_done_callback(self.write_reply)

   def write_reply(self, fut):
       reply = fut.result()
       print('Send: {!r}'.format(reply))
       self.transport.write(reply.encode())

       #print('Close the client socket')
       #self.transport.close()

q = asyncio.Queue()
loop = asyncio.get_event_loop()
loop.add_reader(sys.stdin, got_stdin_data, q)
# Each client connection will create a new protocol instance
coro = loop.create_server(EchoServerClientProtocol, '127.0.0.1', 8888)
server = loop.run_until_complete(coro)

# Serve requests until CTRL+c is pressed
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
唯一棘手的是我们如何调用Queue.put/Queue.get方法;它们都是协程,不能在回调或Protocol实例方法中使用yield from调用。相反,我们只使用asyncio.ensure_future使用事件循环来调度它们,然后使用add_done_callback方法来处理我们从get()调用中检索到的回复。

注意:asyncio.ensure_future是在Python3.4.4中引入的。在此之前,该方法称为asyncio.async。此外,Python3.7引入了asyncio.create_task,这是现在首选的方法。

相关文章