Запрашивать ввод пользователя с использованием экземпляра 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())