コードブロックを用いたサーバーとクライアントの例
お金 | 仕事 | 勉強 | プライベート | 健康 | 心
プログラミング関連のコンテンツ
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_ を使う
メソッドの後ろにつく?, !
カスタム演算子
ドッグフードを食べる
- - 関連記事 -
- プロキシオブジェクトの例で関数型プログラミングを理解
- 無名クラスを継承するRubyの動的機能
- method_missing()とsend()でフック(処理を捕捉)
- obj.instance_eval(&block)とblock.call(obj)の違いを確認
- メソッド名とシグニチャを統一して美しいAPI設計
- mustメソッドで読みやすいテスト