Zalecany sposób uzyskania nazwy hosta w Javie

274

Który z poniższych jest najlepszym i najbardziej przenośnym sposobem na uzyskanie nazwy hosta bieżącego komputera w Javie?

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

vs

InetAddress.getLocalHost().getHostName()

Mahendra
źródło
Co to za stos technologii?
tom redfern
Wydaje mi się, że jedyna rzeczywista nazwa poparta uname (nazwa_użytkownika) pochodzi z VMID RMI / JMX, ale jest to specyficzne dla implementacji.
eckes

Odpowiedzi:

336

Ściśle mówiąc - nie masz wyboru, musisz zadzwonić albo hostname(1)- na Uniksie gethostname(2). To jest nazwa twojego komputera. Każda próba ustalenia nazwy hosta na podstawie adresu IP takiego jak ten

InetAddress.getLocalHost().getHostName()

w niektórych okolicznościach może się nie powieść:

  • Adres IP może nie zostać przetworzony na żadną nazwę. Przyczyną może być zła konfiguracja DNS, zła konfiguracja systemu lub zła konfiguracja dostawcy.
  • Nazwa w DNS może mieć wiele aliasów zwanych CNAME. Można je rozwiązać tylko w jednym kierunku: nazwa na adres. Odwrotny kierunek jest niejednoznaczny. Która to „oficjalna” nazwa?
  • Host może mieć wiele różnych adresów IP - a każdy adres może mieć wiele różnych nazw. Dwa typowe przypadki to: Jeden port Ethernet ma kilka „logicznych” adresów IP lub komputer ma kilka portów Ethernet. Można skonfigurować, czy mają one wspólny adres IP, czy mają różne adresy IP. Nazywa się to „multihomed”.
  • Jedna nazwa w DNS może rozwiązać kilka adresów IP. I nie wszystkie z tych adresów muszą znajdować się na tym samym komputerze! (Przypadek użycia: Prosta forma równoważenia obciążenia)
  • Nie mówmy nawet o dynamicznych adresach IP.

Nie należy także mylić nazwy adresu IP z nazwą hosta (nazwa hosta). Metafora może to wyjaśnić:

Istnieje duże miasto (serwer) o nazwie „Londyn”. W obrębie murów miejskich dzieje się wiele interesów. Miasto ma kilka bram (adresy IP). Każda brama ma nazwę („North Gate”, „River Gate”, „Southampton Gate” ...), ale nazwa bramy nie jest nazwą miasta. Nie można też wydedukować nazwy miasta na podstawie nazwy bramy - „Brama Północna” złapałaby połowę większych miast, a nie tylko jedno miasto. Jednak - nieznajomy (pakiet IP) idzie wzdłuż rzeki i pyta miejscowego: „Mam dziwny adres:„ Rivergate, drugi w lewo, trzeci dom ”. Czy możesz mi pomóc?” Miejscowy mówi: „Oczywiście jesteś na dobrej drodze, po prostu idź przed siebie, a dotrzesz do celu w ciągu pół godziny”.

Myślę, że to ilustruje.

Dobra wiadomość jest taka: prawdziwa nazwa hosta zwykle nie jest konieczna. W większości przypadków wystarczy nazwa, która zamienia się w adres IP na tym hoście. (Nieznajomy może wejść do miasta przez Northgate, ale pomocni miejscowi tłumaczą część „2. lewa”).

W przypadku pozostałych przypadkach narożnych należy użyć ostateczne źródło tego ustawienia konfiguracji - co jest funkcja C gethostname(2). Ta funkcja jest również wywoływana przez program hostname.

