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

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

Я думал, что это будет так же просто, как просто добавить вызов input(), но, конечно, 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 на стороне сервера и указать, к какому соединению его отправлять, пока еще поступают входные данные от подключенных клиентов?

2 ответа

Решение

Ты можешь использовать loop.add_reader запланировать обратный вызов, когда данные будут доступны на sys.stdin, а затем использовать asyncio.Queue передать данные стандартного ввода в ваш 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() вызов.

Код в других ответах хорош. кроме того, приходит python3.7+, я думаю, это могло бы быть проще:

         class AInput():
      def __init__(self):
          self.q = asyncio.Queue()

      async def out(self, prompt='input:'):
          print(prompt)
          await self.q.put(sys.stdin.readline().strip())

      async def input(self):
          tasks = [self.out(), self.q.get()]
          res = await asyncio.gather(*tasks)
          return res[1]


  async def demo():
      ain = AInput()
      txt = await ain.input()
      print(f"got: {txt}")

  if __name__ == "__main__":
      asyncio.run(demo())
Другие вопросы по тегам