Forma recomendada de obtener el nombre de host en Java

275

¿Cuál de las siguientes es la mejor y más portátil forma de obtener el nombre de host de la computadora actual en Java?

Runtime.getRuntime().exec("hostname")

vs

InetAddress.getLocalHost().getHostName()

Mahendra
fuente
¿Qué pila de tecnología es esta?
Tom Redfern
Creo que el único nombre real respaldado por uname (uts_name) es del VMID RMI / JMX, pero esto es específico de la implementación.
eckes

Respuestas:

336

Hablando estrictamente, no tiene más remedio que llamar a cualquiera de los dos hostname(1), en Unix gethostname(2). Este es el nombre de tu computadora. Cualquier intento de determinar el nombre de host por una dirección IP como esta

InetAddress.getLocalHost().getHostName()

está obligado a fallar en algunas circunstancias:

  • La dirección IP podría no resolverse en ningún nombre. La mala configuración de DNS, la mala configuración del sistema o la mala configuración del proveedor pueden ser la razón de esto.
  • Un nombre en DNS puede tener muchos alias llamados CNAME. Estos solo pueden resolverse en una dirección correctamente: nombre a dirección. La dirección inversa es ambigua. ¿Cuál es el nombre "oficial"?
  • Un host puede tener muchas direcciones IP diferentes, y cada dirección puede tener muchos nombres diferentes. Dos casos comunes son: un puerto ethernet tiene varias direcciones IP "lógicas" o la computadora tiene varios puertos ethernet. Es configurable si comparten una IP o tienen IP diferentes. Esto se llama "multihomed".
  • Un nombre en DNS puede resolver varias direcciones IP. ¡Y no todas esas direcciones deben estar ubicadas en la misma computadora! (Caso de uso: una forma simple de equilibrio de carga)
  • Ni siquiera empecemos a hablar de direcciones IP dinámicas.

Tampoco confunda el nombre de una dirección IP con el nombre del host (nombre del host). Una metáfora podría aclararlo:

Hay una gran ciudad (servidor) llamada "Londres". Dentro de las murallas de la ciudad ocurren muchos negocios. La ciudad tiene varias puertas (direcciones IP). Cada puerta tiene un nombre ("Puerta Norte", "Puerta del Río", "Puerta de Southampton" ...) pero el nombre de la puerta no es el nombre de la ciudad. Tampoco puede deducir el nombre de la ciudad utilizando el nombre de una puerta: "Puerta Norte" atraparía la mitad de las ciudades más grandes y no solo una ciudad. Sin embargo, un extraño (paquete de IP) camina a lo largo del río y le pregunta a un local: "Tengo una dirección extraña: 'Rivergate, segunda izquierda, tercera casa'. ¿Me pueden ayudar?" El local dice: "Por supuesto, está en el camino correcto, simplemente siga adelante y llegará a su destino dentro de media hora".

Esto lo ilustra más o menos, creo.

La buena noticia es: el nombre de host real generalmente no es necesario. En la mayoría de los casos, cualquier nombre que se resuelva en una dirección IP en este host funcionará. (El extraño podría ingresar a la ciudad por Northgate, pero los lugareños útiles traducen la parte de "segunda izquierda").

Si los casos de esquina restantes debe utilizar la definitiva fuente de esta opción de configuración - que es la función C gethostname(2). Esa función también es llamada por el programa hostname.

