memo

2016-06-05

python の asyncio について調べる その2

前回 は asyncio event loop がファイルディスクリプタを扱う辺りの API を見た。

今回はその上に構築された API, transport と protocol を調べる。

Transport と protocol

Transport は TCP や SSL などの、何かしらのデータチャンネルを表す。

Protocol は HTTP や SMTP など、 transport 上を流れるデータをしきたりに則って解釈し、意味のあるデータを取り出したり意味のあるデータを送り付けたりする。

Transport は作成時に指定された protocol と結び付けられる。 Transport が受け取ったデータは protocol に渡され、また、 protocol が書き出したいデータは transport に渡される事になる。

Transport についてもう少し

Asyncio は標準で TCP や SSL といった transport を提供している。 とりあえず TCP な transport の動作について、もう少し見てみる。

Transport は作成時に、

  • 自身を作成した event loop

  • socket

  • 紐付けられた protocol

を受け取る。

そして、 event loop の add_reader() メソッドを使い、 socket が読みだし可能になったら (データを受け取ったら) 自身の _read_ready() メソッドが呼び出されるよう登録する。

Event loop から _read_ready() 呼びだされたら、 socket からデータを読みだし、 自身に紐付いた protocol の data_received() メソッドにデータを渡す。

Protocol より transport にデータが書き込まれた (write() メソッドが呼ばれた) 際は、 event loop の add_writer() メソッドで socket が書き込み可能になるのを待ち、 呼ばれた callback 内でたまったデータを書き出してゆく。

試す

前回同様、サーバーから返ってきたデータを画面に出すクライアントを書いてみる。

import asyncio
import time


class HelloProtocol(asyncio.Protocol):
    def connection_made(self, transport):
        print('connected', transport)

    def data_received(self, data):
        # Transport により、 socket からデータが読み出されるたびに呼び出される
        # 前回同様、現在時間と受け取ったデータを画面に表示する。
        print(int(time.time()))
        print(data)
        print('end')

    def eof_received(self):
        print('eof')
        # connection が切断されたら event loop を停止。
        loop.stop()


loop = asyncio.get_event_loop()
# XXX: おまじないとして ensure_future を呼ぶ。
asyncio.ensure_future(
    # 名前解決、 socket 接続、 TCP 用の transport インスタンス作成、
    # protocol インスタンス作成、 transport と protocol のひも付け、
    # 辺りまでやってくれる event loop のメソッド呼び出し。
    loop.create_connection(HelloProtocol, '127.0.0.1', 9999))
loop.run_forever()

実行してみる:

$ python3.5 hello-client3.py
connected <_SelectorSocketTransport fd=7 read=idle write=<idle, bufsize=0>>
1465115330
b'hello world\n'
end
1465115331
b'hello world\n'
end
1465115332
b'hello world\n'
end
eof

前回同様、1秒毎 (データ受信ごと) にコールバックが呼び出されていることが確認できる。