可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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