AH
fuente
9
No era la respuesta que esperaba, pero ahora sé cómo hacer una mejor pregunta, gracias.
Sam Hasler
3
Ver también stackoverflow.com/questions/6050011/…
Raedwald
44
Esa es una buena descripción de las limitaciones que cualquier software (no solo un programa Java) tiene para determinar el nombre del host en el mundo. Sin embargo, tenga en cuenta que getHostName se implementa en términos del sistema operativo subyacente, presumiblemente de la misma forma que hostname / gethostname. En un sistema "normal", InetAddress.getLocalHost (). GetHostName () es equivalente a llamar a hostname / gethostname, por lo que realmente no puede decir que uno falla de la manera que el otro no.
Peter Cardona
System.err.println (Runtime.getRuntime (). Exec ("hostname")); me da esto: java.lang.UNIXProcess@6eb2384f
user152468
55
La implementación de InetAddress.getLocalHost (). GetHostName () es en realidad muy determinista :) Si sigue el código fuente, finalmente llama gethostname () en Windows y getaddrinfo () en sistemas Unixy. El resultado es el mismo que usar el comando de nombre de host del sistema operativo. Ahora el nombre de host puede proporcionar una respuesta que no desea utilizar, que es posible por muchas razones. En general, el software debe obtener el nombre de host del usuario en un archivo de configuración, de esa manera, siempre es el nombre de host correcto. Puede usar InetAddress.getLocalhost (). GetHostName () como valor predeterminado si el usuario no proporciona un valor.
Greg
93

InetAddress.getLocalHost().getHostName() Es la forma más portátil.

exec("hostname")en realidad llama al sistema operativo para ejecutar el hostnamecomando.

Aquí hay otras respuestas relacionadas sobre SO:

EDITAR: debe echar un vistazo a la respuesta de AH o la respuesta de Arnout Engelen para obtener detalles sobre por qué esto podría no funcionar como se esperaba, dependiendo de su situación. Como respuesta para esta persona que solicitó específicamente un dispositivo portátil, sigo pensando que getHostName()está bien, pero mencionan algunos puntos buenos que deberían considerarse.

Nick Knowlson
fuente
12
La ejecución de getHostName () ocasiona un error algunas veces. Por ejemplo, esto es lo que obtengo en una instancia de Amazon EC2 AMI Linux: java.net.UnknownHostException: nombre o servicio desconocido java.net.InetAddress.getLocalHost (InetAddress.java:1438)
Márquez
1
Además, InetAddress.getLocalHost()en algunos casos devuelve el dispositivo de bucle invertido. En ese caso, getHostName()devuelve "localhost", que no es muy útil.
olenz
54

Como otros han señalado, obtener el nombre de host basado en la resolución DNS no es confiable.

Como desafortunadamente esta pregunta aún es relevante en 2018 , me gustaría compartir con ustedes mi solución independiente de la red, con algunas ejecuciones de prueba en diferentes sistemas.

El siguiente código intenta hacer lo siguiente:

  • En Windows

    1. Lea la COMPUTERNAMEvariable de entorno a través System.getenv().

    2. Ejecuta hostname.exey lee la respuesta

  • En Linux

    1. Lea la HOSTNAMEvariable de entorno a través deSystem.getenv()

    2. Ejecuta hostnamey lee la respuesta

    3. Leer /etc/hostname(para hacer esto, lo estoy ejecutando catya que el fragmento ya contiene código para ejecutar y leer. Simplemente leer el archivo sería mejor).

El código:

public static void main(String[] args) throws IOException {
    String os = System.getProperty("os.name").toLowerCase();

    if (os.contains("win")) {
        System.out.println("Windows computer name through env:\"" + System.getenv("COMPUTERNAME") + "\"");
        System.out.println("Windows computer name through exec:\"" + execReadToString("hostname") + "\"");
    } else if (os.contains("nix") || os.contains("nux") || os.contains("mac os x")) {
        System.out.println("Unix-like computer name through env:\"" + System.getenv("HOSTNAME") + "\"");
        System.out.println("Unix-like computer name through exec:\"" + execReadToString("hostname") + "\"");
        System.out.println("Unix-like computer name through /etc/hostname:\"" + execReadToString("cat /etc/hostname") + "\"");
    }
}

public static String execReadToString(String execCommand) throws IOException {
    try (Scanner s = new Scanner(Runtime.getRuntime().exec(execCommand).getInputStream()).useDelimiter("\\A")) {
        return s.hasNext() ? s.next() : "";
    }
}

Resultados para diferentes sistemas operativos:

macOS 10.13.2

Unix-like computer name through env:"null"
Unix-like computer name through exec:"machinename
"
Unix-like computer name through /etc/hostname:""

OpenSuse 13.1

Unix-like computer name through env:"machinename"
Unix-like computer name through exec:"machinename
"
Unix-like computer name through /etc/hostname:""

