EventMachineによるネットワークプログラミング

これすごいんじゃな?あんま、記事がないんだけど。
EventMachine: 高速でスケーラブルなEvent-Driven I/Oフレームワーク
このサイトがわかりやすかった。
An EventMachine Tutorial
EventMachineとはイベントドリブンのRubyネットワークライブラリだそうな。
なにがすごいのかというと、低レベルのネットワークの接続やクローズといったことを書かなくてよくて、接続したとか、クローズしたとかっていうイベントが発生するとコールバックされる関数があるのでそれをオーバーライドしてコーディングするので、本当にやりたいことに集中できるんだそうだ。
たしかに、これで自分のようなネットワークプログラミングに疎い人間にもかけるような気がしてきた。
サンプルでよくあるのが↓のエコーサーバというやつ。このサーバに接続して文字を入力するとヤマビコのように返事をするというアレです。

#!/usr/bin/env ruby

require 'rubygems'
require 'eventmachine'

module Echo
  def receive_data data
    send_data data
  end
end

例えば上のコードをecho.rbとして保存。

$ chmod +x echo.rb
$ ./echo.rb

として実行したら、別のコンソールからTelnetで10000ポートで接続

$ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
hello  <ここでhelloと入力してEnter
hello <サーバからhelloと返ってきた(以下も同じ)
aiueo
aiueo
^]   <これは「Ctrl + ]」を入力してEnterで終了

telnet> quit
Connection closed.

とこんな感じで簡単にEchoサーバが実装できてしまう。
上記コードのようにロジックはモジュールの各種コールバック関数の中に定義する。上ではreceive_dataメソッドを定義しているが、これは相手からデータを受信したときに呼び出される。このれいでは、クライアントから何かデータを受け取ったとき「data」という変数へ内容が格納されてくる。そしてそのままsend_dataで返しているだけ。
次に、

EM.run {
EM.start_server "0.0.0.0", 10000, Echo
}

この部分でサーバとして起動することを指示している。1つ目の引数いはリスンするアドレス(0.0.0.0で、どのアドレスでもリクエストを受け付けるようになる)を指定、2つめは受け付けるポート番号を指定、3つ目でロジックを定義したモジュールを指定する。
何て簡単。で、このコールバック関数「receive_data」以外にも「post_init」(コネクションを確立したあとに呼ばれる)や「unbind」(コネクションをクローズしたときに呼ばれる)等色々ありそう。
あと、最初に紹介したサイトにあったチャットプログラムの例も載せておこう

#!/usr/bin/env ruby

require 'rubygems'
require 'eventmachine'

module Chat
  # コネクションが確立されたとき呼び出される
  # コネクションを確立するのはまずクライアント側からなので、
  # この場合、各クライアントが接続してくる度に実行される
  def post_init
    (@@connections ||= []) << self # 自分自身をクラス変数「@@connections」に入れる
    # 初めての接続なので、ユーザ名の入力を促す
    send_data "Please enter your name: "
  end
  # クライアントからデータを受け取った時の処理
  def receive_data data
    @name ||= data.strip
    # @@connectionsには各クライアントのChatオブジェクトが入っているはず
    # すべてのクライントにデータを返す
    @@connections.each do |client|
      client.send_data "#{@name} say: #{data}"
    end
  end
end
EM.run do
  EM.start_server "0.0.0.0", 8081, Chat
end

うーん。簡単ぽい。ちゃんとしたチャットサーバにするには色々やることはあるだろうけど。これだけ簡単にかけるんだね。
もう一つ、上のサイトにあった例でHttpClinetとして動くプログラムも載せておこう。これ、なんかへんだけど。

#!/usr/bin/env ruby
require 'rubygems'
require 'eventmachine'

module HttpHeaders
  # コネクションを確立した後、GETリクエストを送る
  def post_init
    send_data "GET /\r\n\r\n"
    @data = ""
  end
  # データを受けとった時
  # 上記GETリクエストの応答がdataに入っているはず
  def reveive_data(data)
    @data << data
  end
  # コネクションがクローズされたとき
  def unbind
    if @data =~ /[\n][\r]*[\n]/m
      $`.each {|line| puts ">>> #{line}" }
    end
    EM.stop_event_loop
  end
end

EM.run do
  EM.connect 'microsoft.com', 80, HttpHeaders
end

まぁ、「あとでやる」だ。