Maneira recomendada de obter o nome do host em Java

275

Qual das alternativas a seguir é a melhor e mais portátil maneira de obter o nome do host do computador atual em Java?

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

vs

InetAddress.getLocalHost().getHostName()

Mahendra
fonte
Que pilha de tecnologia é essa?
tom Redfern
Eu acho que o único nome real (unts_name) suportado é do RMI / JMX VMID, mas isso é específico da implementação.
Eckes

Respostas:

336

A rigor - você não tem escolha a não ser ligar para hostname(1)ou - no Unix gethostname(2). Este é o nome do seu computador. Qualquer tentativa de determinar o nome do host por um endereço IP como este

InetAddress.getLocalHost().getHostName()

está fadado a falhar em algumas circunstâncias:

  • O endereço IP pode não ser resolvido em nenhum nome. Configuração de DNS incorreta, configuração incorreta do sistema ou configuração incorreta do provedor pode ser a razão disso.
  • Um nome no DNS pode ter muitos aliases chamados CNAMEs. Eles só podem ser resolvidos em uma direção corretamente: nome para o endereço. A direção inversa é ambígua. Qual é o nome "oficial"?
  • Um host pode ter muitos endereços IP diferentes - e cada endereço pode ter muitos nomes diferentes. Dois casos comuns são: Uma porta Ethernet possui vários endereços IP "lógicos" ou o computador possui várias portas Ethernet. É configurável se eles compartilham um IP ou têm IPs diferentes. Isso é chamado "multihomed".
  • Um nome no DNS pode resolver para vários endereços IP. E nem todos esses endereços devem estar localizados no mesmo computador! (Usecase: uma forma simples de balanceamento de carga)
  • Não vamos nem começar a falar sobre endereços IP dinâmicos.

Além disso, não confunda o nome de um endereço IP com o nome do host (hostname). Uma metáfora pode deixar mais claro:

Há uma grande cidade (servidor) chamada "London". Dentro das muralhas da cidade, muitos negócios acontecem. A cidade possui vários portões (endereços IP). Cada portão tem um nome ("North Gate", "River Gate", "Southampton Gate" ...), mas o nome do portão não é o nome da cidade. Além disso, você não pode deduzir o nome da cidade usando o nome de um portão - "North Gate" pegaria metade das cidades maiores e não apenas uma cidade. No entanto, um estranho (pacote IP) caminha ao longo do rio e pergunta a um morador local: "Eu tenho um endereço estranho: 'Rivergate, segundo à esquerda, terceira casa'. Você pode me ajudar?" O local diz: "Claro que você está no caminho certo, basta seguir em frente e chegará ao seu destino dentro de meia hora".

Isso ilustra muito bem, eu acho.

A boa notícia é: o nome do host real geralmente não é necessário. Na maioria dos casos, qualquer nome que se resolva em um endereço IP nesse host será suficiente. (O estrangeiro pode entrar na cidade por Northgate, mas moradores úteis traduzem a parte "2ª esquerda".)

Se os casos de canto restantes você deve usar a definitiva fonte desta configuração - que é a função C gethostname(2). Essa função também é chamada pelo programa hostname.

AH
fonte
9
Não é exatamente a resposta que eu esperava, mas agora sei fazer uma pergunta melhor, obrigado.
Sam Hasler
3
Veja também stackoverflow.com/questions/6050011/…
Raedwald
4
Essa é uma boa descrição das limitações que qualquer software (e não apenas um programa Java) possui na determinação do nome do host no mundo. No entanto, observe que getHostName é implementado em termos do sistema operacional subjacente, provavelmente da mesma forma que o nome do host / nome do host. Em um sistema "normal", InetAddress.getLocalHost (). GetHostName () é equivalente a chamar hostname / gethostname, portanto, você não pode realmente dizer que um falha de maneira que o outro não.
Peter Cardona
System.err.println (Runtime.getRuntime (). Exec ("hostname")); me dá o seguinte: java.lang.UNIXProcess@6eb2384f
user152468
5
A implementação de InetAddress.getLocalHost (). GetHostName () é realmente muito determinística :) Se você seguir o código-fonte, ele finalmente chamará gethostname () no Windows e getaddrinfo () nos sistemas Unixy. O resultado é o mesmo que usar o comando hostname do SO. Agora, o nome do host pode fornecer uma resposta que você não deseja usar, que é possível por vários motivos. Geralmente, o software deve obter o nome do host do usuário em um arquivo de configuração, assim, é sempre o nome do host correto. Você poderia usar InetAddress.getLocalhost (). GetHostName () como padrão se o usuário não fornecer um valor.
Greg
93

