【问题标题】:Ruby - See if a port is openRuby - 查看端口是否打开
【发布时间】:2010-10-05 18:10:25
【问题描述】:

我需要一种快速的方法来确定给定端口是否已使用 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 代码。

【问题讨论】:

    标签: ruby bash network-programming port


    【解决方案1】:

    类似以下的方法可能会起作用:

    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
    

    【讨论】:

    • 我在这个阻塞上遇到了一些麻烦(我认为)。基本上超时实际上不会超时。不知道为什么,但是 netcat 解决方案在它的位置上运行良好。
    • 这个答案有一个同样适用于 Windows 的解决方案:stackoverflow.com/a/3473208/362951
    • 不应该交换真假吗?
    • 在其中添加一些命令行选项,您就有了一个功能程序!
    • 请注意这段代码。 Timeout::timeout 真的很糟糕,尽管它可以在较小的脚本中“工作”,但它会在较大的应用程序中引入难以跟踪的错误。一些阅读:jvns.ca/blog/2015/11/27/…
    【解决方案2】:

    为了完整起见,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

    【讨论】:

    • 它们非常简单:假装 /dev/{tcp}/HOST/PORT 是文件 :)
    • 为了将来参考,我在我的系统上发现这是nc,而不是netcat
    • 警告:在 MacOS X 上,这会给出错误 nc: invalid option -- q。以下适用于 MacOS X 和 Ubuntu,对我来说似乎更简单:nc -z $HOST $PORT
    【解决方案3】:

    更多 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
    

    【讨论】:

    • 这对输入“192.0.2.0”、80、10 给出了误报,这应该是无效的(根据stackoverflow.com/questions/10456044/…)。我在 Mac 上使用 Ruby 1.9.3p448 和 2.0.0p195 得到了相同的结果。在什么情况下这个方法设法返回 false? (我什至尝试在关闭套接字之前写入套接字,但仍然返回 true!)
    • 我认为这个函数 def 在超时之前缺少一个开始语句,或者这是可选的? (rescue Timeout::Error 应该在 begin 块中,不是吗?)
    • @nash Ruby 允许救援子句属于方法本身。实际上,该方法是一个隐式块。
    • Timeout块放在内层begin...rescue中不是更好,在同一个缩进级别分析错误!
    【解决方案4】:

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

        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
    

    【讨论】:

      【解决方案5】:

      我最近想出了这个解决方案,利用 unix lsof 命令:

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

      【讨论】:

      • 这对我来说非常好。我想在 vagrant 中引入一个为虚拟机分配端口的系统,并写了这个单行代码来检查我要分配的端口是否打开:vms['port'] += 1 while ports.include? vms['port'] or system("lsof -i:#{vms['port']}")
      • 这仅适用于登录用户。要全面工作,请使用sudo lsof -i:&lt;port&gt;
      • 我必须删除!(不是操作员)才能让它工作。
      • "open" 这里的意思是 "in use" 所以当端口是 "free" 时返回 false
      【解决方案6】:

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

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

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

      【讨论】:

      • 非常适合我:port_is_open = Socket.tcp(host, port, connect_timeout: 5) { true } rescue false。从单行扩展以挽救所需的特定异常很容易。
      【解决方案7】:

      所有 *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`
      

      【讨论】:

        【解决方案8】:

        我的解决方案来源于发布的解决方案。

        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
        

        【讨论】:

        • 这是一个更好的答案,谢谢!
        猜你喜欢
        • 2012-08-03
        • 1970-01-01
        • 2012-06-09
        • 1970-01-01
        • 1970-01-01
        • 2022-01-26
        • 2023-03-07
        • 2013-10-12
        相关资源
        最近更新 更多