Sockets: descubra la disponibilidad de puertos utilizando Java

123

¿Cómo determino programáticamente la disponibilidad de un puerto en una máquina dada usando Java?

es decir, dado un número de puerto, determinar si ya se está utilizando o no?

usuario54075
fuente
2
Si bien esta pregunta es sobre cómo saber si un puerto dado ya está en uso, es posible que haya aterrizado aquí tratando de encontrar una manera de obtener un número de puerto gratuito, que stackoverflow.com/questions/2675362/… (con enlace a mi esencia) .github.com / 3429822 ) cubre mejor.
vorburger
¿Con qué propósito? Si solo desea encontrar un socket vacío para escuchar, especifique cero. Cualquier técnica de escaneo es susceptible a problemas de ventana de tiempo: el puerto puede estar vacante cuando escanea y ocupado cuando va a reclamar.
Marqués de Lorne

Respuestas:

91

Esta es la implementación proveniente del proyecto Camel de Apache :

/**
 * Checks to see if a specific port is available.
 *
 * @param port the port to check for availability
 */
public static boolean available(int port) {
    if (port < MIN_PORT_NUMBER || port > MAX_PORT_NUMBER) {
        throw new IllegalArgumentException("Invalid start port: " + port);
    }

    ServerSocket ss = null;
    DatagramSocket ds = null;
    try {
        ss = new ServerSocket(port);
        ss.setReuseAddress(true);
        ds = new DatagramSocket(port);
        ds.setReuseAddress(true);
        return true;
    } catch (IOException e) {
    } finally {
        if (ds != null) {
            ds.close();
        }

        if (ss != null) {
            try {
                ss.close();
            } catch (IOException e) {
                /* should not be thrown */
            }
        }
    }

    return false;
}

También están verificando el DatagramSocket para verificar si el puerto está disponible en UDP y TCP.

Espero que esto ayude.

David Santamaria
fuente
55
Una buena variante de esto si tiene MINA es AvailablePortFinder.getNextAvailable (1024), que le proporciona el siguiente puerto no privilegiado.
Alain O'Dea
9
Sin embargo, esto no atrapa todo. Me ha mordido un nginx en ejecución en TCP 8000, mientras que la rutina anterior informa que 8000 está disponible. No tengo idea de por qué: sospecho que nginx hace algunas cosas furtivas (esto es en OS X). La solución para mí es hacer lo anterior y también abrir un nuevo Socket ("localhost", puerto) y luego devolver false si no obtenemos una excepción. Pensamientos?
Parcialmente nublado
2
El mismo problema en Windows que intenta detectar si se está ejecutando el servidor SMTP papercut. Una solución en la que abres una conexión a un puerto, en lugar de intentar unirte a ella (como sugiere el comentario de Clodys y la respuesta de Ivan) parece funcionar de manera más confiable.
stian
44
Interesante. La llamada a setReuseAddress () es completamente inútil después de que el socket ya está vinculado, y también es completamente contrario al propósito del código.
Marqués de Lorne
3
Este código está sujeto a problemas de ventana de tiempo. Si el objetivo es crear un ServerSocketpuerto de escucha, eso es lo que debe hacer y devolver el ServerSocket. Crearlo y luego cerrarlo y devolverlo proporciona cero garantía de que se ServerSocketpueda crear un subsiguiente usando ese puerto. Lo mismo para DatagramSocket.
Marqués de Lorne
41

Para Java 7, puede usar try-with-resource para obtener un código más compacto:

private static boolean available(int port) {
    try (Socket ignored = new Socket("localhost", port)) {
        return false;
    } catch (IOException ignored) {
        return true;
    }
}
eivindw
fuente
15
Esto no prueba si en el puerto está disponible. Comprueba si está en estado ESCUCHAR, si se puede acceder a la dirección IP, etc.
Marqués de Lorne
1
Además, esta prueba es bastante lenta (casi un segundo por puerto).
JMax
¿No debería devolver falso cuando se detecta una IOException?
Baroudi Safwen
@BaroudiSafwen Depende completamente de cuál sea realmente la excepción. Pues ConnectException: 'connection refused'sí, debería devolver falso. Para los tiempos de espera, nada de lo que podría devolver sería válido, ya que no se conoce la respuesta real. Es por eso que esta técnica es inútil para este propósito.
Marqués de Lorne el
36

Parece que a partir de Java 7, la respuesta de David Santamaria ya no funciona de manera confiable. Sin embargo, parece que aún puede usar un Socket de manera confiable para probar la conexión.

private static boolean available(int port) {
    System.out.println("--------------Testing port " + port);
    Socket s = null;
    try {
        s = new Socket("localhost", port);

        // If the code makes it this far without an exception it means
        // something is using the port and has responded.
        System.out.println("--------------Port " + port + " is not available");
        return false;
    } catch (IOException e) {
        System.out.println("--------------Port " + port + " is available");
        return true;
    } finally {
        if( s != null){
            try {
                s.close();
            } catch (IOException e) {
                throw new RuntimeException("You should handle this error." , e);
            }
        }
    }
}
TwentyMiles
fuente
Quiero verificar si hay un servidor proxy disponible para realizar llamadas en su nombre.
Dejell el
1
La respuesta de @DavidSantamaria nunca funcionó. Nada que ver con Java 7.
Marqués de Lorne
35

Si no le preocupa demasiado el rendimiento, siempre puede intentar escuchar en un puerto utilizando la clase ServerSocket . Si arroja una excepción, es probable que se esté utilizando.

public static boolean isAvailable(int portNr) {
  boolean portFree;
  try (var ignored = new ServerSocket(portNr)) {
      portFree = true;
  } catch (IOException e) {
      portFree = false;
  }
  return portFree;
}

