Рекомендуемый способ получить имя хоста в Java

275

Какой из следующих способов является лучшим и наиболее переносимым способом получения имени хоста текущего компьютера в Java?

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

против

InetAddress.getLocalHost().getHostName()

Мээндра
источник
Какой это технологический стек?
Том Редферн
Я думаю, что единственное реальное имя с поддержкой uname (uts_name) взято из RID / JMX VMID, но это зависит от реализации.
Eckes

Ответы:

336

Строго говоря, у вас нет выбора, кроме как позвонить hostname(1)или - в Unix gethostname(2). Это имя вашего компьютера. Любая попытка определить имя хоста по IP-адресу, подобному этому

InetAddress.getLocalHost().getHostName()

обязательно потерпит неудачу в некоторых обстоятельствах:

  • IP-адрес может не указываться ни в одном имени. Причиной этого могут быть неправильные настройки DNS, плохие настройки системы или плохие настройки провайдера.
  • Имя в DNS может иметь много псевдонимов, называемых CNAME. Они могут быть разрешены только в одном направлении: имя по адресу. Обратное направление неоднозначно. Какое "официальное" имя?
  • Хост может иметь много разных IP-адресов - и каждый адрес может иметь много разных имен. Два распространенных случая: один порт Ethernet имеет несколько «логических» IP-адресов или компьютер имеет несколько портов Ethernet. Это настраивается независимо от того, используют ли они один или несколько IP-адресов. Это называется "многосетевой".
  • Одно имя в DNS может разрешать несколько IP-адресов. И не все эти адреса должны быть расположены на одном компьютере! (Usecase: простая форма распределения нагрузки)
  • Давайте даже не будем говорить о динамических IP-адресах.

Также не следует путать имя IP-адреса с именем хоста (hostname). Метафора может прояснить это:

Существует большой город (сервер) под названием "Лондон". Внутри городских стен много дел происходит. В городе есть несколько ворот (IP-адреса). Каждые ворота имеют имя («Северные ворота», «Речные ворота», «Саутгемптонские ворота» ...), но название ворот не является названием города. Также вы не можете определить название города, используя название ворот - «Северные ворота» будут охватывать половину крупных городов, а не только один город. Однако незнакомец (IP-пакет) идет вдоль реки и спрашивает у местного жителя: «У меня странный адрес:« Ривергейт, второй слева, третий дом ». Вы можете мне помочь?» Местные жители говорят: «Конечно, вы на правильном пути, просто идите вперед, и вы прибудете к месту назначения в течение получаса».

Это иллюстрирует это в значительной степени, я думаю.

Хорошая новость: реальное имя хоста обычно не требуется. В большинстве случаев подойдет любое имя, которое разрешается в IP-адрес на этом хосте. (Незнакомец может войти в город через Нортгейт, но полезные местные жители переводят часть «2-й левый».)

Если в остальных случаях угловых вы должны использовать окончательный источник этой настройки конфигурации - которая является функцией C gethostname(2). Эта функция также вызывается программой hostname.

AH
источник
9
Не совсем тот ответ, на который я надеялся, но теперь я знаю, как задать лучший вопрос, спасибо.
Сэм Хаслер
3
См. Также stackoverflow.com/questions/6050011/…
Raedwald
4
Это хорошее описание ограничений, которые имеет любое программное обеспечение (не только Java-программа) при определении имени хоста в мире. Однако обратите внимание, что getHostName реализован в терминах базовой ОС, предположительно так же, как имя хоста / gethostname. В «нормальной» системе InetAddress.getLocalHost (). GetHostName () эквивалентен вызову hostname / gethostname, так что вы не можете сказать, что один из них выходит из строя так же, как другой.
Питер Кардона
System.err.println (Runtime.getRuntime () Exec ( "имя хоста").); дает мне это: java.lang.UNIXProcess@6eb2384f
user152468
5
Реализация InetAddress.getLocalHost (). GetHostName () на самом деле очень детерминистична :). Если вы следуете исходному коду, в конечном итоге он вызывает gethostname () в Windows и getaddrinfo () в системах Unixy. Результат такой же, как при использовании команды имени вашей операционной системы. Теперь имя хоста может дать ответ, который вы не хотите использовать, что возможно по многим причинам. Как правило, программное обеспечение должно получать имя хоста от пользователя в файле конфигурации, таким образом, это всегда правильное имя хоста. Вы можете использовать InetAddress.getLocalhost (). GetHostName () по умолчанию, если пользователь не предоставляет значение.
Грег
93