AH
źródło
9
Nie do końca oczekiwałem odpowiedzi, ale teraz wiem, jak zadać lepsze pytanie, dzięki.
Sam Hasler,
3
Zobacz także stackoverflow.com/questions/6050011/…
Raedwald,
4
To miły zapis ograniczeń, które każde oprogramowanie (nie tylko program Java) ma przy określaniu nazwy hosta na świecie. Należy jednak pamiętać, że getHostName jest zaimplementowany w odniesieniu do podstawowego systemu operacyjnego, prawdopodobnie w taki sam sposób, jak nazwa hosta / gethostname. W „normalnym” systemie funkcja InetAddress.getLocalHost (). GetHostName () jest równoważna z wywołaniem nazwy hosta / gethostname, więc nie można tak naprawdę powiedzieć, że jeden zawodzi w sposób inny niż drugi.
Peter Cardona
System.err.println (Runtime.getRuntime (). Exec („nazwa hosta”)); daje mi to: java.lang.UNIXProcess@6eb2384f
user152468
5
Implementacja InetAddress.getLocalHost (). GetHostName () jest w rzeczywistości bardzo deterministyczna :) Jeśli podążasz za kodem źródłowym, to ostatecznie wywołuje gethostname () w systemie Windows i getaddrinfo () w systemach Unixy. Rezultat jest taki sam, jak użycie polecenia hostname systemu operacyjnego. Teraz nazwa hosta może dostarczyć odpowiedź, której nie chcesz używać, co jest możliwe z wielu powodów. Zasadniczo oprogramowanie powinno pobrać nazwę hosta od użytkownika w pliku konfiguracyjnym, w ten sposób zawsze jest to poprawna nazwa hosta. Możesz użyć InetAddress.getLocalhost (). GetHostName () jako domyślny, jeśli użytkownik nie poda wartości.
Greg
93

InetAddress.getLocalHost().getHostName() jest bardziej przenośnym sposobem.

exec("hostname")faktycznie woła system operacyjny, aby wykonać hostnamepolecenie.

Oto kilka innych powiązanych odpowiedzi na temat SO:

EDYCJA: Powinieneś spojrzeć na odpowiedź AH lub odpowiedź Arnouta Engelena, aby dowiedzieć się, dlaczego może to nie działać zgodnie z oczekiwaniami, w zależności od twojej sytuacji. W odpowiedzi dla tej osoby, która specjalnie poprosiła o urządzenie przenośne, nadal uważam, że getHostName()jest w porządku, ale przynoszą one kilka dobrych punktów, które należy wziąć pod uwagę.

Nick Knowlson
źródło
12
Uruchomienie getHostName () powoduje czasami błąd. Na przykład, oto co otrzymuję w instancji Amazon EC2 AMI Linux: java.net.UnknownHostException: Nazwa lub usługa nieznana java.net.InetAddress.getLocalHost (InetAddress.java:1438)
Marquez
1
Ponadto InetAddress.getLocalHost()w niektórych przypadkach zwraca urządzenie sprzężenia zwrotnego. W takim przypadku getHostName()zwraca „localhost”, co nie jest zbyt przydatne.
olenz
53

Jak zauważyli inni, uzyskanie nazwy hosta na podstawie rozpoznania DNS jest zawodne.

Ponieważ to pytanie jest niestety nadal aktualne w 2018 r. , Chciałbym podzielić się z Wami moim niezależnym od sieci rozwiązaniem z niektórymi testami przeprowadzanymi na różnych systemach.

Poniższy kod próbuje wykonać następujące czynności:

  • W systemie Windows

    1. Przeczytaj COMPUTERNAMEzmienną środowiskową System.getenv().

    2. Wykonaj hostname.exei przeczytaj odpowiedź

  • W systemie Linux

    1. Przeczytaj HOSTNAMEzmienną środowiskowąSystem.getenv()

    2. Wykonaj hostnamei przeczytaj odpowiedź

    3. Przeczytaj /etc/hostname(w tym celu wykonuję, catponieważ fragment zawiera już kod do wykonania i odczytu. Lepiej byłoby po prostu odczytać plik).

Kod:

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

