Modo consigliato per ottenere il nome host in Java


275

Quale dei seguenti è il modo migliore e più portatile per ottenere il nome host del computer corrente in Java?

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

vs

InetAddress.getLocalHost().getHostName()


Che stack tecnologico è questo?
Tom Redfern,

Penso che l'unico vero nome supportato uname (uts_name) provenga dal VMID RMI / JMX, ma questo è specifico dell'implementazione.
Controlla il

Risposte:


336

A rigor di termini - non hai altra scelta che chiamare uno hostname(1)o - su Unix gethostname(2). Questo è il nome del tuo computer. Qualsiasi tentativo di determinare il nome host tramite un indirizzo IP come questo

InetAddress.getLocalHost().getHostName()

è destinato a fallire in alcune circostanze:

  • L'indirizzo IP potrebbe non risolversi in nessun nome. Il motivo potrebbe essere un'errata configurazione DNS, un'impostazione errata del sistema o un'errata installazione del provider.
  • Un nome in DNS può avere molti alias chiamati CNAME. Questi possono essere risolti correttamente solo in una direzione: nome per indirizzo. La direzione inversa è ambigua. Qual è il nome "ufficiale"?
  • Un host può avere molti indirizzi IP diversi e ogni indirizzo può avere molti nomi diversi. Due casi comuni sono: una porta Ethernet ha diversi indirizzi IP "logici" o il computer ha diverse porte Ethernet. È configurabile se condividono un IP o hanno IP diversi. Questo si chiama "multihomed".
  • Un nome in DNS può essere risolto in più indirizzi IP. E non tutti quegli indirizzi devono trovarsi sullo stesso computer! (Usecase: una forma semplice di bilanciamento del carico)
  • Non parliamo nemmeno di indirizzi IP dinamici.

Inoltre, non confondere il nome di un indirizzo IP con il nome dell'host (nome host). Una metafora potrebbe rendere più chiaro:

C'è una grande città (server) chiamata "Londra". All'interno delle mura della città accadono molti affari. La città ha diverse porte (indirizzi IP). Ogni porta ha un nome ("North Gate", "River Gate", "Southampton Gate" ...) ma il nome della porta non è il nome della città. Inoltre, non puoi dedurre il nome della città usando il nome di una porta: "North Gate" catturerebbe metà delle città più grandi e non solo una città. Tuttavia - uno sconosciuto (pacchetto IP) cammina lungo il fiume e chiede a un locale: "Ho uno strano indirizzo: 'Rivergate, seconda a sinistra, terza casa'. Mi potete aiutare?" Il locale dice: "Certo, sei sulla strada giusta, semplicemente vai avanti e arriverai a destinazione entro mezz'ora".

Questo lo illustra praticamente penso.

La buona notizia è: il vero nome host di solito non è necessario. Nella maggior parte dei casi, funzionerà qualsiasi nome che si risolve in un indirizzo IP su questo host. (Lo sconosciuto potrebbe entrare nella città da Northgate, ma i locali utili tradurranno la parte "2a sinistra".)

Se i rimanenti casi d'angolo è necessario utilizzare la definitiva fonte di questa impostazione di configurazione - che è la funzione C gethostname(2). Quella funzione è anche chiamata dal programma hostname.


9
Non proprio la risposta che speravo, ma ora so come fare una domanda migliore, grazie.
Sam Hasler,


4
È un bel riassunto delle limitazioni che qualsiasi software (non solo un programma Java) ha nel determinare il nome dell'host nel mondo. Tuttavia, si noti che getHostName è implementato in termini di sistema operativo sottostante, presumibilmente allo stesso modo di hostname / gethostname. Su un sistema "normale", InetAddress.getLocalHost (). GetHostName () equivale a chiamare hostname / gethostname, quindi non si può davvero dire che uno fallisce in modo diverso dall'altro.
Peter Cardona,

System.err.println (Runtime.getRuntime () exec ( "hostname").); mi dà questo: java.lang.UNIXProcess@6eb2384f
user152468