Ubuntu 14.04 LTS Este es un poco extraño ya que echo $HOSTNAMEdevuelve el nombre de host correcto, pero System.getenv("HOSTNAME")no:

Unix-like computer name through env:"null"
Unix-like computer name through exec:"machinename
"
Unix-like computer name through /etc/hostname:"machinename
"

EDITAR: Según legolas108 , System.getenv("HOSTNAME")funciona en Ubuntu 14.04 si lo ejecuta export HOSTNAMEantes de ejecutar el código Java.

Windows 7

Windows computer name through env:"MACHINENAME"
Windows computer name through exec:"machinename
"

Windows 10

Windows computer name through env:"MACHINENAME"
Windows computer name through exec:"machinename
"

Los nombres de las máquinas han sido reemplazados pero mantuve las mayúsculas y la estructura. Tenga en cuenta la nueva línea adicional al ejecutar hostname, puede que tenga que tenerla en cuenta en algunos casos.

Malta
fuente
3
Si export HOSTNAMEantes de ejecutar el código Java en Ubuntu 14.04 LTS también lo devuelve System.getenv("HOSTNAME").
legolas108
¿Tienes que explícitamente export HOSTNAMEque Java lo use? Pensé que debería ser un entorno predeterminado como PATH.
David
2
Sí, este es uno de los errores de Java que nunca se solucionarán por razones religiosas. Una relacionada es poder establecer el nombre del proceso. Errores registrados hace casi 20 años.
Aplicable el
Excelente respuesta, muy minuciosa! No sé por qué usas ese delimitador extraño, especialmente teniendo en cuenta que cada salida impresa tiene una nueva línea extraña. De todos modos, estoy actualizando la respuesta para trabajar con MacOS, acortar el indexOf a contiene llamadas y arreglar el nombre de la variable del sistema operativo para que sea más coherente con las convenciones de nombre de variable estándar.
Charlie
1
@Charlie el \Adelimitador es solo un truco para leer todo el InputStream usando a Scanner.
Malta
24

InetAddress.getLocalHost().getHostName() es mejor (como explica Nick), pero aún no es muy bueno

Un host se puede conocer con muchos nombres de host diferentes. Por lo general, buscará el nombre de host que tiene en un contexto específico.

Por ejemplo, en una aplicación web, es posible que esté buscando el nombre de host utilizado por quien emitió la solicitud que está manejando actualmente. La mejor manera de encontrarla depende del marco que esté utilizando para su aplicación web.

En algún otro tipo de servicio con conexión a Internet, querrá el nombre de host para el que está disponible su servicio desde el 'exterior'. Debido a los servidores proxy, los cortafuegos, etc., es posible que este ni siquiera sea un nombre de host en la máquina en la que está instalado su servicio; puede intentar obtener un valor predeterminado razonable, pero definitivamente debe hacer que esto sea configurable para quien lo instale.

Arnout Engelen
fuente
18

Aunque este tema ya ha sido respondido, hay más que decir.

Primero que nada: Claramente necesitamos algunas definiciones aquí. El InetAddress.getLocalHost().getHostName()le da el nombre del host como se ve desde una perspectiva de red . Los problemas con este enfoque están bien documentados en las otras respuestas: a menudo requiere una búsqueda de DNS, es ambiguo si el host tiene múltiples interfaces de red y simplemente falla a veces (ver más abajo).

Pero en cualquier sistema operativo también hay otro nombre. Un nombre del host que se define muy temprano en el proceso de arranque, mucho antes de que se inicialice la red. Windows se refiere a esto como nombre de computadora , Linux lo llama nombre de host del núcleo y Solaris usa la palabra nombrenodo . Me gusta más la palabra computername , así que usaré esa palabra de ahora en adelante.

Encontrar el nombre de la computadora

  • En Linux / Unix, el nombre de la computadora es lo que obtienes de la función C gethostname(), o el hostnamecomando de la shell o HOSTNAMEla variable de entorno en shells tipo Bash.

  • En Windows, el nombre del equipo es lo que obtienes de la variable de entorno COMPUTERNAMEo la GetComputerNamefunción Win32 .