InetAddress.getLocalHost().getHostName() é a maneira mais portátil.

exec("hostname")realmente chama o sistema operacional para executar o hostnamecomando.

Aqui estão algumas outras respostas relacionadas ao SO:

EDIT: Você deve dar uma olhada na resposta da AH ou na resposta de Arnout Engelen para obter detalhes sobre por que isso pode não funcionar como o esperado, dependendo da sua situação. Como resposta para essa pessoa que solicitou especificamente o portátil, ainda acho que getHostName()está bem, mas eles trazem alguns bons pontos que devem ser considerados.

Nick Knowlson
fonte
12
A execução de getHostName () resulta em um erro algumas vezes. Por exemplo, aqui está o que eu recebo em uma instância do Amazon EC2 AMI Linux: java.net.UnknownHostException: nome ou serviço não conhecido java.net.InetAddress.getLocalHost (InetAddress.java:1438)
Marquez
1
Além disso, InetAddress.getLocalHost()em alguns casos, retorna o dispositivo de loopback. Nesse caso, getHostName()retorna "localhost", o que não é muito útil.
olenz 21/07
54

Como outros observaram, a obtenção do nome do host com base na resolução do DNS não é confiável.

Como essa pergunta ainda é relevante em 2018 , gostaria de compartilhar com você minha solução independente de rede, com alguns testes em diferentes sistemas.

O código a seguir tenta fazer o seguinte:

  • No Windows

    1. Leia a COMPUTERNAMEvariável de ambiente System.getenv().

    2. Execute hostname.exee leia a resposta

  • No Linux

    1. Leia a HOSTNAMEvariável de ambiente através deSystem.getenv()

    2. Execute hostnamee leia a resposta

    3. Read /etc/hostname(para fazer isso, estou executando, catjá que o trecho já contém código para executar e ler. Porém, basta ler o arquivo).

O 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 operacionais:

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 é meio estranho, pois echo $HOSTNAMEretorna o nome de host correto, mas System.getenv("HOSTNAME")não:

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

EDIT: De acordo com legolas108 , System.getenv("HOSTNAME")funciona no Ubuntu 14.04 se você executar export HOSTNAMEantes de executar o 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
"

Os nomes das máquinas foram substituídos, mas mantive a capitalização e a estrutura. Observe a nova linha extra ao executar hostname, talvez seja necessário levar isso em consideração em alguns casos.

Malte
fonte
3
Se você export HOSTNAMEantes de executar o código Java no Ubuntu 14.04 LTS, ele também será retornado por System.getenv("HOSTNAME").
precisa saber é
Você precisa explicitamente export HOSTNAMEpara o Java usá-lo? Eu pensei que deveria ser um padrão var var como PATH.
David
2
Sim, este é um dos erros do Java que nunca serão corrigidos por motivos religiosos. Um relacionado deve ser capaz de definir o nome do processo. Erros registrados quase 20 anos atrás.
Tuntable
Excelente resposta, muito completa! Não sei por que você usa esse delimitador estranho, especialmente considerando que toda saída impressa possui uma nova linha estranha. Independentemente disso, estou atualizando a resposta para trabalhar com o MacOS, abreviando o indexOf para chamadas e corrigindo o nome da variável do SO para ser mais consistente com as convenções padrão de nomes de variáveis.
628 Charlie
1
@ Charlie, o \Adelimitador, é apenas um truque para ler todo o InputStream usando a Scanner.
Malt
24

