18.6. "asyncore" --- 非同期ソケットハンドラ
*******************************************

**ソースコード:** Lib/asyncore.py

バージョン 3.6 で非推奨: 代わりに "asyncio" を使ってください。

======================================================================

注釈: このモジュールは後方互換性のためだけに存在します。新しいコード
  では "asyncio" を利用することを推奨します。

このモジュールは、非同期ソケットサービスのクライアント・サーバを開発す
るための基盤として使われます。

CPUが一つしかない場合、プログラムが"二つのことを同時に"実行する方法は
二つしかありません。もっとも簡単で一般的なのはマルチスレッドを利用する
方法ですが、これとはまったく異なるテクニックで、一つのスレッドだけでマ
ルチスレッドと同じような効果を得られるテクニックがあります。このテクニ
ックはI/O処理が中心である場合にのみ有効で、CPU負荷の高いプログラムでは
効果が無く、この場合にはプリエンプティブなスケジューリングが可能なスレ
ッドが有効でしょう。しかし、多くの場合、ネットワークサーバではCPU負荷
よりはIO負荷が問題となります。

もしOSのI/Oライブラリがシステムコール "select()" をサポートしている場
合（ほとんどの場合はサポートされている）、I/O処理は"バックグラウンド"
で実行し、その間に他の処理を実行すれば、複数の通信チャネルを同時にこな
すことができます。一見、この戦略は奇妙で複雑に思えるかもしれませんが、
いろいろな面でマルチスレッドよりも理解しやすく、制御も容易です。
"asyncore" は多くの複雑な問題を解決済みなので、洗練され、パフォーマン
スにも優れたネットワークサーバとクライアントを簡単に開発することができ
ます。とくに、 "asynchat" のような、対話型のアプリケーションやプロトコ
ルには非常に有効でしょう。

基本的には、この二つのモジュールを使う場合は一つ以上のネットワーク *チ
ャネル* を "asyncore.dispatcher" クラス、または "asynchat.async_chat"
のインスタンスとして作成します。作成されたチャネルはグローバルマップに
登録され、 "loop()" 関数で参照されます。 "loop()" には、専用の *マップ
* を渡す事も可能です。

チャネルを生成後、 "loop()" を呼び出すとチャネル処理が開始し、最後のチ
ャネル（非同期処理中にマップに追加されたチャネルを含む）が閉じるまで継
続します。

asyncore.loop([timeout[, use_poll[, map[, count]]]])

   ポーリングループを開始し、count 回が過ぎるか、全てのオープン済みチ
   ャネルがクローズされた場合のみ終了します。全ての引数はオプションで
   す。引数 *count* のデフォルト値は "None" で、ループは全てのチャネル
   がクローズされた場合のみ終了します。引数 *timeout* は "select()" ま
   たは "poll()" の引数 timeout として渡され、秒単位で指定します。デフ
   ォルト値は 30 秒です。引数 *use_poll* が真の場合、 "select()" では
   なく "poll()" が使われます (デフォルト値は "False" です)。

   引数 *map* には、監視するチャネルをアイテムとして格納した辞書を指定
   します。チャネルがクローズされた時に *map* からそのチャネルが削除さ
   れます。 *map* が省略された場合、グローバルなマップが使用されます。
   チャネル ("asyncore.dispatcher", "asynchat.async_chat" とそのサブク
   ラス) は自由に混ぜて map に入れることができます。

