Python の asyncio で作成した socket サーバーです。 簡単なecho サーバです。 OSレベルのAPI selectors を直接使うのではなく、より高レベルの asyncioを使うことで簡潔で読みやすいプログラミングが可能となっています。クライアントとのやり取りをasyncioのコルーチンとして書き、イベントループ上で並列実行させています。
Python Asyncio入門 - Qiita
import asyncio, socket
async def handle_client(client, loop):
while data := await loop.sock_recv(client, 1024):
print( f"data = {data}")
await loop.sock_sendall(client, data)
client.close()
async def run_server():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Server ソケット
server.setblocking(False) # ノンブロッキングソケット
server.bind(('localhost', 8000))
server.listen()
loop = asyncio.get_event_loop()
while True:
client, address = await loop.sock_accept(server) # Client ソケット
client.setblocking(False) # ノンブロッキングソケット
print(f"New client from {address}")
asyncio.create_task(handle_client(client, loop)) # イベントループでスケジューリング
asyncio.run(run_server())
プログラムの起動
python main.py
クライアントからの接続
telnet localhost 8000
コルーチン run_server() はServer ソケットを作成しクライアントからの接続を待ち受けます。クライアントから説毒が来るとacceptしClient ソケットを作成し、コルーチン handle_client()をイベントループに放り込みスケジューリングします(asyncio.create_task())。
コルーチン handle_client()はクライアントからのデータを待ち受け(loop.sock_recv())、受け取るとそのままクライアントに返却します(loop.sock_sendall())。
ここで重要なのが、asyncio の イベントループ上で使える以下の3つのコルーチンです。
-
coroutine loop.sock_accept(sock)
接続を受け付けます。ブロッキングコールの socket.accept() メソッドをモデルとしています。ソケット sock はアドレスにbind 済みで、接続を listen 中である必要があります。戻り値は (conn, address) のペアで、conn は接続を通じてデータの送受信を行うための 新しい ソケットオブジェクト、address は接続先の端点でソケットに束縛されているアドレスを示します。sock はノンブロッキングソケットでなければなりません。 -
coroutine loop.sock_recv(sock, nbytes)
nbytes で指定したバイト数までのデータをソケット sock から受信します。 このメソッドは socket.recv() の非同期版です。受信したデータをバイトオブジェクトとして返します。sock はノンブロッキングソケットでなければなりません。 -
coroutine loop.sock_sendall(sock, data)
データ data をソケット sock に送信します。 socket.sendall() メソッドの非同期版です。このメソッドは data をすべて送信し終えるか、またはエラーが起きるまでデータをソケットに送信し続けます。送信に成功した場合 None を返します。エラーの場合は例外が送出されます。エラーとなった場合、接続の受信側で正しく処理されたデータの総量を特定する方法はありません。sock はノンブロッキングソケットでなければなりません。
今回は以上です。