5
L'implementazione di InetAddress.getLocalHost (). GetHostName () è in realtà molto deterministica :) Se segui il codice sorgente, alla fine chiama gethostname () su Windows e getaddrinfo () su sistemi Unixy. Il risultato è lo stesso dell'utilizzo del comando hostname OS. Ora il nome host può fornire una risposta che non si desidera utilizzare, ciò è possibile per molte ragioni. Generalmente, il software dovrebbe ottenere il nome host dell'utente in un file di configurazione, in questo modo è sempre il nome host corretto. È possibile utilizzare InetAddress.getLocalhost (). GetHostName () come predefinito se l'utente non fornisce un valore.
Greg,

93

InetAddress.getLocalHost().getHostName() è il modo più portatile.

exec("hostname")chiama effettivamente il sistema operativo per eseguire il hostnamecomando.

Ecco un paio di altre risposte correlate su SO:

EDIT: dovresti dare un'occhiata alla risposta di AH o alla risposta di Arnout Engelen per i dettagli sul perché questo potrebbe non funzionare come previsto, a seconda della tua situazione. Come risposta per questa persona che ha specificamente richiesto il portatile, penso ancora getHostName()che vada bene, ma fanno emergere alcuni punti positivi che dovrebbero essere considerati.


12
L'esecuzione di getHostName () provoca un errore alcune volte. Ad esempio, ecco cosa ottengo in un'istanza di Amazon EC2 AMI Linux: java.net.UnknownHostException: nome o servizio non noto java.net.InetAddress.getLocalHost (InetAddress.java:1438)
Marquez

1
Inoltre, InetAddress.getLocalHost()in alcuni casi restituisce il dispositivo di loopback. In tal caso, getHostName()restituisce "localhost", che non è molto utile.
Olenz,

54

Come altri hanno notato, ottenere il nome host basato sulla risoluzione DNS non è affidabile.

Dal momento che questa domanda è purtroppo ancora rilevante nel 2018 , vorrei condividere con voi la mia soluzione indipendente dalla rete, con alcuni test eseguiti su sistemi diversi.

Il codice seguente tenta di effettuare le seguenti operazioni:

  • Su Windows

    1. Leggi la COMPUTERNAMEvariabile d'ambiente attraverso System.getenv().

    2. Esegui hostname.exee leggi la risposta

  • Su Linux

    1. Leggi la HOSTNAMEvariabile d'ambiente attraversoSystem.getenv()

    2. Esegui hostnamee leggi la risposta

    3. Leggi /etc/hostname(per fare ciò sto eseguendo catpoiché lo snippet contiene già codice da eseguire e leggere. Tuttavia, leggere semplicemente il file sarebbe meglio).

Il codice:

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() : "";
    }
}

Risultati per diversi sistemi operativi:

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 Questo è un po 'strano poiché echo $HOSTNAMErestituisce il nome host corretto, ma System.getenv("HOSTNAME")non:

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

EDIT: Secondo legolas108 , System.getenv("HOSTNAME")funziona su Ubuntu 14.04 se si esegue export HOSTNAMEprima di eseguire il codice 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
"

I nomi delle macchine sono stati sostituiti, ma ho mantenuto la capitalizzazione e la struttura. Nota la nuova riga aggiuntiva durante l'esecuzione hostname, in alcuni casi potrebbe essere necessario tenerne conto.


3
Se export HOSTNAMEprima di eseguire il codice Java su Ubuntu 14.04 LTS viene anche restituito da System.getenv("HOSTNAME").
legolas108,

Devi esplicitamente export HOSTNAMEche Java lo utilizzi? Ho pensato che dovrebbe essere un ambiente predefinito come PATH.
David,

2
Sì, questo è uno dei bug di Java che non verrà mai corretto per motivi religiosi. Uno correlato è quello di poter impostare il nome del processo. I bug registrati quasi 20 anni fa.
Montabile il

Risposta eccellente, molto approfondita! Non so perché usi quello strano delimitatore, specialmente considerando che ogni output stampato ha una strana nuova riga. Indipendentemente da ciò, sto aggiornando la risposta per lavorare con MacOS, abbreviando l'indiceOf per contenere le chiamate e sistemando il nome della variabile del sistema operativo in modo che sia più coerente con le convenzioni del nome della variabile standard.
Charlie,

1
@Charlie il \Adelimitatore è solo un trucco per leggere l'intero InputStream usando a Scanner.
Malt,

24