class asyncore.dispatcher

   "dispatcher" クラスは、低レベルソケットオブジェクトの薄いラッパーで
   す。便宜上、非同期ループから呼び出されるイベント処理メソッドを追加
   していますが、これ以外の点では、non-blockingなソケットと同様です。

   非同期ループ内で低レベルイベントが発生した場合、発生のタイミングや
   コネクションの状態から特定の高レベルイベントへと置き換えることがで
   きます。例えばソケットを他のホストに接続する場合、最初の書き込み可
   能イベントが発生すれば接続が完了した事が分かります(この時点で、ソケ
   ットへの書き込みは成功すると考えられる)。このように判定できる高レベ
   ルイベントを以下に示します:

   +------------------------+------------------------------------------+
   | Event                  | 説明                                     |
   |========================|==========================================|
   | "handle_connect()"     | 最初にreadもしくはwriteイベントが発生し  |
   |                        | た時                                     |
   +------------------------+------------------------------------------+
   | "handle_close()"       | 読み込み可能なデータなしでreadイベントが |
   |                        | 発生した時                               |
   +------------------------+------------------------------------------+
   | "handle_accepted()"    | listen中のソケットでreadイベントが発生し |
   |                        | た時                                     |
   +------------------------+------------------------------------------+

   非同期処理中、マップに登録されたチャネルの "readable()" メソッドと
   "writable()" メソッドが呼び出され、 "select()" か "poll()" で
   read/writeイベントを検出するリストに登録するか否かを判定します。

   このようにして、チャネルでは低レベルなソケットイベントの種類より多
   くの種類のイベントを検出する事ができます。以下にあげるイベントは、
   サブクラスでオーバライドすることが可能です:

   handle_read()

      非同期ループで、チャネルのソケットの "read()" メソッドの呼び出し
      が成功した時に呼び出されます。

   handle_write()

      非同期ループで、書き込み可能ソケットが実際に書き込み可能になった
      時に呼び出されます。このメソッドでは、しばしばパフォーマンスの向
      上のために必要なバッファリングを実装します。例:

         def handle_write(self):
             sent = self.send(self.buffer)
             self.buffer = self.buffer[sent:]

   handle_expt()

      out of band (OOB)データが検出された時に呼び出されます。OOBはあま
      りサポートされておらず、また滅多に使われないので、
      "handle_expt()" が呼び出されることはほとんどありません。

   handle_connect()

      ソケットの接続が確立した時に呼び出されます。"welcome"バナーの送
      信、プロトコルネゴシエーションの初期化などを行います。

   handle_close()

      ソケットが閉じた時に呼び出されます。

   handle_error()

      捕捉されない例外が発生した時に呼び出されます。デフォルトでは、短
      縮したトレースバック情報が出力されます。

   handle_accept()

      listen 中のチャネル (受動的にオープンしたもの) がリモートホスト
      からの "connect()" で接続され、接続が確立した時に呼び出されます
      。 バージョン 3.2 で非推奨になりました; 代わりに
      "handle_accepted()" を使ってください。

      バージョン 3.2 で非推奨.

   handle_accepted(sock, addr)

      listen 中のチャネル (受動的にオープンしたもの) がリモートホスト
      からの "connect()" で接続され、接続が確立した時に呼び出されます
      。 *sock* はその接続でデータを送受信するのに使える *新しい* ソケ
      ットオブジェクトで、 *addr* は接続の対向のソケットに bind されて
      いるアドレスです。

      バージョン 3.2 で追加.

   readable()

      非同期ループ中に呼び出され、readイベントの監視リストに加えるか否
      かを決定します。デフォルトのメソッドでは "True" を返し、readイベ
      ントの発生を監視します。

   writable()

      非同期ループ中に呼び出され、writeイベントの監視リストに加えるか
      否かを決定します。デフォルトのメソッドでは "True" を返し、write
      イベントの発生を監視します。

   さらに、チャネルにはソケットのメソッドとほぼ同じメソッドがあり、チ
   ャネルはソケットのメソッドの多くを委譲・拡張しており、ソケットとほ
   ぼ同じメソッドを持っています。

   create_socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

      引数も含め、通常のソケット生成と同一です。ソケットの生成について
      は、 "socket" モジュールのドキュメントを参照してください。

      バージョン 3.3 で変更: *family* 引数と *type* 引数が省略可能にな
      りました。

   connect(address)

      通常のソケットオブジェクトと同様、*address* には一番目の値が接続
      先ホスト、2番目の値がポート番号であるタプルを指定します。

   send(data)

      リモート側の端点に *data* を送出します。

   recv(buffer_size)

      リモート側の端点より、最大 *buffer_size* バイトのデータを読み込
      みます。長さ0のバイト列オブジェクトが返ってきた場合、チャネルは
      リモートから切断された事を示します。

      "select.select()" や "select.poll()" がソケットが読み込みできる
      状態にあると報告したとしても、 "recv()" が "BlockingIOError" を
      送出する場合があります。

   listen(backlog)

      ソケットへの接続を待ちます。引数 *backlog* は、キューに追加でき
      るコネクションの最大数 (1 以上) を指定します。最大値はシステムに
      依存します（通常は5)。

   bind(address)

      ソケットを *address* にバインドします。ソケットはバインド済みで
      あってはなりません。 (*address* の形式は、アドレスファミリに依存
      します。 "socket" モジュールを参照のこと。) ソケットを再利用可能
      にする ("SO_REUSEADDR" オプションを設定する) には、 "dispatcher"
      オブジェクトの "set_reuse_addr()" メソッドを呼び出してください。

   accept()

      接続を受け入れます。ソケットはアドレスにバインド済みであり、
      "listen()" で接続待ち状態でなければなりません。戻り値は "None"
      か "(conn, address)" のペアで、*conn* はデータの送受信を行う **
      新しい** ソケットオブジェクト、*address* は接続先ソケットがバイ
      ンドされているアドレスです。"None" が返された場合、接続が起こら
      なかったことを意味します。その場合、サーバーはこのイベントを無視
      して後続の接続を待ち続けるべきです。

   close()

      ソケットをクローズします。以降の全ての操作は失敗します。リモート
      端点では、キューに溜まったデータ以外、これ以降のデータ受信は行え
      ません。ソケットはガベージコレクト時に自動的にクローズされます。