Wyniki dla różnych systemów operacyjnych:

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 Ten jest dość dziwny, ponieważ echo $HOSTNAMEzwraca prawidłową nazwę hosta, ale System.getenv("HOSTNAME")nie:

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

EDYCJA: Według legolas108 , System.getenv("HOSTNAME")działa na Ubuntu 14.04, jeśli uruchomisz export HOSTNAMEprzed uruchomieniem kodu Java.

System 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
"

Nazwy komputerów zostały zastąpione, ale zachowałem wielkie litery i strukturę. Zwróć uwagę na dodatkowy znak nowej linii podczas wykonywania hostname, w niektórych przypadkach może być konieczne wzięcie go pod uwagę.

Słód
źródło
3
Jeśli export HOSTNAMEprzed uruchomieniem kodu Java na Ubuntu 14.04 LTS jest on również zwracany przez System.getenv("HOSTNAME").
legolas108
Musisz jawnie, export HOSTNAMEaby Java mogła z niego korzystać? Myślałem, że powinna to być domyślna zmienna env jak PATH.
David
2
Tak, jest to jeden z błędów Java, który nigdy nie zostanie naprawiony z powodów religijnych. Pokrewny ma być w stanie ustawić nazwę procesu. Błędy zarejestrowano prawie 20 lat temu.
Tuntable
Doskonała odpowiedź, bardzo dokładna! Nie wiem, dlaczego używasz tego dziwnego separatora, szczególnie biorąc pod uwagę, że na każdym wydruku jest dziwna nowa linia. Niezależnie od tego aktualizuję odpowiedź do pracy z MacOS, skracam indexOf, aby zawierał wywołania, i poprawiam nazwę zmiennej systemu operacyjnego, aby była bardziej spójna ze standardowymi konwencjami nazw zmiennych.
Charlie
1
@Charlie \Aogranicznik to tylko włamanie do odczytu całego InputStream za pomocą Scanner.
Słód
24

InetAddress.getLocalHost().getHostName() jest lepszy (jak wyjaśnia Nick), ale wciąż niezbyt dobry

Jeden host może być znany pod wieloma różnymi nazwami hostów. Zazwyczaj będziesz szukał nazwy hosta, którą host ma w określonym kontekście.

Na przykład w aplikacji internetowej możesz szukać nazwy hosta używanej przez osobę, która wysłała aktualnie obsługiwane żądanie. Jak najlepiej to znaleźć, zależy od tego, którego frameworku używasz w swojej aplikacji internetowej.

W jakiejś innej usłudze internetowej potrzebujesz nazwy hosta, za pośrednictwem której Twoja usługa jest dostępna z „zewnątrz”. Z powodu serwerów proxy, zapór ogniowych itp. Może to nawet nie być nazwa hosta na komputerze, na którym zainstalowana jest usługa - możesz spróbować znaleźć rozsądne ustawienie domyślne, ale zdecydowanie powinieneś skonfigurować tę opcję dla każdego, kto ją zainstaluje.

Arnout Engelen
źródło
18

Chociaż na ten temat już udzielono odpowiedzi, jest więcej do powiedzenia.

Po pierwsze: najwyraźniej potrzebujemy tutaj kilku definicji. InetAddress.getLocalHost().getHostName()Daje nazwę hosta, jak widać z perspektywy sieci . Problemy z tym podejściem są dobrze udokumentowane w innych odpowiedziach: często wymaga wyszukiwania DNS, niejednoznaczne jest, jeśli host ma wiele interfejsów sieciowych, a czasami po prostu czasami zawodzi (patrz poniżej).

Ale w każdym systemie operacyjnym jest też inna nazwa. Nazwa hosta, która jest definiowana na bardzo wczesnym etapie procesu rozruchu, na długo przed zainicjowaniem sieci. Windows określa to mianem nazwa_komputera , Linux nazywa ją nazwą hosta jądra, a Solaris używa słowa nazwa_węzła . Najbardziej podoba mi się słowo nazwa_komputera , więc odtąd będę go używać.