InetAddress.getLocalHost().getHostName() это более портативный способ.

exec("hostname")фактически вызывает операционную систему для выполнения hostnameкоманды.

Вот пара других связанных ответов на SO:

РЕДАКТИРОВАТЬ: Вам следует взглянуть на ответ AH или ответ Arnout Engelen, чтобы узнать, почему это может работать не так, как ожидалось, в зависимости от вашей ситуации. В качестве ответа для этого человека, который специально попросил портативное устройство, я все еще думаю, что getHostName()это хорошо, но они поднимают некоторые хорошие моменты, которые следует учитывать.

Ник Ноулсон
источник
12
Запуск getHostName () приводит к ошибке несколько раз. Например, вот что я получаю в экземпляре Amazon EC2 AMI Linux: java.net.UnknownHostException: имя или служба неизвестна java.net.InetAddress.getLocalHost (InetAddress.java:1438)
Marquez
1
Также InetAddress.getLocalHost()в некоторых случаях возвращает устройство обратной петли. В этом случае getHostName()возвращает «localhost», что не очень полезно.
Оленц
54

Как уже отмечалось, получение имени хоста на основе разрешения DNS ненадежно.

Поскольку этот вопрос, к сожалению, по-прежнему актуален в 2018 году , я хотел бы поделиться с вами своим независимым от сети решением, проведя несколько тестовых прогонов на разных системах.

Следующий код пытается сделать следующее:

  • На винде

    1. Прочитайте COMPUTERNAMEпеременную среды через System.getenv().

    2. Выполнить hostname.exeи прочитать ответ

  • В линуксе

    1. Прочитайте HOSTNAMEпеременную среды черезSystem.getenv()

    2. Выполнить hostnameи прочитать ответ

    3. Читать /etc/hostname(для этого я выполняю, catтак как фрагмент уже содержит код для выполнения и чтения. Хотя лучше просто прочитать файл).

Код:

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

Результаты для разных операционных систем:

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 Это странно, так как echo $HOSTNAMEвозвращает правильное имя хоста, но System.getenv("HOSTNAME")не делает:

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

РЕДАКТИРОВАТЬ: Согласно legolas108 , System.getenv("HOSTNAME")работает на Ubuntu 14.04, если вы запускаете export HOSTNAMEдо выполнения кода 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
"

Названия машин были заменены, но я сохранил капитализацию и структуру. Обратите внимание на дополнительный символ новой строки при выполнении hostname, возможно, вам придется принять его во внимание в некоторых случаях.

солод
источник
3
Если вы export HOSTNAMEдо запуска Java-кода на Ubuntu 14.04 LTS также возвращали System.getenv("HOSTNAME").
legolas108
Вы должны явно export HOSTNAMEдля Java использовать его? Я думал, что это должен быть env var по умолчанию, как PATH.
Дэвид
2
Да, это одна из ошибок Java, которая никогда не будет исправлена ​​по религиозным причинам. Связанный должен иметь возможность установить имя процесса. Ошибки регистрировались почти 20 лет назад.
Tuntable
Отличный ответ, очень тщательно! Я не знаю, почему вы используете этот странный разделитель, особенно учитывая, что в каждом печатном виде есть странный символ новой строки. Несмотря на это, я обновляю ответ для работы с MacOS, сокращаю indexOf до вызовов и исправляю имя переменной ОС, чтобы оно больше соответствовало стандартным соглашениям об именах переменных.
Чарли
1
@Charlie \Aразделитель - это просто хак для чтения всего InputStream с помощью a Scanner.
солод
24

