20

Ruby で SO_RCVTIMEO ソケット オプションを使用してソケットをタイムアウトさせようとしていますが、最近の *nix オペレーティング システムでは効果がないようです。

Ruby の Timeout モジュールを使用することはオプションではありません。これは、タイムアウトごとにスレッドを生成して結合する必要があり、コストが高くなる可能性があるためです。低いソケット タイムアウトを必要とし、スレッド数が多いアプリケーションでは、基本的にパフォーマンスが低下します。これは、 Stack Overflowを含む多くの場所で指摘されています。

この件に関する Mike Perham の優れた投稿を読みまし。問題を実行可能なコードの 1 つのファイルに減らすために、リクエストを受信し、リクエストで送信された時間を待機し、その後、接続を閉じます。

クライアントはソケットを作成し、受信タイムアウトを 1 秒に設定してから、サーバーに接続します。クライアントは、5 秒後にセッションを閉じるようにサーバーに指示し、データを待ちます。

クライアントは 1 秒後にタイムアウトするはずですが、代わりに 5 秒後に正常に接続を閉じます。

#!/usr/bin/env ruby
require 'socket'

def timeout
  sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)

  # Timeout set to 1 second
  timeval = [1, 0].pack("l_2")
  sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, timeval

  # Connect and tell the server to wait 5 seconds
  sock.connect(Socket.pack_sockaddr_in(1234, '127.0.0.1'))
  sock.write("5\n")

  # Wait for data to be sent back
  begin
    result = sock.recvfrom(1024)
    puts "session closed"
  rescue Errno::EAGAIN
    puts "timed out!"
  end
end

Thread.new do
  server = TCPServer.new(nil, 1234)
  while (session = server.accept)
    request = session.gets
    sleep request.to_i
    session.close
  end
end

timeout

TCPSocket (自動的に接続) でも同じことを試してみましたが、redisや他のプロジェクトで同様のコードを見てきました。

getsockoptさらに、次のように呼び出すことで、オプションが設定されていることを確認できます。

sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO).inspect

このソケットオプションの設定は実際に誰にとっても機能しますか?

4

3 に答える 3

25

これは、Ruby の IO クラスを使用して効率的に行うことができます。select

IO::select4 つのパラメータを取ります。最初の 3 つは監視するソケットの配列で、最後の 1 つはタイムアウト (秒単位で指定) です。

select が機能する方法は、IO オブジェクトのリストを、少なくとも 1 つのオブジェクトからの読み取り、書き込み、またはエラーを発生させる準備ができるまでブロックすることによって、特定の操作の準備を整えることです。

したがって、最初の 3 つの引数は、監視するさまざまなタイプの状態に対応しています。

  • すぐに読める
  • 書き込み準備完了
  • 保留中の例外があります

4 番目は、設定するタイムアウト (存在する場合) です。このパラメーターを利用します。

Select は、監視対象の特定のアクションに対してオペレーティング システムによって準備ができていると見なされる IO オブジェクト (この場合はソケット) の配列を含む配列を返します。

したがって、select の戻り値は次のようになります。

[
  [sockets ready for reading],
  [sockets ready for writing],
  [sockets raising errors]
]

ただし、nilオプションのタイムアウト値が指定され、タイムアウト秒以内に IO オブジェクトの準備ができていない場合、select は戻ります。

したがって、Ruby でパフォーマンスの高い IO タイムアウトを実行し、Timeout モジュールを使用する必要がないようにするには、次のようにします。

timeoutの読み取りを数秒待つ例を作成しましょうsocket

ready = IO.select([socket], nil, nil, timeout)

if ready
  # do the read
else
  # raise something that indicates a timeout
end

これには、(Timeout モジュールのように) タイムアウトごとに新しいスレッドをスピンアップしないという利点があり、Rubyで多くのタイムアウトを持つマルチスレッド アプリケーションをはるかに高速にします。

于 2012-08-24T14:14:05.473 に答える
6

あなたは基本的に運が悪いと思います。strace(出力をきれいに保つために外部サーバーのみを使用して)例を実行すると、setsockopt実際に呼び出されていることを簡単に確認できます。

$ strace -f ruby foo.rb 2>&1 | grep setsockopt
[pid  5833] setsockopt(5, SOL_SOCKET, SO_RCVTIMEO, "\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) = 0

straceプログラムをブロックしているものも表示されます。これは、サーバーがタイムアウトする前に画面に表示される行です。

[pid  5958] ppoll([{fd=5, events=POLLIN}], 1, NULL, NULL, 8

ppollこれは、プログラムが への呼び出しではなく、この呼び出しでブロックしていることを意味しrecvfromます。ソケットオプション ( socket(7) )を一覧表示する man ページには、次のように記載されています。

タイムアウトは、select(2)、poll(2)、epoll_wait(2) などには影響しません。

そのため、タイムアウトが設定されていますが、効果はありません。ここで間違っていることを願っていますが、Ruby でこの動作を変更する方法はないようです。実装をざっと見てみましたが、明らかな方法が見つかりませんでした。繰り返しますが、私が間違っていることを願っています。これは基本的なことのように思えます。

1 つの (非常に醜い) 回避策は、dlto callreadまたはdirect を使用するrecvfromことです。これらの呼び出しは、設定したタイムアウトの影響を受けます。例えば:

require 'socket'
require 'dl'
require 'dl/import'

module LibC
  extend DL::Importer
  dlload 'libc.so.6'
  extern 'long read(int, void *, long)'
end

sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
timeval = [3, 0].pack("l_l_")
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, timeval
sock.connect( Socket.pack_sockaddr_in(1234, '127.0.0.1'))

buf = "\0" * 1024
count = LibC.read(sock.fileno, buf, 1024)
if count == -1
  puts 'Timeout'
end

このコードはここで機能します。もちろん、これは醜いソリューションであり、多くのプラットフォームでは機能しません。

また、Ruby で同様のことを行うのはこれが初めてであるため、見落としている可能性のあるすべての落とし穴に気づいていないことに注意してください'long read(int, void *, long)'。読み取るバッファを渡しています。

于 2012-03-24T18:36:08.647 に答える
6

私のテストと、Jesse Storimer の優れた ebook on "Working with TCP Sockets" (Ruby) によると、タイムアウト ソケット オプションは Ruby 1.9では機能しません(そして、2.0 と 2.1 を想定しています)。ジェシー 言います:

オペレーティング システムは、SNDTIMEO および RCVTIMEO ソケット オプションを介して設定できるネイティブ ソケット タイムアウトも提供します。しかし、Ruby 1.9 以降、この機能は機能しなくなりました。」

わお。この話の教訓は、これらのオプションを忘れて、IO.selectTony Arcieri の NIO ライブラリを使用することだと思います。

于 2013-10-11T19:57:20.967 に答える