Запрос на ввод пользователя с использованием экземпляра asyncio.create_server python

Я изучаю библиотеку python 3 asyncio, и я столкнулся с небольшой проблемой. Я пытаюсь адаптировать пример EchoServer из документов python, чтобы запрашивать ввод пользователя, а не просто отсылать то, что отправляет клиент.

Я думал, что это будет так же просто, как просто добавить вызов для ввода(), но, конечно, input() будет блокироваться до тех пор, пока не будет введен пользовательский ввод, который вызывает проблемы.

В идеале я хотел бы продолжать получать данные от клиента, даже если серверу нечего "сказать". Скорее как чат-клиент, где каждое соединение общается с сервером. Я хотел бы иметь возможность переключаться между отдельными соединениями и отправлять их по отдельности и отправлять их по мере необходимости из stdin. Почти как клиент чата 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 на стороне сервера и указать, какое соединение будет отправлять, пока все еще принимаются входы от подключенных клиентов?

Ответ 1

Вы можете использовать loop.add_reader расписание обратного вызова для запуска, когда данные доступны на sys.stdin, а затем используйте asyncio.Queue передать данные stdin, полученные вашему методу data_received:

import sys
import asyncio


def got_stdin_data(q):
    asyncio.async(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.async(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; оба из них являются сопрограммами, которые нельзя вызвать с помощью yield from в методах обратного вызова или Protocol. Вместо этого мы просто планируем их с циклом событий, используя asyncio.async, а затем используем метод add_done_callback для обработки ответа, который мы получаем из вызова get().