InetAddress.getLocalHost().getHostName() лучше (как объяснил Ник), но все же не очень хорошо

Один хост может быть известен под разными именами хоста. Обычно вы будете искать имя вашего хоста в определенном контексте.

Например, в веб-приложении вы можете искать имя хоста, используемое тем, кто отправил запрос, который вы сейчас обрабатываете. Как лучше всего это найти, зависит от того, какую платформу вы используете для своего веб-приложения.

В какой-либо другой интернет-службе вам нужно, чтобы имя хоста, через которое доступна ваша служба, было «извне». Из-за прокси-серверов, брандмауэров и т. Д. Это может даже не быть именем хоста на машине, на которой установлена ​​ваша служба - вы можете попытаться найти разумное значение по умолчанию, но вам обязательно следует настроить его для любого, кто его устанавливает.

Арно Энгелен
источник
18

Хотя на эту тему уже ответили, есть еще что сказать.

Прежде всего: очевидно, нам нужны некоторые определения здесь. Это InetAddress.getLocalHost().getHostName()дает вам имя хоста с точки зрения сети . Проблемы с этим подходом хорошо задокументированы в других ответах: он часто требует поиска DNS, он неоднозначен, если у хоста несколько сетевых интерфейсов, и иногда он просто выходит из строя (см. Ниже).

Но в любой ОС есть и другое имя. Имя хоста, которое определяется очень рано в процессе загрузки, задолго до инициализации сети. Windows называет это как имя компьютера , Linux называет его именем хоста ядра, а Solaris использует слово nodename . Мне больше всего нравится слово computername , так что теперь я буду использовать это слово.

Нахождение имени компьютера

  • В Linux / Unix имя компьютера - это то, что вы получаете из функции C gethostname()или hostnameкоманды из оболочки или HOSTNAMEпеременной среды в оболочках, подобных Bash.

  • В Windows имя компьютера - это то, что вы получаете из переменной среды COMPUTERNAMEили GetComputerNameфункции Win32 .

У Java нет способа получить то, что я определил как «имя компьютера». Конечно, есть обходные пути, как описано в других ответах, например, для вызовов Windows System.getenv("COMPUTERNAME"), но в Unix / Linux нет хорошего обходного пути, не прибегая к JNI / JNA или Runtime.exec(). Если вы не возражаете против решения JNI / JNA, то есть gethostname4j, который очень прост и очень прост в использовании.

Давайте перейдем к двум примерам, одному из Linux и одному из Solaris, которые демонстрируют, как вы легко можете попасть в ситуацию, когда вы не можете получить имя компьютера, используя стандартные методы Java.

Пример Linux

В недавно созданной системе, где хост во время установки был назван 'chicago', мы теперь изменим так называемое имя хоста ядра:

$ hostnamectl --static set-hostname dallas

Теперь имя хоста ядра 'dallas', как видно из команды hostname:

$ hostname
dallas

Но у нас все еще есть

$ cat /etc/hosts
127.0.0.1   localhost
127.0.0.1   chicago

Там нет неправильной конфигурации в этом. Это просто означает, что сетевое имя хоста (или, скорее, имя интерфейса обратной связи) отличается от имени компьютера хоста.

Теперь попробуйте выполнить, InetAddress.getLocalHost().getHostName() и он выдаст исключение java.net.UnknownHostException. Вы в основном застряли. Нет способа получить ни значение «Даллас», ни значение «Чикаго».

Пример соляриса

Пример ниже основан на Solaris 11.3.

Хост специально настроен так, чтобы имя петлевого имени <> имя узла.

Другими словами, мы имеем:

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

и содержимое / etc / hosts:

:1 chicago localhost
127.0.0.1 chicago localhost loghost

и результат команды hostname будет:

$ hostname
dallas

Как и в примере с Linux, при вызове InetAddress.getLocalHost().getHostName()произойдет сбой

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

Точно так же, как пример с Linux, вы застряли. Нет способа получить ни значение «Даллас», ни значение «Чикаго».