InetAddress.getLocalHost().getHostName() è meglio (come spiegato da Nick), ma ancora non molto buono

Un host può essere conosciuto con molti nomi host diversi. Di solito cercherai il nome host che il tuo host ha in un contesto specifico.

Ad esempio, in un'applicazione Web, potresti cercare il nome host utilizzato da chiunque abbia emesso la richiesta che stai gestendo. Il modo migliore per individuarlo dipende dal framework utilizzato per l'applicazione Web.

In una sorta di altro servizio con connessione a Internet, vorrai il nome host attraverso il quale il tuo servizio è disponibile dall'esterno. A causa di proxy, firewall, ecc., Questo potrebbe non essere nemmeno un nome host sulla macchina su cui è installato il tuo servizio - potresti provare a trovare un valore predefinito ragionevole, ma dovresti sicuramente renderlo configurabile per chiunque lo installi.


18

Sebbene a questo argomento sia già stata data risposta, c'è altro da dire.

Prima di tutto: chiaramente abbiamo bisogno di alcune definizioni qui. La InetAddress.getLocalHost().getHostName()si dà il nome dell'host come si è visto dal punto di vista della rete . I problemi con questo approccio sono ben documentati nelle altre risposte: spesso richiede una ricerca DNS, è ambiguo se l'host ha più interfacce di rete e talvolta fallisce (vedi sotto).

Ma su qualsiasi sistema operativo c'è anche un altro nome. Un nome dell'host che viene definito molto presto nel processo di avvio, molto prima dell'inizializzazione della rete. Windows si riferisce a questo come nomecomputer , Linux lo chiama kernel hostname e Solaris usa la parola nodename . Mi piace di più la parola nomecomputer , quindi userò quella parola d'ora in poi.

Trovare il nomecomputer

  • Su Linux / Unix il nomecomputer è quello che si ottiene dalla funzione C gethostname(), o hostnamecomando da shell o HOSTNAMEvariabile d'ambiente in shell tipo Bash.

  • Su Windows il nomecomputer è quello che ottieni dalla variabile d'ambiente COMPUTERNAMEo dalla GetComputerNamefunzione Win32 .

Java non ha modo di ottenere quello che ho definito "nomecomputer". Certo, ci sono soluzioni alternative come descritto in altre risposte, come per le chiamate Windows System.getenv("COMPUTERNAME"), ma su Unix / Linux non c'è soluzione alternativa senza ricorrere a JNI / JNA o Runtime.exec(). Se non ti dispiace una soluzione JNI / JNA, c'è gethostname4j che è morto semplice e molto facile da usare.

Andiamo avanti con due esempi, uno da Linux e uno da Solaris, che dimostrano come si può facilmente entrare in una situazione in cui non è possibile ottenere il nomecomputer usando i metodi Java standard.

Esempio Linux

Su un sistema appena creato, in cui l'host durante l'installazione è stato chiamato "chicago", ora cambiamo il cosiddetto nome host del kernel:

$ hostnamectl --static set-hostname dallas

Ora il nome host del kernel è 'dallas', come risulta dal comando hostname:

$ hostname
dallas

Ma abbiamo ancora

$ cat /etc/hosts
127.0.0.1   localhost
127.0.0.1   chicago

