Ruby - See if a port is open

2019-01-13 04:43发布

问题:

I need a quick way to find out if a given port is open with Ruby. I currently am fiddling around with this:

require 'socket'

def is_port_open?(ip, port)
  begin
    TCPSocket.new(ip, port)
  rescue Errno::ECONNREFUSED
    return false
  end
  return true
end

It works great if the port is open, but the downside of this is that occasionally it will just sit and wait for 10-20 seconds and then eventually time out, throwing a ETIMEOUT exception (if the port is closed). My question is thus:

Can this code be amended to only wait for a second (and return false if we get nothing back by then) or is there a better way to check if a given port is open on a given host?

Edit: Calling bash code is acceptable also as long as it works cross-platform (e.g., Mac OS X, *nix, and Cygwin), although I do prefer Ruby code.

回答1:

Something like the following might work:

require 'socket'
require 'timeout'

def is_port_open?(ip, port)
  begin
    Timeout::timeout(1) do
      begin
        s = TCPSocket.new(ip, port)
        s.close
        return true
      rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
        return false
      end
    end
  rescue Timeout::Error
  end

  return false
end


回答2:

More Ruby idiomatic syntax:

require 'socket'
require 'timeout'

def port_open?(ip, port, seconds=1)
  Timeout::timeout(seconds) do
    begin
      TCPSocket.new(ip, port).close
      true
    rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
      false
    end
  end
rescue Timeout::Error
  false
end


回答3:

I recently came up with this solution, making use of the unix lsof command:

def port_open?(port)
  !system("lsof -i:#{port}", out: '/dev/null')
end


回答4:

Just for completeness, the Bash would be something like this:

$ netcat $HOST $PORT -w 1 -q 0 </dev/null && do_something

-w 1 specifies a timeout of 1 second, and -q 0 says that, when connected, close the connection as soon as stdin gives EOF (which /dev/null will do straight away).

Bash also has its own built-in TCP/UDP services, but they are a compile-time option and I don't have a Bash compiled with them :P



回答5:

All other existing answer are undesirable. Using Timeout is discouraged. Perhaps things depend on ruby version. At least since 2.0 one can simply use:

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

For older ruby the best method I could find is using non-blocking mode and then select. Described here:

  • https://spin.atomicobject.com/2013/09/30/socket-connection-timeout-ruby/


回答6:

All *nix platforms:

try nc / netcat command as follow.

`nc -z -w #{timeout_in_seconds} -G #{timeout_in_seconds} #{host} #{port}`
if $?.exitstatus == 0
  #port is open
else
  #refused, port is closed
end

The -z flag can be used to tell nc to report open ports, rather than initiate a connection.

The -w flag means Timeout for connects and final net reads

The -G flag is connection timeout in seconds

Use -n flag to work with IP address rather than hostname.

Examples:

# `nc -z -w 1 -G 1 google.com 80`
# `nc -z -w 1 -G 1 -n 123.234.1.18 80`


回答7:

My slight variation to Chris Rice's answer. Still handles timing out on a single attempt but also allows multiple retries until you give up.

    def is_port_open?(host, port, timeout, sleep_period)
      begin
        Timeout::timeout(timeout) do
          begin
            s = TCPSocket.new(host, port)
            s.close
            return true
          rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
            sleep(sleep_period)
            retry
          end
        end
      rescue Timeout::Error
        return false
      end
    end