web-dev-qa-db-ja.com

python asyncio.create_serverインスタンスを使用してユーザー入力を求める

python 3 asyncioライブラリについて学習していて、小さな問題が発生しました。python)からEchoServerの例を適応させようとしています。クライアントが送信するものを単にエコーするのではなく、ユーザー入力を求めるためのドキュメント。

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から入力フォームを取得し、接続されたクライアントから入力を受信したまま送信する接続を指定するにはどうすればよいですか?

19
RG5

_loop.add_reader_ データが_sys.stdin_で利用可能になったときに実行するコールバックをスケジュールし、次に _asyncio.Queue_ を使用して_data_received_メソッドで受信したstdinデータ:

_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_メソッドの呼び出し方法です。どちらもコルーチンであり、コールバックで_yield from_またはProtocolインスタンスメソッドを使用して呼び出すことはできません。代わりに、 _asyncio.ensure_future_ を使用してイベントループでそれらをスケジュールし、_add_done_callback_メソッドを使用してget()呼び出しから取得した応答を処理します。 。

注:_asyncio.ensure_future_はPython 3.4.4で導入されました。それ以前は、メソッドは _asyncio.async_ と呼ばれていました。さらにPython 3.7が導入されました _asyncio.create_task_ 、これが現在推奨されている方法です。

12
dano