EDITAR: si todo lo que intenta hacer es seleccionar un puerto libre new ServerSocket(0), encontrará uno para usted.

Spencer Ruport
fuente
Úselo finalmente para la limpieza (intente {...} catch {...} finally {if (socket! = Null) socket.close ();}
Hosam Aly
También puede establecer "portTaken = (socket == null);" al final en lugar de hacerlo en la captura.
Hosam Aly
1
No sentí que cayera dentro del alcance previsto de la pregunta del autor.
Spencer Ruport
1
¿Hay alguna manera de hacerlo sin intentar / atrapar? No me importaría usar ningún puerto disponible para mis propósitos, pero iterar sobre los números de puerto e intentar / atrapar cada uno hasta que encuentre uno libre es un poco inútil. ¿No hay un método 'nativo booleano disponible (int)' en alguna parte, que simplemente verifica?
Oren Shalev
27
El nuevo SeverSocket (0) seleccionará automáticamente un puerto libre.
Spencer Ruport
10

La siguiente solución está inspirada en la implementación SocketUtils de Spring-core (licencia de Apache).

En comparación con otras soluciones, su uso Socket(...)es bastante rápido (prueba de 1000 puertos TCP en menos de un segundo):

public static boolean isTcpPortAvailable(int port) {
    try (ServerSocket serverSocket = new ServerSocket()) {
        // setReuseAddress(false) is required only on OSX, 
        // otherwise the code will not work correctly on that platform          
        serverSocket.setReuseAddress(false);
        serverSocket.bind(new InetSocketAddress(InetAddress.getByName("localhost"), port), 1);
        return true;
    } catch (Exception ex) {
        return false;
    }
}       
JMax
fuente
3

Las soluciones basadas en sockets try / catch pueden no producir resultados precisos (la dirección del socket es "localhost" y en algunos casos el puerto podría estar "ocupado" no por la interfaz de bucle invertido y al menos en Windows he visto que esta prueba falla, es decir el prot falsamente declarado como disponible).

Hay una biblioteca genial llamada SIGAR , el siguiente código puede conectarte:

Sigar sigar = new Sigar();
int flags = NetFlags.CONN_TCP | NetFlags.CONN_SERVER | NetFlags.CONN_CLIENT;             NetConnection[] netConnectionList = sigar.getNetConnectionList(flags);
for (NetConnection netConnection : netConnectionList) {
   if ( netConnection.getLocalPort() == port )
        return false;
}
return true;
Shmil The Cat
fuente
Obtengo: "no sigar-x86-winnt.dll en java.library.path" Desafortunadamente, requiere la descarga de algún dll adicional que no es realmente viable en el entorno empresarial (no tengo control sobre el sistema CI). Tan mal ... Gracias de todos modos.
uthomas
@uthomas Supongo que todavía tiene control sobre el paquete de implementación de la aplicación, si este es el caso, siempre puede proporcionar las DLL / SO nativas de tiempo de ejecución de Sigar cerca de sus JAR y establecer la ruta JAVA lib pragmáticamente (a través de un simple hack puede influir incluso después de su aplicación ha sido lanzada por la JVM).
Shmil The Cat
2

Una limpieza de la respuesta señalada por David Santamaría:

/**
 * Check to see if a port is available.
 *
 * @param port
 *            the port to check for availability.
 */
public static boolean isPortAvailable(int port) {
    try (var ss = new ServerSocket(port); var ds = new DatagramSocket(port)) {
        return true;
    } catch (IOException e) {
        return false;
    }
}

Esto todavía está sujeto a una condición de carrera señalada por el usuario207421 en los comentarios a la respuesta de David Santamaria (algo podría tomar el puerto después de que este método cierre el ServerSockety DatagramSockety regrese).

Luke Hutchison
fuente
1

En mi caso, ayudó a intentar conectarse al puerto; si el servicio ya está presente, respondería.

    try {
        log.debug("{}: Checking if port open by trying to connect as a client", portNumber);
        Socket sock = new Socket("localhost", portNumber);          
        sock.close();
        log.debug("{}: Someone responding on port - seems not open", portNumber);
        return false;
    } catch (Exception e) {         
        if (e.getMessage().contains("refused")) {
            return true;
    }
        log.error("Troubles checking if port is open", e);
        throw new RuntimeException(e);              
    }
Ivan
fuente
1
No es lo que se pidió.
Marqués de Lorne
0

En mi caso tuve que usar la clase DatagramSocket.

boolean isPortOccupied(int port) {
    DatagramSocket sock = null;
    try {
        sock = new DatagramSocket(port);
        sock.close();
        return false;
    } catch (BindException ignored) {
        return true;
    } catch (SocketException ex) {
        System.out.println(ex);
        return true;
    }
}

No olvides importar primero

import java.net.DatagramSocket;
import java.net.BindException;
import java.net.SocketException;
kmchmk
fuente
Esto funciona para puertos UDP. La pregunta parece ser sobre los puertos TCP.
Marqués de Lorne
-2

He intentado algo como esto y funcionó muy bien conmigo

            Socket Skt;
            String host = "localhost";
            int i = 8983; // port no.

                 try {
                    System.out.println("Looking for "+ i);
                    Skt = new Socket(host, i);
                    System.out.println("There is a Server on port "
                    + i + " of " + host);
                 }
                 catch (UnknownHostException e) {
                    System.out.println("Exception occured"+ e);

                 }
                 catch (IOException e) {
                     System.out.println("port is not used");

                 }
mayu jadhv
fuente
1
No es lo que se pidió.
Marqués de Lorne
Y también una fuga de enchufe.
Marqués de Lorne