Ruby - 查看端口是否打开

问题描述 投票:0回答:9

我需要一种快速方法来查明给定端口是否已使用 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
9个回答
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

32
投票

所有其他现有答案都是不可取的。使用

Timeout
不鼓励。也许事情取决于 ruby 版本。至少从 2.0 开始,人们可以简单地使用:

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

对于较旧的 ruby,我能找到的最好方法是使用非阻塞模式,然后

select
。这里描述:


27
投票

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

16
投票

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

lsof
命令:

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

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


2
投票

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

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

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

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`

0
投票

很久没有关注这个问题了,但这是我用来查看某个端口是否正在侦听的脚本。请注意,我在已接受的答案中建议的方法并未使用。


#!/usr/bin/env ruby
# encoding: ASCII-8bit
# warn_indent: true
# frozen_string_literal: true

# rubocop:disable Lint/MissingCopEnableDirective
# rubocop:disable Style/StderrPuts

# Test a connection to a port on a host. A host name, IPv4 address, or IPv6
# address is allowed for the host. A service name or a port number is allowed
# for the port.

# Trap a few signals and exit. No problem if the operating system doesn't
# support one of the signals. Rescuing ArgumentError takes care of that.
%w[HUP INT PIPE QUIT TERM].each do |signame|
  Signal.trap(signame) do
    $stdout.puts
    exit 1
  end
rescue ArgumentError
  # the operating system doesn't support the signal, try the next list element
end

require 'socket'

def usage
  me = File.basename($PROGRAM_NAME)
  $stderr.puts "usage: #{me} [-v] host_name_or_address service_name_or_port_number"
  $stderr.puts "   or: #{me} [-v] host_name_or_address:service_name_or_port_number"
  exit 1
end

# address family to string or nil
def af_to_s(addr)
  if addr.ipv4?
    'IPv4'
  elsif addr.ipv6?
    'IPv6'
  end
end

# VERY simple command line argument handling follows...

verbose = if ARGV[0] == '-v'
            ARGV.shift
            true
          else
            false
          end

case ARGV.size

when 1
  if ARGV[0] =~ /^(.*):([^:]+)$/
    host = Regexp.last_match(1)
    port = Regexp.last_match(2)
  else
    usage
  end

when 2
  host, port = *ARGV

else
  usage
end

host = host.strip  # get thawed copy of host with white space removed
port = port.strip  # get thawed copy of port with white space removed

host = Regexp.last_match(1) if host =~ /^\[(.*)\]$/

usage if host.empty? || port.empty?

begin
  addrs = Addrinfo.getaddrinfo(host, port, nil, :STREAM, nil, Socket::AI_ALL)
rescue SocketError => e
  $stderr.puts "error: host #{host}: #{e}"
  exit 2
end

if addrs.empty? # can this ever happen?
  $stderr.puts "error: host #{host}: getaddrinfo failed to return anything!"
  exit 3
end

num_conn = 0
num_addrs = 0

addrs.each do |addr|
  family = af_to_s(addr)
  next if family.nil? # not an IPv4 or IPv6 address

  num_addrs += 1

  # Get an IPv4 or IPv6 socket (which is determined by the address family).
  s = Socket.new(addr.afamily, Socket::SOCK_STREAM)

  # Connect in non-blocking mode. If the connection can be made immediately,
  # then skip the select() call. If the connection can't be made immediately,
  # then use select() to wait up to 10 seconds for the connection to be
  # allowed. If select() times out, then just go on to the next address.

  begin
    s.connect_nonblock(addr.to_sockaddr)
  rescue Errno::EINPROGRESS
    next if IO.select(nil, [s], nil, 10).nil?

    begin
      # Socket is ready. Attempt real connection.
      s.connect_nonblock(addr.to_sockaddr)
    rescue Errno::EISCONN
      # connected
    rescue Errno::ECONNREFUSED => e
      $stderr.puts "error: #{e}" if verbose
      next
    rescue SystemCallError => e
      $stderr.puts "error: #{e}" if verbose
      next
    end
  rescue Errno::ENETUNREACH => e
    $stderr.puts "error: #{e}" if verbose
    next
  end

  puts "connected #{family} #{host}"
  num_conn += 1
end

if num_addrs.zero?
  $stderr.puts "error: no IPv4 or IPv6 addresses found for #{host}"
  exit 4
elsif num_conn.zero?
  $stderr.puts 'error: all connections failed'
  exit 5
else
  exit 0
end
© www.soinside.com 2019 - 2024. All rights reserved.