InetAddress.getLocalHost().getHostName() é melhor (como explicado por Nick), mas ainda não é muito bom

Um host pode ser conhecido sob vários nomes de host diferentes. Geralmente, você procurará o nome do host que seu host possui em um contexto específico.

Por exemplo, em um aplicativo Web, você pode estar procurando o nome do host usado por quem emitiu a solicitação que você está manipulando no momento. A melhor maneira de descobrir que isso depende de qual estrutura você está usando para seu aplicativo da web.

Em algum tipo de outro serviço voltado para a Internet, você deseja que o nome do host esteja disponível através do 'fora'. Devido a proxies, firewalls, etc., pode até não ser um nome de host na máquina em que o serviço está instalado - você pode tentar obter um padrão razoável, mas definitivamente deve configurá-lo para quem o instalar.

Arnout Engelen
fonte
18

Embora este tópico já tenha sido respondido, há mais a dizer.

Primeiro de tudo: Claramente, precisamos de algumas definições aqui. O InetAddress.getLocalHost().getHostName()fornece o nome do host como visto da perspectiva da rede . Os problemas com essa abordagem estão bem documentados nas outras respostas: geralmente requer uma pesquisa de DNS, é ambíguo se o host tiver várias interfaces de rede e simplesmente falhar algumas vezes (veja abaixo).

Mas em qualquer sistema operacional também existe outro nome. Um nome do host que é definido muito cedo no processo de inicialização, muito antes da inicialização da rede. O Windows se refere a isso como nome do computador , o Linux chama de nome do host do kernel e o Solaris usa a palavra nome do . Eu gosto mais da palavra nomedocomputador , então usarei essa palavra a partir de agora.

Localizando o nome do computador

  • No Linux / Unix, o nome do computador é o que você obtém da função C gethostname(), ou hostnamecomando do shell ou HOSTNAMEvariável de ambiente nos shells do tipo Bash.

  • No Windows, o nome do computador é o que você obtém da variável de ambiente COMPUTERNAMEou da GetComputerNamefunção Win32 .

Java não tem como obter o que defini como 'computername'. Claro, existem soluções alternativas, conforme descrito em outras respostas, como para chamadas do Windows System.getenv("COMPUTERNAME"), mas no Unix / Linux não há uma boa solução alternativa sem recorrer ao JNI / JNA ou Runtime.exec(). Se você não se importa com uma solução JNI / JNA, existe o gethostname4j que é simples e muito fácil de usar.

Vamos seguir com dois exemplos, um do Linux e outro do Solaris, que demonstram como você pode facilmente entrar em uma situação em que não pode obter o nome do computador usando métodos Java padrão.

Exemplo de Linux

Em um sistema recém-criado, em que o host durante a instalação foi nomeado como 'chicago', agora alteramos o chamado nome do host do kernel:

$ hostnamectl --static set-hostname dallas

Agora, o nome do host do kernel é 'dallas', como é evidente no comando hostname:

$ hostname
dallas

Mas ainda temos

$ cat /etc/hosts
127.0.0.1   localhost
127.0.0.1   chicago

Não há configuração incorreta nisso. Significa apenas que o nome da rede do host (ou melhor, o nome da interface de loopback) é diferente do nome do computador do host.

Agora, tente executar InetAddress.getLocalHost().getHostName() e ele lançará java.net.UnknownHostException. Você está basicamente preso. Não há como recuperar nem o valor 'dallas' nem o valor 'chicago'.

Exemplo do Solaris

O exemplo abaixo é baseado no Solaris 11.3.

O host foi deliberadamente configurado para que o nome do loopback <> nodename.

Em outras palavras, temos:

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

e o conteúdo de / etc / hosts:

:1 chicago localhost
127.0.0.1 chicago localhost loghost

e o resultado do comando hostname seria:

$ hostname
dallas

Assim como no exemplo do Linux, uma chamada para InetAddress.getLocalHost().getHostName()falhará com

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

Assim como no exemplo do Linux, você está preso agora. Não há como recuperar nem o valor 'dallas' nem o valor 'chicago'.

Quando você realmente lutará com isso?