Znajdowanie nazwy komputera

  • W Linux / Unix nazwa komputera jest tym, co otrzymujesz z funkcji C gethostname()lub hostnamepolecenia z powłoki lub HOSTNAMEzmiennej środowiskowej w powłokach typu Bash.

  • W systemie Windows nazwa komputera to nazwa zmiennej środowiskowej COMPUTERNAMElub GetComputerNamefunkcji Win32 .

Java nie ma możliwości uzyskania tego, co zdefiniowałem jako „nazwa komputera”. Oczywiście istnieją obejścia opisane w innych odpowiedziach, na przykład w przypadku wywoływania systemu Windows System.getenv("COMPUTERNAME"), ale w systemach Unix / Linux nie można obejść tego problemu bez uciekania się do JNI / JNA lub Runtime.exec(). Jeśli nie masz nic przeciwko rozwiązaniu JNI / JNA, istnieje gethostname4j, który jest bardzo prosty i bardzo łatwy w użyciu.

Przejdźmy do dwóch przykładów, jednego z Linuksa i jednego z Solaris, które pokazują, jak łatwo dostać się do sytuacji, w której nie można uzyskać nazwy komputera przy użyciu standardowych metod Java.

Przykład systemu Linux

W nowo utworzonym systemie, w którym host podczas instalacji został nazwany „chicago”, zmieniamy teraz tak zwaną nazwę hosta jądra:

$ hostnamectl --static set-hostname dallas

Teraz nazwa hosta jądra to „dallas”, jak widać po poleceniu hostname:

$ hostname
dallas

Ale nadal mamy

$ cat /etc/hosts
127.0.0.1   localhost
127.0.0.1   chicago

Nie ma w tym żadnej błędnej konfiguracji. Oznacza to po prostu, że nazwa sieciowa hosta (a raczej nazwa interfejsu pętli zwrotnej) różni się od nazwy komputera hosta.

Teraz spróbuj wykonać, InetAddress.getLocalHost().getHostName() a wygeneruje wyjątek java.net.UnknownHostException. W zasadzie utknąłeś. Nie ma możliwości odzyskania wartości „dallas” ani wartości „chicago”.

Przykład systemu Solaris

Poniższy przykład oparty jest na systemie Solaris 11.3.

Host został celowo skonfigurowany, aby nazwa pętli zwrotnej <> nazwa węzła.

Innymi słowy mamy:

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

oraz zawartość / etc / hosts:

:1 chicago localhost
127.0.0.1 chicago localhost loghost

a wynikiem polecenia nazwy hosta byłoby:

$ hostname
dallas

Podobnie jak w przykładzie z Linuksem wywołanie InetAddress.getLocalHost().getHostName()nie powiedzie się

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

Tak jak w przykładzie z Linuksem, który utknął. Nie ma możliwości odzyskania wartości „dallas” ani wartości „chicago”.

Kiedy naprawdę będziesz miał z tym problem?

Bardzo często okazuje się, że InetAddress.getLocalHost().getHostName()rzeczywiście zwróci wartość równą nazwie komputera. Więc nie ma problemu (z wyjątkiem dodatkowego obciążenia związanego z rozpoznawaniem nazw).

Problem pojawia się zwykle w środowiskach PaaS, w których istnieje różnica między nazwą komputera a nazwą interfejsu sprzężenia zwrotnego. Na przykład ludzie zgłaszają problemy w Amazon EC2.

Raporty o błędach / RFE

Trochę wyszukiwania ujawnia ten raport RFE: link1 , link2 . Jednak sądząc po komentarzach do tego raportu, wydaje się, że problem został w dużej mierze źle zrozumiany przez zespół JDK, więc jest mało prawdopodobne, że zostanie rozwiązany.