Java no tiene forma de obtener lo que he definido como 'nombre de computadora'. Claro, hay soluciones como se describe en otras respuestas, como para las llamadas de Windows System.getenv("COMPUTERNAME"), pero en Unix / Linux no hay una buena solución sin recurrir a JNI / JNA o Runtime.exec(). Si no le importa una solución JNI / JNA, hay gethostname4j, que es muy simple y muy fácil de usar.

Pasemos con dos ejemplos, uno de Linux y otro de Solaris, que demuestran cómo puede entrar fácilmente en una situación en la que no puede obtener el nombre de la computadora utilizando métodos estándar de Java.

Ejemplo de Linux

En un sistema recién creado, donde el host durante la instalación ha sido nombrado como 'Chicago', ahora cambiamos el llamado nombre de host del núcleo:

$ hostnamectl --static set-hostname dallas

Ahora el nombre de host del núcleo es 'dallas', como se desprende del comando hostname:

$ hostname
dallas

Pero todavia tenemos

$ cat /etc/hosts
127.0.0.1   localhost
127.0.0.1   chicago

No hay una mala configuración en esto. Simplemente significa que el nombre en red del host (o más bien el nombre de la interfaz de bucle invertido) es diferente del nombre de la computadora del host.

Ahora, intente ejecutar InetAddress.getLocalHost().getHostName() y arrojará java.net.UnknownHostException. Básicamente estás atrapado. No hay forma de recuperar ni el valor 'dallas' ni el valor 'chicago'.

Ejemplo de Solaris

El siguiente ejemplo se basa en Solaris 11.3.

El host se configuró deliberadamente para que el nombre de bucle invertido <> nombrenodo.

En otras palabras tenemos:

$ svccfg -s system/identity:node listprop config
...
...
config/loopback             astring        chicago
config/nodename             astring        dallas

y el contenido de / etc / hosts:

:1 chicago localhost
127.0.0.1 chicago localhost loghost

y el resultado del comando hostname sería:

$ hostname
dallas

Al igual que en el ejemplo de Linux, una llamada a InetAddress.getLocalHost().getHostName()fallará con

java.net.UnknownHostException: dallas:  dallas: node name or service name not known

Al igual que el ejemplo de Linux, ahora estás atascado. No hay forma de recuperar ni el valor 'dallas' ni el valor 'chicago'.

¿Cuándo realmente lucharás con esto?

Muy a menudo encontrará que InetAddress.getLocalHost().getHostName(), de hecho, devolverá un valor que es igual al nombre del equipo. Por lo tanto, no hay problema (a excepción de la sobrecarga adicional de la resolución de nombres).

El problema surge típicamente en entornos PaaS donde hay una diferencia entre el nombre de la computadora y el nombre de la interfaz de bucle invertido. Por ejemplo, las personas informan problemas en Amazon EC2.

Informes de errores / RFE

Un poco de búsqueda revela este informe RFE: link1 , link2 . Sin embargo, a juzgar por los comentarios sobre ese informe, el tema parece haber sido mal entendido por el equipo de JDK, por lo que es poco probable que se aborde.

Me gusta la comparación en el RFE con otros lenguajes de programación.

Peter
fuente
12

Las variables de entorno también pueden proporcionar un medio útil, COMPUTERNAMEen Windows, HOSTNAMEen la mayoría de los shells modernos de Unix / Linux.

Ver: https://stackoverflow.com/a/17956000/768795

Los estoy utilizando como métodos "complementarios" para InetAddress.getLocalHost().getHostName(), como señalan varias personas, esa función no funciona en todos los entornos.

Runtime.getRuntime().exec("hostname")Es otro posible suplemento. En este momento, no lo he usado.

import java.net.InetAddress;
import java.net.UnknownHostException;

// try InetAddress.LocalHost first;
//      NOTE -- InetAddress.getLocalHost().getHostName() will not work in certain environments.
try {
    String result = InetAddress.getLocalHost().getHostName();
    if (StringUtils.isNotEmpty( result))
        return result;
} catch (UnknownHostException e) {
    // failed;  try alternate means.
}

