目次
ノンブロッキングIO
- ノンブロッキングIOは、コネクションの多重化によって実現できる
ノンブロッキングな読み込み
read
を使うと、EOFを受け取るまでブロックする
readpartial
を使えば今読めるデータだけを取得できるが、データが送られないとブロックする
- 絶対に ブロックしないようにするには、
read_nonblock
を使用する
require 'socket'
Socket.tcp_server_loop(4481) do |connection|
loop do
begin
puts connection.read_nonblock(4096)
rescue Errno::EAGAIN
retry
rescue EOFError
end
end
connection.close
end
- データが無い場合、
Errno::EAGAIN
の例外が発生する
Errno::EAGAIN
が発生したらretry
している
retry
の実行を制御する例は以下
rescue Errno::EAGAIN
IO.select([connection])
retry
- IO.selectを使うと、connectionから読み込みが可能になるまでブロックする
- 読み込みが可能になった時点で
retry
している
- この実装では、読み込み可能までブロックするので、
read
を使うのと変わらない!
読み込みはいつブロックするのか?
read_nonblock
メソッドはRubyの内部バッファをチェックして保留中のデータがないか確認する
- select(2)を使って利用可能なデータを確認する
- 利用可能なデータがある場合はデータが返り、利用可能なデータがない場合はブロックする
ノンブロッキングな書き込み
write_nonblock
によってノンブロッキングな書き込みを実装できる
require 'socket'
client = TCPSocket.new('localhost', 4481)
payload = 'Lorem ipsum' * 100_000
written = client.write_nonblock(payload)
p written < payload.size # trueになるはず
write_nonblock
は、大量のデータ送信等でブロックするような状態に陥った場合、全てのデータを送信せずに送信済みのバイト数を返す
write_nonblock
の挙動はwrite(2)と同じ
- Rubyの
write
は何度もwrite(2)を呼んで全てのデータを書き込む
write_nonblock
の制御にもIO.select
が利用できる
require 'socket'
client = TCPSocket.new('localhost', 4481)
payload = 'Lorem ipsum' * 100_000
begin
loop do
bytes = client.write_nonblock(payload)
break if bytes >= payload.size
payload.slice!(0, bytes)
IO.select(nil, [client])
end
rescue Errno::EAGAIN
IO.select(nil, [client])
retry
end
IO.select
の第2引数にソケット配列を渡すと、書き込み可能になるまでブロックする
write_nonblock
とpayloadのスライスを繰り返して、全てのデータを送信している
書き込みはいつブロックするのか?
- write(2)がブロックする可能性があるのは以下の2つの状況
- TCPコネクションの受信側に到達するのに時間がかかるとき
- 受信側が処理可能なデータ量の限界を超えているとき
ノンブロッキングなaccept
- 通常の
accept
は、キューにコネクションが1つもないとブロックする
accept_nonblock
は、キューにコネクションがないとErrno::EAGAIN
を発生させる
require 'socket'
server = TCPServer.new(4481)
loop do
begin
connection = server.accept_nonblock
rescue Errno::EAGAIN
# コネクションがない場合にやることを書く
retry
end
end
ノンブロッキングなconnect
connect_nonblock
の挙動は、他のxxx_nonblock
とは異なる
xxx_nonblock
は正常に処理を行うか、例外を投げるかのいずれか
connect_nonblock
は、処理を行い、 かつ 、例外を投げることがある
- リモートホストと即時に接続ができない場合、バックグラウンドで接続を試みつつ
Errno::EINPROGRESS
を発生させる
- 詳細なサンプルは次章
コメントをどうぞ