Non c'è configurazione errata in questo. Significa solo che il nome di rete dell'host (o piuttosto il nome dell'interfaccia di loopback) è diverso dal nome computer dell'host.

Ora prova a eseguire InetAddress.getLocalHost().getHostName() e verrà lanciato java.net.UnknownHostException. Sei praticamente bloccato. Non c'è modo di recuperare né il valore "dallas" né il valore "chicago".

Esempio di Solaris

L'esempio seguente si basa su Solaris 11.3.

L'host è stato deliberatamente configurato in modo che il nome di loopback <> nodename.

In altre parole abbiamo:

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

e il contenuto di / etc / hosts:

:1 chicago localhost
127.0.0.1 chicago localhost loghost

e il risultato del comando hostname sarebbe:

$ hostname
dallas

Proprio come nell'esempio Linux con cui una chiamata InetAddress.getLocalHost().getHostName()fallirà

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

Proprio come nell'esempio di Linux, ora sei bloccato. Non c'è modo di recuperare né il valore "dallas" né il valore "chicago".

Quando farai davvero fatica con questo?

Molto spesso scoprirai che InetAddress.getLocalHost().getHostName()in effetti restituirà un valore uguale al nomecomputer. Quindi non ci sono problemi (tranne per l'overhead aggiunto della risoluzione dei nomi).

Il problema sorge in genere negli ambienti PaaS in cui esiste una differenza tra nomecomputer e il nome dell'interfaccia di loopback. Ad esempio, le persone segnalano problemi in Amazon EC2.

Rapporti bug / RFE

Un po 'di ricerca rivela questo rapporto RFE: link1 , link2 . Tuttavia, a giudicare dai commenti su tale rapporto, il problema sembra essere stato ampiamente frainteso dal team JDK, quindi è improbabile che venga affrontato.

Mi piace il confronto tra RFE e altri linguaggi di programmazione.


12

Le variabili di ambiente possono anche fornire un mezzo utile - COMPUTERNAMEsu Windows, HOSTNAMEsulla maggior parte delle moderne shell Unix / Linux.

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

Sto usando questi come metodi "supplementari" per InetAddress.getLocalHost().getHostName(), dal momento che come molte persone sottolineano, quella funzione non funziona in tutti gli ambienti.

Runtime.getRuntime().exec("hostname")è un altro possibile supplemento. In questa fase, non l'ho usato.

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;

6
StringUtils? Cos'è quello?? (So ​​cosa intendi, penso solo che sia un cattivo karma portare in una biblioteca esterna per questo unico scopo .. e per di più nemmeno menzionarlo)
peterh

23
È un karma negativo scrivere costantemente assegni "vuoti" manualmente. Moltissimi progetti eseguono tali controlli manualmente (come suggerisci) e in modo incoerente, con molti bug risultanti. Usa una biblioteca.
Thomas W,

15
Nel caso in cui qualcuno lo veda ed è effettivamente confuso StringUtils, è fornito dal progetto commons-lang di Apache. Si consiglia vivamente di usarlo o qualcosa di simile.
JBCP

In qualche modo System.getenv("HOSTNAME")è risultato nullo su Mac tramite Beanshell per Java, ma è PATHstato estratto ok. Potrei farlo anche echo $HOSTNAMEsu Mac, solo System.getenv ("HOSTNAME") sembra avere problemi. Strano.
David,

6

Il modo più portatile per ottenere il nome host del computer corrente in Java è il seguente:

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);
    }
}

8
Portabilità a parte, come si confronta questo metodo con il metodo nella domanda? Cosa sono i pro / contro?
Marquez,

4

Se non sei contrario all'utilizzo di una dipendenza esterna da Maven Central, ho scritto gethostname4j per risolvere questo problema da solo. Usa semplicemente JNA per chiamare la funzione gethostname di libc (o ottiene ComputerName su Windows) e te lo restituisce come una stringa.

https://github.com/mattsheppard/gethostname4j


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();
            }
        }
    }
}

1
Qual è il vantaggio di questo metodo?
Sam Hasler,

1
Questo non è valido, fornisce solo il nome della prima scheda NIC che non è un adattatore di loopback.
Steve-o

in realtà è il primo non loopback che ha un nome host ... non necessariamente il primo non loopback.
Adam Gent,

1

Just one-liner ... cross platform (Windows-Linux-Unix-Mac (Unix)) [Funziona sempre, non è richiesto il DNS]:

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

Hai finito !!


InetAddress.getLocalHost (). GetHostName () non funzionerà in Unix?
P Satish Patro,

1
Funziona ma richiede una risoluzione DNS, e se sei connesso a un tethering wifi dal tuo telefono (per citare solo un esempio) non avrà un DNS che conosce la macchina localhost e non funzionerà.
Dan Ortega,

-2

InetAddress.getLocalHost (). GetHostName () è il modo migliore per uscire dai due in quanto questa è la migliore astrazione a livello di sviluppatore.


Sto cercando una risposta che si occupa di un host conosciuto da molti nomi host.
Sam Hasler,

@SamHasler, qual è la tua situazione specifica? Come ha spiegato Arnout, ci sono diversi modi a seconda di ciò di cui hai bisogno.
Nick Knowlson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.