2009-02-05 20 views
38

Ho bisogno di un modo rapido per scoprire se una determinata porta è aperta con Ruby. Io attualmente sto trafficando con questo:Ruby: verifica se una porta è aperta

require 'socket' 

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

Funziona benissimo se la porta è aperta, ma il rovescio della medaglia è che a volte sarà solo sedersi e aspettare per 10-20 secondi e poi alla fine timeout, gettando un'eccezione ETIMEOUT (se la porta è chiusa). La mia domanda è quindi:

Questo codice può essere modificato per attendere solo un secondo (e restituire false se non si ottiene nulla indietro allora) o c'è un modo migliore per verificare se una determinata porta è aperta su un determinato host ?

Modifica: La chiamata di codice bash è accettabile anche se funziona su più piattaforme (ad esempio, Mac OS X, * nix e Cygwin), anche se preferisco il codice Ruby.

risposta

43

simile al seguente potrebbe funzionare:

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 
+0

funziona come un fascino! Grazie! –

+0

Ho avuto qualche problema con questo blocco (credo). Fondamentalmente il timeout non sarebbe in realtà timeout. Non so perché, ma la soluzione netcat ha funzionato bene al suo posto. –

+2

Questa risposta ha una soluzione che funziona anche su Windows: http://stackoverflow.com/a/3473208/362951 – mit

10

Solo per completezza, la Bash sarebbe qualcosa di simile:

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

-w 1 specifica un timeout di 1 secondo, -q 0 dice che, una volta connesso, chiudere la connessione non appena stdin restituisce EOF (che verrà eseguito immediatamente da /dev/null).

Bash ha anche i suoi propri servizi/UDP built-in TCP, ma sono un'opzione di compilazione e io non ho un Bash compilato con loro: P

+1

Sono piuttosto semplici: solo fingere/dev/{tcp}/HOST/PORT sono file :) – ephemient

+2

Per riferimento futuro, ho trovato questo come 'nc' sul mio sistema piuttosto che' netcat' – HXCaine

+1

Avviso: Su MacOS X, questo dà l'errore 'nc: opzione non valida - q'. Il seguente funziona su MacOS X e Ubuntu, e mi sembra più semplice: 'nc -z $ HOST $ PORT' – mercurial

26

Più Rubino sintassi idiomatica:

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 
+0

Questo ha dato un falso positivo per gli ingressi '192.0.2.0', 80, 10 che dovrebbero essere non validi (secondo http://stackoverflow.com/questions/10456044/what-is-a-good-invalid-ip-address-to-use-for-unit-tests). Ho ottenuto lo stesso risultato con Ruby 1.9.3p448 e 2.0.0p195, entrambi su Mac. In quali situazioni questo metodo riesce a restituire false? (Ho anche provato a scrivere sulla presa prima di chiuderla, ma ciò è comunque tornato vero!) –

+0

Ho appena provato '0.0.0.0', 80, 1 e questo ha dato anche true. –

+0

funziona bene per me! – sunsations

1

La mia leggera variazione rispetto alla risposta di Chris Rice. Gestisce ancora il timeout su un singolo tentativo, ma consente anche più tentativi fino a quando non ci si arrende.

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 
9

ho recentemente si avvicinò con questa soluzione, facendo uso del comando unix lsof:

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

Questo è stato molto bello per me. Volevo introdurre un sistema di assegnazione delle porte alle macchine virtuali in vagabondo e ho scritto questo one-liner per verificare se la porta che stavo per assegnare fosse aperta o meno: 'vms ['port'] + = 1 durante le porte. includere? vms ['port'] o system ("lsof -i: # {vms ['port']}") ' – Dannid

+1

Funziona solo per l'utente che ha effettuato l'accesso. Per lavorare su tutta la linea, usare 'sudo lsof -i: ' – alpinweis

+0

Ho dovuto rimuovere '!' (Non operatore) per farlo funzionare. –

7

Tutti gli altri risposta esistenti sono indesiderabili. L'utilizzo di Timeout è discouraged. Forse le cose dipendono dalla versione rubino. Almeno dal 2.0 si può utilizzare semplicemente:

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

Per le versioni precedenti rubino il metodo migliore che ho trovato è usare la modalità non-blocking e poi select. Descritto qui:

+3

Ha funzionato perfettamente per me: 'port_is_open = Socket.tcp (host, porta, connect_timeout: 5) {true} rescue false'. È facile espandersi da un one-liner per salvare le specifiche eccezioni necessarie. – anothermh

2

Tutte le piattaforme * nix: comando

try NC/netcat come segue.

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

L'indicatore -z può essere utilizzato per comunicare a nc di segnalare le porte aperte, anziché avviare una connessione.

La bandiera -w significa Timeout per connette e netto finale recita

La bandiera -G è timeout di connessione in secondi

Utilizzare il flag -n per lavorare con l'indirizzo IP piuttosto che il nome host.

Esempi:

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