47

我需要一种快速的方法来确定给定端口是否已使用 Ruby 打开。我目前正在摆弄这个:

require 'socket'

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

如果端口是打开的,它会很好用,但它的缺点是偶尔它会坐下来等待 10-20 秒,然后最终超时,抛出ETIMEOUT异常(如果端口关闭)。我的问题是:

可以将此代码修改为仅等待一秒钟(false如果到那时我们什么都没有返回,则返回)还是有更好的方法来检查给定端口是否在给定主机上打开?

编辑:调用 bash 代码也是可以接受的,只要它可以跨平台工作(例如,Mac OS X、*nix 和 Cygwin),尽管我更喜欢 Ruby 代码。

4

8 回答 8

54

像下面这样的东西可能会起作用:

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
于 2009-02-05T19:55:42.330 回答
28

更多 Ruby 惯用语法:

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
于 2012-01-26T12:08:23.997 回答
28

所有其他现有答案都是不可取的。Timeout鼓励使用。也许事情取决于红宝石版本。至少从 2.0 开始,人们可以简单地使用:

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

对于较旧的 ruby​​,我能找到的最佳方法是使用非阻塞模式,然后select. 此处描述:

于 2016-07-08T12:02:02.217 回答
16

我最近提出了这个解决方案,使用了 unixlsof命令:

def port_open?(port)
  !system("lsof -i:#{port}", out: '/dev/null')
end
于 2014-03-31T01:11:43.917 回答
9

为了完整起见,Bash 应该是这样的:

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

-w 1指定 1 秒的超时时间,并-q 0表示在连接时,一旦stdin给出就关闭连接EOF(这/dev/null将立即完成)。

Bash 也有自己的内置 TCP/UDP 服务,但它们是编译时选项,我没有用它们编译的 Bash:P

于 2009-02-05T23:20:05.163 回答
1

我对克里斯·赖斯的回答略有不同。仍然处理一次尝试超时,但也允许多次重试,直到你放弃。

    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
于 2014-02-20T23:25:38.403 回答
1

所有 *nix 平台:

尝试如下 nc / netcat 命令。

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

-z 标志可用于告诉 nc 报告打开的端口,而不是启动连接。

-w 标志表示连接和最终网络读取超时

-G 标志是以秒为单位的连接超时

使用 -n 标志来处理 IP 地址而不是主机名。

例子:

# `nc -z -w 1 -G 1 google.com 80`
# `nc -z -w 1 -G 1 -n 123.234.1.18 80`
于 2016-09-05T08:25:16.180 回答
1

我的解决方案来自发布的解决方案。

require 'socket'
def is_port_open?(ip, port)
  begin
    s = Socket.tcp(ip, port, connect_timeout: 5)
    s.close
    return true
  rescue => e
    # possible exceptions:
    # - Errno::ECONNREFUSED
    # - Errno::EHOSTUNREACH
    # - Errno::ETIMEDOUT
    puts "#{e.class}: #{e.message}"
    return false
  end
end
于 2020-12-14T19:29:57.773 回答