Podoba mi się porównanie w RFE z innymi językami programowania.

Peter
źródło
12

Zmienne środowiskowe mogą również stanowić użyteczny środek - COMPUTERNAMEw systemie Windows, HOSTNAMEw większości nowoczesnych powłok Unix / Linux.

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

Używam ich jako „dodatkowych” metod InetAddress.getLocalHost().getHostName(), ponieważ, jak zauważa kilka osób, ta funkcja nie działa we wszystkich środowiskach.

Runtime.getRuntime().exec("hostname")jest kolejnym możliwym dodatkiem. Na tym etapie nie korzystałem z niego.

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.
źródło
6
StringUtils? Co to jest?? (Wiem, co masz na myśli, po prostu uważam, że wprowadzanie zewnętrznej biblioteki do tego jedynego celu jest złą karmą… a na
dodatek
23
Złą karmą jest ciągłe ręczne pisanie „pustych” czeków. W wielu projektach takie kontrole są wykonywane ręcznie (tak jak sugerujesz) i niespójnie, z wieloma wynikającymi z tego błędami. Użyj biblioteki.
Thomas W
15
Na wypadek, gdyby ktoś to zauważył i był faktycznie zdezorientowany StringUtils, zapewniono go w ramach projektu commons-lang Apache. Zaleca się korzystanie z niego lub czegoś podobnego.
JBCP
Jakoś System.getenv("HOSTNAME")wyszedł na zero na Macu przez Beanshell dla Javy, ale PATHzostał wyodrębniony. Mógłbym także zrobić echo $HOSTNAMEna komputerze Mac, tylko System.getenv („HOSTNAME”) wydaje się mieć problem. Dziwne.
David
6

Najbardziej przenośnym sposobem uzyskania nazwy hosta bieżącego komputera w Javie jest:

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
źródło
8
Pomijając przenośność, jak ta metoda wypada w porównaniu do metody opisanej w pytaniu? Jakie są zalety / wady?
Marquez
4

Jeśli nie jesteś przeciwny używaniu zewnętrznej zależności od maven central, napisałem gethostname4j, aby rozwiązać ten problem dla siebie. Po prostu używa JNA do wywołania funkcji gethostname libc (lub pobiera ComputerName w systemie Windows) i zwraca ją jako ciąg znaków.

https://github.com/mattsheppard/gethostname4j

Matt Sheppard
źródło
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
źródło
1
Jakie są zalety tej metody?
Sam Hasler,
1
Jest to nieprawidłowe, podaje tylko nazwę pierwszej karty sieciowej, która nie jest adapterem pętli zwrotnej.
Steve-o
w rzeczywistości jest to pierwsza nie pętla zwrotna, która ma nazwę hosta ... niekoniecznie pierwsza nie pętla zwrotna.
Adam Gent,
1

Wystarczy jedna linijka ... na wielu platformach (Windows-Linux-Unix-Mac (Unix)) [Zawsze działa, nie wymaga DNS]:

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

Jesteś skończony !!

Dan Ortega
źródło
InetAddress.getLocalHost (). GetHostName () nie będzie działać w Uniksie?
P Satish Patro
1
To działa, ale wymaga rozdzielczości dns, co jeśli jesteś podłączony do modemu Wi-Fi z telefonu (żeby wymienić tylko jeden przykład), nie będzie miał DNS znającego lokalną maszynę hosta i nie będzie działać.
Dan Ortega
-2

InetAddress.getLocalHost (). GetHostName () to najlepsze wyjście z tych dwóch, ponieważ jest to najlepsza abstrakcja na poziomie programisty.

java_mouse
źródło
Szukam odpowiedzi, która dotyczy jednego hosta znanego pod wieloma nazwami.
Sam Hasler,
@SamHasler, jaka jest twoja konkretna sytuacja? Jak wyjaśnił Arnout, istnieją różne sposoby zależne od potrzeb.
Nick Knowlson