// try environment properties.
//      
String host = System.getenv("COMPUTERNAME");
if (host != null)
    return host;
host = System.getenv("HOSTNAME");
if (host != null)
    return host;

// undetermined.
return null;
Thomas W
fuente
66
StringUtils? ¿¿Que es eso?? (Sé lo que quieres decir, solo creo que es un mal karma traer una biblioteca externa para este único propósito ... y además de eso ni siquiera mencionarlo)
Peter
23
Es un mal karma escribir constantemente cheques "vacíos" manualmente. En muchos proyectos se realizan tales comprobaciones manualmente (de la forma que sugiere) y de forma inconsistente, con muchos errores resultantes. Usa una biblioteca.
Thomas W
15
En caso de que alguien vea esto y se confunda StringUtils, es proporcionado por el proyecto Apache commons-lang. Usarlo, o algo así, es muy recomendable.
JBCP
De alguna manera System.getenv("HOSTNAME")salió nulo en Mac a través de Beanshell para Java, pero PATHse extrajo bien. También podría hacerlo echo $HOSTNAMEen Mac, solo System.getenv ("HOSTNAME") parece tener problemas. Extraño.
David
6

La forma más portátil de obtener el nombre de host de la computadora actual en Java es la siguiente:

import java.net.InetAddress;
import java.net.UnknownHostException;

public class getHostName {

    public static void main(String[] args) throws UnknownHostException {
        InetAddress iAddress = InetAddress.getLocalHost();
        String hostName = iAddress.getHostName();
        //To get  the Canonical host name
        String canonicalHostName = iAddress.getCanonicalHostName();

        System.out.println("HostName:" + hostName);
        System.out.println("Canonical Host Name:" + canonicalHostName);
    }
}
Desta Haileselassie Hagos
fuente
8
Dejando de lado la portabilidad, ¿cómo se compara este método con el método de la pregunta? ¿Qué son los pros / contras?
Márquez
4

Si no está en contra de usar una dependencia externa de maven central, escribí gethostname4j para resolver este problema por mí mismo. Simplemente usa JNA para llamar a la función gethostname de libc (u obtiene ComputerName en Windows) y se la devuelve como una cadena.

https://github.com/mattsheppard/gethostname4j

Matt Sheppard
fuente
3
hostName == null;
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
{
    while (interfaces.hasMoreElements()) {
        NetworkInterface nic = interfaces.nextElement();
        Enumeration<InetAddress> addresses = nic.getInetAddresses();
        while (hostName == null && addresses.hasMoreElements()) {
            InetAddress address = addresses.nextElement();
            if (!address.isLoopbackAddress()) {
                hostName = address.getHostName();
            }
        }
    }
}
ThuVien247.net
fuente
1
¿Cuál es el beneficio de este método?
Sam Hasler
1
Esto no es válido, solo proporciona el nombre de la primera NIC que no es un adaptador de bucle invertido.
Steve-o
en realidad es el primer no loopback que tiene un nombre de host ... no necesariamente el primer no loopback.
Adam Gent
1

Solo una línea ... multiplataforma (Windows-Linux-Unix-Mac (Unix)) [Siempre funciona, no se requiere DNS]:

String hostname = new BufferedReader(
    new InputStreamReader(Runtime.getRuntime().exec("hostname").getInputStream()))
   .readLine();

¡Ya terminaste!

Dan Ortega
fuente
InetAddress.getLocalHost (). GetHostName () no funcionará en Unix?
P Satish Patro
1
Eso funciona pero requiere resolución dns, y si está conectado a una conexión wifi desde su teléfono (por mencionar solo un ejemplo), no tendrá un DNS que conozca la máquina localhost y no funcionará.
Dan Ortega
-2

InetAddress.getLocalHost (). GetHostName () es la mejor manera de salir de los dos, ya que esta es la mejor abstracción a nivel de desarrollador.

java_mouse
fuente
Estoy buscando una respuesta que trate con un host conocido por muchos nombres de host.
Sam Hasler
@SamHasler, ¿cuál es su situación específica? Como explicó Arnout, hay diferentes formas según lo que necesite.
Nick Knowlson