コードブロックを用いたサーバーとクライアントの例

スポンサーリンク
スポンサーリンク
ライフスタイル関連のコンテンツ
お金 | 仕事 | 勉強 | プライベート | 健康 |
プログラミング関連のコンテンツ
C言語/C++入門 | Ruby入門 | Python入門 | プログラミング全般

=追記(2010/07/23)
Rubyベストプラクティスのひとり読書。理解が難しかった部分を中心にまとめています。
書籍から引用しすぎと思われたページ、理解したページなどを削除して、当初より公開ページを減らしました。すいません。
読書のまとめ記事は難しいと感じたので、今後はブログで読書のまとめは書かないでおきます。差し障りがあれば現存するページも削除致しますので予めご了承下さい。
=追記ここまで

スポンサーリンク

P51あたりからサーバーとクライアントの例。
コードブロックを用いて上手にDRYに徹する方法が書いてあって面白いです。

DRY違反なクライアント側コードの例

class Client
  def initialize(ip = "127.0.0.1", port = 3333)
    @ip, @port = ip, port
  end
 
  def send_message(msg)
    socket = TCPSocket.new(@ip, @port)
    socket.puts(msg)
    response = socket.gets
  ensure
    socket.close if socket
  end
 
  def recieve_message
    socket = TCPSocket.new(@ip, @port)
    response = socket.gets
  ensure
    socket.close if socket
  end
end

send_message, recieve_messageメソッドの両者は、socketのnew, close部分など共通が多いのでprivateメソッドとして切り出したいところですが、異なる箇所がメソッドの中央部分、send_message(msg)のsocket.puts(msg)の部分であるため難しい。
こういう時にコードブロックを用いて、共通する前後処理を、ブロック付きのメソッド(connectionメソッド)として切り出す。
そして、connectionメソッドに渡すブロックに、異なる箇所の処理を書くとすっきりします。

共通前後処理をブロック付きメソッドに切り出したクライアント側コード

P51あたりのコードを少し修正+クライアント側からの実行コードを加えた。51c.rb として保存。

51c.rb

require "socket"
 
class Client
  def initialize(ip = "127.0.0.1", port = 3333)
    @ip, @port = ip, port
  end
 
  def send_message(msg)
    connection do |socket|
      socket.puts(msg)
      socket.gets
    end
  end
 
  def recieve_message
    connection do |socket
      socket.gets
    end
  end
 
  private
 
  def connection
    socket = TCPSocket.new(@ip, @port)
    # socketをブロック内引数としてconnectionメソッドに渡されたブロックを実行
    yield(socket)
  ensure
    socket.close if socket
  end
end
 
client = Client.new
 
["Hello", "My name is Greg", "See you Tomorrow", "Goodbye"].each do |msg|
  response = client.send_message(msg)
  puts response
end

connectionメソッド定義中のyield(socket)によって、connectionメソッドに渡されたブロックにsocketがブロック内引数として渡されて実行される。

ブロックを引数にとるメソッドを用いたサーバー側コード

ブロックを引数にとるメソッドを定義してサーバーを書く。コメントを多めに書きました。51s.rbとして保存。

51s.rb

require "socket"
 
class Server
 
  def initialize(port = 3333)
    @server = TCPserver.new("127.0.0.1", port)
    @handlers = {}
  end
 
  # ブロックを引数にとる。
  # pattern(クライアントから送られるメッセージ)と、
  # block(サーバーがクライアントに返すレスポンス)のペア作成
  def handle(pattern, &block)
    @handlers[pattern] = block
  end
 
  def run
    while session = @server.accept
      msg = session.gets  # クライアントからのメッセージ受信
      match = nil
 
      @handlers.each do |pattern, block|
        if match = msg.match(pattern)
          # matchをブロック引数にしてblockを実行する
          break session.puts(block.call(match))
        end
      end
 
      unless match
        session.puts "Server recieved unknown message: #{msg}"
      end
    end
  end
 
  # Serverを走らせるクラスメソッド
  def self.run(port = 3333, &block)
    server = Server.new(port) # serverインスタンス生成
 
    # blockをserverインスタンスのコンテキストで評価(serverをレシーバとして)
    # server.handle~を実行するのと同じ
    server.instance_eval(&block)
    server.run
  end
 
end
 
=begin
# この書き方でも良いが、server.handleが何回も出てうざい
server = Server.new
 
server.handle(/hello/i) { "Hello from server at #{Time.now}" }
server.handle(/goodbye/i) { "Goodbye from server at #{Time.now}" }
server.handle(/name is (\w+)/) { |m| "Nice to meet you #{m[1]}" }
 
server.run
=end
 
# よってブロック引数をとるクラスメソッドrunを定義しブロック(do~end部分)を渡す
Server.run do
  handle(/hello/i) { "Hello from server at #{Time.now}" }
  handle(/goodbye/i) { "Goodbye from server at #{Time.now}" }
  handle(/name is (\w+)/) { |m| "Nice to meet you #{m[1]}" }
end

サーバーを起動し、クライアントコードを実行する

サーバー用とクライアント用にコンソール(コマンドプロンプト)を2つ立ち上げます。
まずは、サーバー起動。

>ruby 51s.rb

カーソルが点滅してサーバーのwhileループ(無限ループ)に入り、クライアントからの接続待ち受け状態になりました。
続いて、もう片方のコンソールでクライアントを実行します。

>ruby 51c.rb
Hello from server at Wed Jun 23 12:36:29 +0900 2010
Nice to meet you Greg
Server recieved unknown message: See you Tomorrow
Goodbye from server at Wed Jun 23 12:36:29 +0900 2010

サーバーが返したレスポンスを受け取り表示できました。

まとめなど

Enumerableを使う
中央部分だけが異なるコードの場合、前後処理の間でブロックをyieldするヘルパーを作る
&blockとinstance_evalで望みのオブジェクトのコンテキストでブロックを実行可

attr_ を使う
メソッドの後ろにつく?, !
カスタム演算子
ドッグフードを食べる

スポンサーリンク
 
スポンサーリンク