Working with TCP Sockets 読書メモ 第7章 ノンブロッキングIO

目次


ノンブロッキング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つの状況
  1. TCPコネクションの受信側に到達するのに時間がかかるとき
  2. 受信側が処理可能なデータ量の限界を超えているとき

ノンブロッキングな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を発生させる
  • 詳細なサンプルは次章

ディスカッションに参加

2件のコメント

コメントをどうぞ

コメントを残す