脳汁portal

アメリカ在住(だった)新米エンジニアがその日学んだIT知識を書き綴るブログ

RubyのIO.select を使って、TCP socketで返信がなくなったら自動でコネクションをきる方法

問題

TCP socketで送ったコマンドのレスポンスが複数行にわたる時とかに、データを全部取得したいんだけどgetsだと1行ずつしかとれないし、ループを回しても次の入力待ちになって帰ってこない。

今までは、、

問い合わせ先で終了文字を設定して送り返すようにしていた(以下のようなメッセージが帰ってくるものとする)

Good morning!!
Hello!!
Good evening!!
END

リクエストのレスポンスはソケットを回して終了文字が出てきたら終了というロジックだった

require 'socket'

res = []
TCPSocket.open('localhost', 10001) do |sock|
  sock.puts @request_command
  sock.each{|s|
    break if s.chomp! == "END" # ENDが帰ってきたら処理を中止する
    res.push(s)
  }
end

puts res
# => 
#Good morning!!
#Hello!!
#Good evening!!
#END
  • これでも一応問題はないが、終了文字が設定できない場合やわからない場合、またイレギュラーで帰ってこなかった場合などは、次の入力待ちプロセスに入ってアクセスが帰ってこなくなる。

これからはIO.selectを使う

singleton method IO.select (Ruby 2.1.0)
IO.selectを使えば、一定時間値が帰ってこなければ自動でコネクションをきって帰ってくるというオプションがある。

select(reads, writes = [], excepts = [], timeout = nil) -> [[IO]] | nil
  • timeoutオプションに秒数を設定する

早すぎない程度に0.5秒応答がなければ、返り値を全て受け取り終えたとみなす場合は、以下のように書ける

res= []
TCPSocket.open('localhost', 10001) do |sock|
  sock.puts @request_command
  while select [sock], nil, nil, 0.5
    res << sock.gets.chomp!
  end
end

puts res
# => 
#Good morning!!
#Hello!!
#Good evening!!
#END

これなら、レスポンスがどのような形で帰ってくるか正確にわからないときや、意図しない応答によるプロセスの停止が防げる。

epoll

今回のような小規模の場合は関係ないが、今はselectではなく、epollというシステムの方が効率がよいらしい。