Как определяется тайм-аут Ruby TCPSocket?

$ irb
1.9.3-p448 :001 > require 'socket'
 => true 
1.9.3-p448 :002 > TCPSocket.new('www.example.com', 111)

дает

Errno:: ETIMEDOUT: время ожидания операции - подключение (2)

Вопросы:

  • Как определить значение тайм-аута для TCPSocket.new?
  • Как я могу правильно поймать исключения (или, в общем, сокет)?

Ответ 1

Используйте begin .. rescue Errno::ETIMEDOUT, чтобы уловить таймаут:

require 'socket'

begin
  TCPSocket.new('www.example.com', 111)
rescue Errno::ETIMEDOUT
  p 'timeout'
end

Чтобы уловить исключения сокетов, используйте SystemCallError вместо этого.

В соответствии с Документация SystemCallError:

SystemCallError является базовым классом для всех низкоуровневых ошибок, зависящих от платформы.

Ошибки, доступные на текущей платформе, являются подклассами SystemCallError и определены в модуле Errno.


TCPSocket.new не поддерживает таймаут напрямую.

Используйте Socket::connect_non_blocking и IO::select для установки тайм-аута.

require 'socket'

def connect(host, port, timeout = 5)

  # Convert the passed host into structures the non-blocking calls
  # can deal with
  addr = Socket.getaddrinfo(host, nil)
  sockaddr = Socket.pack_sockaddr_in(port, addr[0][4])

  Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0).tap do |socket|
    socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

    begin
      # Initiate the socket connection in the background. If it doesn't fail 
      # immediatelyit will raise an IO::WaitWritable (Errno::EINPROGRESS) 
      # indicating the connection is in progress.
      socket.connect_nonblock(sockaddr)

    rescue IO::WaitWritable
      # IO.select will block until the socket is writable or the timeout
      # is exceeded - whichever comes first.
      if IO.select(nil, [socket], nil, timeout)
        begin
          # Verify there is now a good connection
          socket.connect_nonblock(sockaddr)
        rescue Errno::EISCONN
          # Good news everybody, the socket is connected!
        rescue
          # An unexpected exception was raised - the connection is no good.
          socket.close
          raise
        end
      else
        # IO.select returns nil when the socket is not ready before timeout 
        # seconds have elapsed
        socket.close
        raise "Connection timeout"
      end
    end
  end
end

connect('www.example.com', 111, 2)

Вышеприведенный код исходит из Установка таймаута соединения сокета в Ruby".

Ответ 2

По крайней мере с 2.0 можно просто использовать:

Socket.tcp("www.ruby-lang.org", 10567, connect_timeout: 5) {}

Для более старых версий лучше всего подходит ответ @falstru.

Ответ 3

Если вам нравится идея избежать ловушек тайм-аута, но предпочитайте избегать необходимости иметь дело с вашей собственной реализацией *_nonblock + select, вы можете использовать драгоценный камень tcp_timeout.

Tcp_timeout gem обезьяна-патчи TCPSocket # connect, #read и #write, чтобы они использовали неблокирующий ввод-вывод и имели таймауты, которые вы можете включить.

Ответ 4

Чтобы сделать тайм-аут, вы можете использовать модуль ruby ​​ Timeout:

reqiure 'socket'
reqiure 'timeout'

begin 
   Timeout.timeout(10) do
      begin
         TCPSocket.new('www.example.com', 111)
      rescue Errno::ENETUNREACH
         retry # or do something on network timeout
      end
   end
rescue Timeout::Error
   puts "timed out"
   # do something on timeout
end

и вы получите через 10 секунд:

# timed out
# => nil