2015-03-16 11 views
13

Sto imparando sulla libreria asyncio di python 3 e ho riscontrato un piccolo problema. Sto cercando di adattare l'esempio EchoServer dai documenti python per richiedere l'input dell'utente piuttosto che solo quello che il client invia.Richiesta di input utente utilizzando l'istanza di python asyncio.create_server

Ho pensato che sarebbe stato semplice come aggiungere una chiamata a input(), ma ovviamente input() verrà bloccato fino a quando non viene immesso dall'utente che causa i problemi.

Idealmente mi piacerebbe continuare a ricevere dati dal client anche quando il server non ha nulla da "dire". Un po 'come un client di chat in cui ogni connessione sta chattando con il server. Mi piacerebbe essere in grado di passare a-e-da ogni singola connessione e inviare l'input secondo necessità da stdin. Quasi come un client di chat P2P.

Si consideri il seguente codice EchoServer modificato:

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() 

Come potrei fare per ottenere modulo di input stdin sul lato server e specificare quale connessione ad inviarlo a mentre ancora ricevuto input da parte dei clienti collegati?

risposta

9

È possibile utilizzare loop.add_reader programma un callback da eseguire quando sono disponibili informazioni sul sys.stdin, e quindi utilizzare un asyncio.Queue per passare i dati stdin ricevuti al data_received metodo:

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() 

L'unica parte difficile è il modo in cui chiamare i metodi Queue.put/Queue.get; entrambi sono coroutine, che non possono essere chiamati usando yield from nel callback o nei metodi di istanza Protocol. Invece, li pianifichiamo semplicemente con il ciclo degli eventi utilizzando asyncio.async e quindi utilizziamo il metodo add_done_callback per gestire la risposta che recuperiamo dalla chiamata get().

+0

Avevo il sospetto che dovevo usare una coda come questa, ma non ero sicuro di come implementare. Grazie per l'ottima risposta! Funziona bene. – RG5

Problemi correlati