class asyncore.dispatcher_with_send

   "dispatcher" のサブクラスで、シンプルなバッファされた出力機能を持ち
   ます。シンプルなクライアントプログラムに適しています。もっと高レベ
   ルな場合には "asynchat.async_chat" を利用してください。

class asyncore.file_dispatcher

   file_dispatcher はファイルデスクリプタか *ファイルオブジェクト* と
   オプションとして map を引数にとって、 "poll()" か "loop()" 関数で利
   用できるようにラップします。与えられたファイルオブジェクトなどが
   "fileno()" メソッドを持っているとき、そのメソッドが呼び出されて戻り
   値が "file_wrapper" のコンストラクタに渡されます。利用できるプラッ
   トフォーム: UNIX。

class asyncore.file_wrapper

   file_wrapper は整数のファイルデスクリプタを受け取って "os.dup()" を
   呼び出してハンドルを複製するので、元のハンドルは file_wrapper と独
   立してclose されます。このクラスは "file_dispatcher" クラスが使うた
   めに必要なソケットをエミュレートするメソッドを実装しています。利用
   できるプラットフォーム: UNIX。


18.6.1. asyncoreの例: 簡単なHTTPクライアント
============================================

基本的なサンプルとして、以下に非常に単純なHTTPクライアントを示します。
このHTTPクライアントは "dispatcher" クラスでソケットを利用しています:

   import asyncore

   class HTTPClient(asyncore.dispatcher):

       def __init__(self, host, path):
           asyncore.dispatcher.__init__(self)
           self.create_socket()
           self.connect( (host, 80) )
           self.buffer = bytes('GET %s HTTP/1.0\r\nHost: %s\r\n\r\n' %
                               (path, host), 'ascii')

       def handle_connect(self):
           pass

       def handle_close(self):
           self.close()

       def handle_read(self):
           print(self.recv(8192))

       def writable(self):
           return (len(self.buffer) > 0)

       def handle_write(self):
           sent = self.send(self.buffer)
           self.buffer = self.buffer[sent:]


   client = HTTPClient('www.python.org', '/')
   asyncore.loop()


18.6.2. 基本的な echo サーバーの例
==================================

この例の基本的な echoサーバーは、 "dispatcher" を利用して接続を受けつ
け、接続をハンドラーにディスパッチします:

   import asyncore

   class EchoHandler(asyncore.dispatcher_with_send):

       def handle_read(self):
           data = self.recv(8192)
           if data:
               self.send(data)

   class EchoServer(asyncore.dispatcher):

       def __init__(self, host, port):
           asyncore.dispatcher.__init__(self)
           self.create_socket()
           self.set_reuse_addr()
           self.bind((host, port))
           self.listen(5)

       def handle_accepted(self, sock, addr):
           print('Incoming connection from %s' % repr(addr))
           handler = EchoHandler(sock)

   server = EchoServer('localhost', 8080)
   asyncore.loop()
