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
まぁ、「あとでやる」だ。