Muitas vezes você descobrirá que InetAddress.getLocalHost().getHostName()realmente retornará um valor igual ao nome do computador. Portanto, não há problema (exceto a sobrecarga adicional da resolução de nomes).

O problema geralmente ocorre em ambientes PaaS, nos quais há uma diferença entre o nome do computador e o nome da interface de loopback. Por exemplo, as pessoas relatam problemas no Amazon EC2.

Relatórios de erros / RFE

Um pouco de pesquisa revela este relatório do RFE: link1 , link2 . No entanto, a julgar pelos comentários nesse relatório, o problema parece ter sido amplamente mal compreendido pela equipe do JDK, portanto, é improvável que seja abordado.

Gosto da comparação no RFE com outras linguagens de programação.

peterh
fonte
12

As variáveis ​​de ambiente também podem fornecer um meio útil - COMPUTERNAMEno Windows, HOSTNAMEna maioria dos shells Unix / Linux modernos.

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

Estou usando esses métodos como "suplementares" para InetAddress.getLocalHost().getHostName(), como várias pessoas apontam, essa função não funciona em todos os ambientes.

Runtime.getRuntime().exec("hostname")é outro complemento possível. Nesta fase, eu não o usei.

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
fonte
6
StringUtils? O que é isso?? (Eu sei o que você quer dizer que eu só acho que é mau karma para trazer uma biblioteca externa para este único propósito .. e em cima do que nem sequer mencioná-lo)
peterh
23
É um karma ruim escrever constantemente cheques "vazios" manualmente. Muitos projetos têm essas verificações feitas manualmente (da maneira que você sugere) e inconsistentemente, com muitos erros resultantes. Use uma biblioteca.
21413 Thomas Thomas W
15
Caso alguém veja isso e fique realmente confuso StringUtils, é fornecido pelo projeto Apache commons-lang. É altamente recomendável usá-lo ou algo parecido.
JBCP
De alguma forma, System.getenv("HOSTNAME")saiu nulo no Mac via Beanshell para Java, mas PATHfoi extraído ok. Eu também poderia fazer echo $HOSTNAMEno Mac, apenas System.getenv ("HOSTNAME") parece ter problema. Estranho.
David
6

A maneira mais portátil de obter o nome do host do computador atual em Java é a seguinte:

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
fonte
8
Portabilidade à parte, como esse método se compara ao método da pergunta? O que são prós / contras?
Marquez
4

Se você não é contra o uso de uma dependência externa da maven central, escrevi gethostname4j para resolver esse problema por conta própria. Ele apenas usa o JNA para chamar a função gethostname da libc (ou obtém o ComputerName no Windows) e a retorna como uma string.

https://github.com/mattsheppard/gethostname4j

Matt Sheppard
fonte
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
fonte
1
Qual é o benefício desse método?
Sam Hasler
1
Isso é inválido, ele fornece apenas o nome da primeira NIC que não é um adaptador de loopback.
Steve-o
na verdade, é o primeiro não loopback que tem um nome de host ... não necessariamente o primeiro não loopback.
Adam Gent
1

Apenas uma linha ... multiplataforma (Windows-Linux-Unix-Mac (Unix)) [Sempre funciona, sem necessidade de DNS]:

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

Você Terminou !!

Dan Ortega
fonte
InetAddress.getLocalHost (). GetHostName () não funcionará no Unix?
P Satish Patro
1
Isso funciona, mas requer resolução DNS, e se você estiver conectado a uma conexão wifi do seu telefone (para mencionar apenas um exemplo), ele não terá um DNS conhecendo a máquina localhost e não funcionará.
Dan Ortega
-2

InetAddress.getLocalHost (). GetHostName () é a melhor saída dentre as duas, pois essa é a melhor abstração no nível do desenvolvedor.

java_mouse
fonte
Estou procurando uma resposta que lida com um host conhecido por muitos nomes de host.
Sam Hasler
@ SamHasler, qual é a sua situação específica? Como Arnout explicou, existem maneiras diferentes, dependendo do que você precisa.
Nick Knowlson