Когда вы действительно будете бороться с этим?

Очень часто вы обнаружите, что InetAddress.getLocalHost().getHostName()действительно вернет значение, равное имени компьютера. Так что проблем нет (за исключением дополнительных накладных расходов на разрешение имен).

Проблема обычно возникает в средах PaaS, где есть разница между именем компьютера и именем интерфейса обратной связи. Например, люди сообщают о проблемах в Amazon EC2.

Отчеты об ошибках / RFE

Немного поиска показывает этот отчет RFE: link1 , link2 . Однако, судя по комментариям к этому отчету, команда JDK, по-видимому, в значительной степени неправильно поняла проблему, поэтому вряд ли она будет решена.

Мне нравится сравнение в RFE с другими языками программирования.

peterh
источник
12

Переменные среды также могут быть полезны - COMPUTERNAMEв Windows, HOSTNAMEв большинстве современных оболочек Unix / Linux.

См .: https://stackoverflow.com/a/17956000/768795.

Я использую их как «дополнительные» методы InetAddress.getLocalHost().getHostName(), поскольку, как отмечают несколько человек, эта функция работает не во всех средах.

Runtime.getRuntime().exec("hostname")еще одно возможное дополнение. На данном этапе я не использовал его.

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? Что это такое?? (Я знаю, что вы имеете в виду, я просто думаю, что это плохая карма - использовать внешнюю библиотеку для этой единственной цели… и,
кроме
23
Плохая карма постоянно писать «пустые» чеки вручную. Во многих проектах такие проверки выполняются вручную (как вы предлагаете) и непоследовательно, и в результате возникает множество ошибок. Используйте библиотеку.
Томас В.
15
В случае, если кто-то видит это и на самом деле смущен StringUtils, это предусмотрено проектом Apache commons-lang. Настоятельно рекомендуется использовать его или что-то подобное.
JBCP
Каким-то образом System.getenv("HOSTNAME")вышел нулевым на Mac через Beanshell для Java, но PATHбыл извлечен нормально. Я мог бы также сделать echo $HOSTNAMEна Mac, просто System.getenv ("HOSTNAME"), кажется, проблема. Странный.
Дэвид
6

Наиболее переносимый способ получения имени хоста текущего компьютера в Java заключается в следующем:

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
Помимо переносимости, как этот метод сравнивается с методом в вопросе? Какие плюсы / минусы?
Маркес
4

Если вы не против использования внешней зависимости от maven central, я написал gethostname4j, чтобы решить эту проблему для себя. Он просто использует JNA для вызова функции gethostname в libc (или получает имя_компьютера в Windows) и возвращает его вам в виде строки.

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();
            }
        }
    }
}
ThuVien247.net
источник
1
В чем выгода этого метода?
Сэм Хаслер
1
Это неверно, оно предоставляет только имя первого сетевого адаптера, который не является адаптером обратной связи.
Steve-o
на самом деле это первая не петля, которая имеет имя хоста ... не обязательно первая не петля.
Адам Гент
1

Просто одна строка ... кросс-платформенная (Windows-Linux-Unix-Mac (Unix)) [Всегда работает, DNS не требуется]:

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

Вы сделали !!

Дэн Ортега
источник
InetAddress.getLocalHost (). GetHostName () не будет работать в Unix?
P Satish Patro
1
Это работает, но требует разрешения DNS, что если вы подключены к Wi-Fi-модему со своего телефона (если упомянуть только один пример), у него не будет DNS, зная локальный хост, и он не будет работать.
Дэн Ортега
-2

InetAddress.getLocalHost (). GetHostName () - лучший выход из двух, поскольку это лучшая абстракция на уровне разработчика.

java_mouse
источник
Я ищу ответ, который касается одного хоста, известного под многими именами хостов.
Сэм Хаслер
@ SamHasler, какова ваша конкретная ситуация? Как объяснил Арно, есть разные способы в зависимости от того, что вам нужно.
Ник Ноулсон