IO::selectを使う

ここでは、IO::selectを使って複数のソケットからデータを受け取る方法を説明したいと思います。

IO::select

普通の状態では、recvなどはデータが受信できるまでブロッキングします。 ソケットを一つしか利用していない場合にはブロッキングは非常に便利なのですが、ソケットが複数になると困ってしまいます。 シングルスレッドで複数のソケットを扱うとき、片方のソケットでブロッキングしたままになってしまうと他のソケットにデータが到着しても受信が出来なくなってしまうからです。 そのため、複数のソケットを扱っていると、どのソケットからデータが受信可能か知りたくなります。

ブロッキングとは、関数が返ってこない事を表します。 例えば、recvはデータを受信して関数が戻ってきます。 言い方を変えると、データを受信するまでブロックしています。 recvをブロッキングしないノンブロッキング方式で使う事も可能ですが、ここではブロッキング方式のまま使う方法を説明します。

そのような機能を提供するのがIO::selectです。 IO::selectを使うと、データが受信可能なソケットでのみrecvを実行することができます。 IO::selectは登録したソケットをブロッキング状態で監視し、どれかがデータ受信するとブロッキング状態を解除します。

IO::selectを使うサンプルコード

IO::selectは受信可能かどうかだけではなく、送信可能かどうか、エラーがあるかなどもチェックできますがここでは受信可能かどうかをチェックする方法のみをとりあげます。

下記サンプルコードでは、UDPソケットを2つ作成しています。 作成した2つのUDPソケットは、IO::selectに登録してIO::selectはブロッキング状態に入ります。 UDPソケットにデータが到着するとrecvを行い、受信した内容を表示します。


#!/usr/bin/ruby

require "socket"

# 受信用UDPSocketを2つ作る
udps0 = UDPSocket.open()
udps1 = UDPSocket.open()

# ポート番号10000番と20000番で待つ
udps0.bind("0.0.0.0", 10000)
udps1.bind("0.0.0.0", 20000)

# 永遠に繰り返す
while true

  # IO::selectの第一引数は入力待ちをするSocketです
  # C言語で言うとread用のfdsです
  ret = IO::select([udps0, udps1])

  # IO::selectは引数で渡した要素の配列です
  # 今回は入力待ちの部分のみを指定したので、
  # 結果は要素0番に入っています
  # 要素0番は、読み込み可能になっているSocketの配列です

  ret[0].each do |sock|
    # 読み込み可能になっているSocketでrecvして内容を表示
    p sock.recv(65535)
  end

end

# ここには到達しません
udps0.close
udps1.close

IO::selectを使うサンプルコードにデータを送信するコード

selectのサンプルコードは、UDPの10000番と20000番のポートでデータを待っています。 UDPを使って10000番と20000番ポートにデータを送信するサンプルを以下に示します。


#!/usr/bin/ruby

require "socket"

udp = UDPSocket.open()

# 10000番ポートに投げるためのsockaddrを生成
sockaddr0 = Socket.pack_sockaddr_in(10000, "127.0.0.1")

# 20000番ポートに投げるためのsockaddrを生成
sockaddr1 = Socket.pack_sockaddr_in(20000, "127.0.0.1")

# 10000番へ送信
udp.send("TO port 10000", 0, sockaddr0)
# 1秒待つ
sleep 1

# 20000番へ送信
udp.send("TO port 20000", 0, sockaddr1)
# 1秒待つ
sleep 1

# もう一度やってみる
udp.send("TO port 10000", 0, sockaddr0)
sleep 1
udp.send("TO port 20000", 0, sockaddr1)

# 使ったソケットを閉じる
udp.close


最後に

ここでは、UDPを使ったサンプルを示しましたが、TCPを使っても同様の事が出来ます。 TCPの場合は、acceptした後のソケットだけではなく、listen状態のソケットに対するIO::selectも可能です。

プロフェッショナルIPv6解説動画シリーズ再生リスト

動画で学ぶ「プロフェッショナルIPv6」を作っています。 もしよろしければご覧ください。お楽しみいただければ幸いです!