AlWorker TCP/IPサーバー
require "al_worker_tcp"
TCPサーバーを作成する機能です。SMTPサーバのような、chat式のサーバ機能を実装しています。
サンプル
サーバー
- tcp_server.rb
require "al_worker_tcp" class TcpServer < AlWorker # イニシャライザでTCPを用意する。 def initialize2() @tcp = Tcp.new( "<any>", 10025 ) # リスンアドレス、ポート番号を指定。 @tcp.run( self ) end # helloコマンド # "helo localhost" def tcp_helo( sock, param ) sock.puts "250 hello welcome." return true end # mailコマンド # "mail from: <nobody@example.com>" def tcp_mail( sock, param ) if /^from: ?(.+)$/ =~ param[""] sock.puts "250 Ok" else sock.puts "501 Syntax error." end return true end end server = TcpServer.new("tcp_server") server.parse_option() server.daemon()
クライアント(以下の例は、Ruby基本機能のみ使用した例で、Aloneライブラリは使わず実装しています)
- tcp_client.rb
require "socket" sock = TCPSocket.open("localhost", 10025) puts 'SEND "helo"' sock.puts "helo" puts 'RECEIVE ' + sock.gets puts 'SEND "quit"' sock.puts "quit" puts 'RECEIVE ' + sock.gets sock.close
実行結果
% ruby tcp_client.rb SEND "helo" RECEIVE 250 hello welcome. SEND "quit" RECEIVE 200 OK quit.
解説
AlWorkerを継承したクラスに “tcp_” をプレフィックスとして付与したメソッドを定義すると、フレームワークにTCPコマンドとして認識されます。
メソッドには、接続ソケット sock と、パラメータ param が渡されます。
パラメータはJSONパースされ、paramには結果のHashが渡されます。JSONパースできなかった場合は、param[""]にパース前の文字列が入ります。
返り値に trueを指定すると継続してコマンドを受けつけ、falseを指定すると接続を切ります。
デフォルトで、接続を切るためのコマンド、"quit"が定義されています。
定義されていないコマンドが送られた場合、"501 Error Command not implemented." を返します。
同期/非同期
イベントハンドラはデフォルトで同期的に呼び出され、たとえば2つのクライアントから同時にコマンドを受信しても、一つずつ実行します。
非同期にしたい場合は、”tcp_a_” をプレフィックスとして付けます。
def tcp_a_xxxx( sock, param ) # any code return true end
バイナリの送受信
Chatプロトコルでは、テキストの送受信のみ想定しており、エンコーディングはデフォルトで UTF-8 です。
バイナリの送受信が必要な場合は、以下のようにエンコーディングを変更します。
@tcp.set_encoding(Encoding::ASCII_8BIT)
サービスモデル(スレッド/プロセス)
デフォルトで、スレッドモデルで動作します。
接続ごとに別プロセスで動作させる(プロセスモデル)には、以下のように指定します。
@tcp.mode_service = :process
chat以外のプロトコルを使用
chat方式ではないプロトコルを使いたい場合は、AlWorker::Tcpクラスを継承して、start_service()メソッドをオーバライドします。
すべてのGETリクエストに "It works!" と返すウェブサーバ
- web_server.rb
require "al_worker_tcp" class WebServer < AlWorker::Tcp def start_service( sock ) req = [] while txt = sock.gets txt.chomp! break if txt.empty? req << txt end if !req.empty? && req[0].upcase.start_with?("GET ") sock.puts "HTTP/1.0 200 OK\r\n" sock.puts "Content-Type: text/html\r\n" sock.puts "\r\n" sock.puts "<!DOCTYPE html>\r\n" sock.puts "<title>TESTPAGE</title>\r\n" sock.puts "<p>It works!</p>\r\n" end end end class TcpServer < AlWorker def initialize2() log.level = Logger::DEBUG @tcp = WebServer.new("<any>", 3000) # リスンアドレス、ポート番号を指定。 @tcp.run( self ) end end server = TcpServer.new("web_server") server.daemon()