在Java中获取主机名的推荐方法


Answers:


336

严格来说- hostname(1)在Unix 上,您别无选择,只能调用或gethostname(2)。这是您的计算机的名称。任何通过这样的IP地址来确定主机名的尝试

InetAddress.getLocalHost().getHostName()

在某些情况下注定会失败:

  • IP地址可能无法解析为任何名称。错误的DNS设置,错误的系统设置或错误的提供程序设置可能是造成此问题的原因。
  • DNS中的名称可以具有许多称为CNAME的别名。这些只能在一个方向上正确解决:从名称到地址。相反的方向是模棱两可的。“正式”名称是哪一个?
  • 主机可以有许多不同的IP地址-每个地址可以有许多不同的名称。两种常见情况是:一个以太网端口具有多个“逻辑” IP地址,或者计算机具有多个以太网端口。它们是共享IP还是具有不同IP,这是可配置的。这称为“多宿主”。
  • DNS中的一个名称可以解析为多个IP地址。并非所有这些地址都必须位于同一台计算机上!(用例:负载平衡的一种简单形式)
  • 我们甚至不要开始谈论动态IP地址。

另外,请勿将IP地址的名称与主机名(主机名)混淆。隐喻可能更清楚:

有一个大城市(服务器),称为“伦敦”。在城墙内,发生了很多生意。这个城市有几个门(IP地址)。每个闸门都有一个名称(“北闸门”,“河闸门”,“南安普敦闸门” ...),但是闸门的名称不是城市的名称。同样,您无法通过使用大门的名称来推断城市的名称-“北门”将覆盖一半的较大城市,而不仅仅是一个城市。但是-一个陌生人(IP数据包)沿着河边走,问当地人:“我有一个陌生的地址:'Rivergate,第二左,第三宫'。你能帮我吗?” 当地人说:“当然,您在正确的道路上,只需继续前进,您将在半小时内到达目的地。”

我认为这几乎说明了这一点。

好消息是:通常不需要真实的主机名。在大多数情况下,任何解析为该主机上IP地址的名称都可以。(陌生人可能会通过北门(Northgate)进入该城市,但乐于助人的当地人会翻译“第二左”部分。)

如果还有其他情况,则必须使用此配置设置的确定来源-C函数gethostname(2)。该函数也由程序调用hostname


9
并不是我希望的答案,但是现在我知道如何问一个更好的问题了,谢谢。
山姆·哈斯勒


4
这很好地说明了任何软件(不仅是Java程序)在确定主机名方面的局限性。但是,请注意,getHostName是根据基础操作系统实现的,大概与主机名/ gethostname的方式相同。在“常规”系统上,InetAddress.getLocalHost()。getHostName()等效于调用主机名/ gethostname,因此您不能真正说出一个以其他方式失败的方式失败。
彼得·卡多纳

System.err.println(Runtime.getRuntime()。exec(“ hostname”)); 给我这个:java.lang.UNIXProcess@6eb2384f
user152468

5
InetAddress.getLocalHost()。getHostName()的实现实际上是确定性的:)如果您遵循源代码,则它最终将在Windows上调用gethostname(),并在Unixy系统上调用getaddrinfo()。结果与使用OS主机名命令相同。现在,主机名可能会提供您不想使用的答案,这可能有很多原因。通常,软件应在配置文件中从用户那里获取主机名,这样,它始终是正确的主机名。如果用户不提供值,则可以将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 2013年

1
同样,InetAddress.getLocalHost()在某些情况下,返回回送设备。在这种情况下,getHostName()返回“ localhost”,这不是很有用。
olenz 2016年

53

正如其他人指出的那样,基于DNS解析获取主机名是不可靠的。

不幸的是,由于这个问题在2018年仍然很重要,因此我想与您分享我的独立于网络的解决方案,并在不同的系统上进行一些测试。

以下代码尝试执行以下操作:

  • 在Windows上

    1. COMPUTERNAME通过阅读环境变量System.getenv()

    2. 执行hostname.exe并阅读回复

  • 在Linux上

    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
"

编辑:根据legolas108System.getenv("HOSTNAME")如果您export HOSTNAME在执行Java代码之前运行,则可在Ubuntu 14.04上运行。

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在Ubuntu 14.04 LTS上运行Java代码之前,也会返回System.getenv("HOSTNAME")
legolas108 '16

您必须明确地export HOSTNAME让Java使用它吗?我认为它应该是像PATH这样的默认环境变量。
大卫,

2
是的,这是出于宗教原因永远不会修复的Java错误之一。一个相关的是能够设置进程名称。错误记录将近20年前。
调谐的

很好的答案,非常彻底!我不知道您为什么使用这个奇怪的分隔符,特别是考虑到每个打印输出中都有一个奇怪的换行符。无论如何,我将更新答案以与MacOS一起使用,将indexOf缩短为包含调用,并修复OS变量名称,使其与标准变量名称约定更加一致。
查理

1
@Charlie \A分隔符只是一种使用读取整个InputStream的技巧Scanner
麦芽

24

InetAddress.getLocalHost().getHostName() 更好(如尼克解释),但仍然不是很好

一个主机可以用许多不同的主机名来识别。通常,您将在特定上下文中查找主机的主机名。

例如,在Web应用程序中,您可能正在寻找发出您当前正在处理的请求的人所使用的主机名。如何最好地找到一个取决于您用于Web应用程序的框架。

在某些其他面向Internet的服务中,您将希望可以从“外部”获得服务的主机名。由于代理服务器,防火墙等的原因,它甚至可能不是您安装服务的计算机上的主机名-您可以尝试使用一个合理的默认值,但无论安装它的人都应该将该配置设为可配置。


18

尽管已经回答了这个话题,但是还有更多话要说。

首先:显然,我们在这里需要一些定义。从网络的角度来看,InetAddress.getLocalHost().getHostName()为您提供了主机名。这种方法的问题已在其他答案中得到了很好的证明:它通常需要进行DNS查找,如果主机具有多个网络接口,则它是模棱两可的,有时甚至会完全失败(请参阅下文)。

但是在任何操作系统上,也都有另一个名字。在启动过程的很早就定义了主机的名称,很早就初始化了网络。Windows将其称为计算机名,Linux将其称为内核主机名,Solaris使用该词 nodename。我最喜欢“ 计算机名”一词,因此从现在开始我将使用该词。

查找计算机名

  • 在Linux / Unix上,计算机名是从C函数获得的gethostname(),或者hostnameHOSTNAME类似于Bash的shell中的shell或环境变量命令。

  • 在Windows上,计算机名是您从环境变量COMPUTERNAME或Win32 GetComputerName函数获得的名称。

Java无法获得我定义的“计算机名”。当然,还有其他答案中所述的解决方法,例如Windows调用System.getenv("COMPUTERNAME"),但是在Unix / Linux上,如果不诉诸JNI / JNA或,就没有很好的解决方法Runtime.exec()。如果您不介意JNI / JNA解决方案,则可以使用gethostname4j,它非常简单且易于使用。

让我们继续两个示例,一个来自Linux,一个来自Solaris,它们演示了如何轻松进入使用标准Java方法无法获得计算机名的情况。

Linux示例

在新创建的系统上,在安装过程中将主机命名为“芝加哥”,我们现在更改所谓的内核主机名:

$ 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示例

下面的示例基于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报告:link1link2。但是,从对该报告的评论来看,该问题似乎在很大程度上已被JDK团队误解,因此不太可能解决该问题。

我喜欢将RFE与其他编程语言进行比较。


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
不断手动编写“空”检查是很不好的行为。许多项目的检查都是手动执行(按照您的建议方式进行),并且不一致,导致许多错误。使用图书馆。
Thomas W

15
万一有人看到StringUtils它,并被Apache commons-lang项目提供的混淆了。强烈建议使用它或类似的东西。
JBCP

System.getenv("HOSTNAME")通过Mac上的Beanshell,以某种方式在Mac上显示为null,但可以将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
除了可移植性,该方法与问题中的方法相比如何?优点/缺点是什么?
Marquez 2013年


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
这种方法有什么好处?
Sam Hasler

1
这是无效的,它仅提供不是环回适配器的第一个NIC的名称。
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解析,如果您通过手机连接到wifi网路共享(仅举一个例子),它将没有知道本地主机的DNS,那么它将无法工作。
Dan Ortega

-2

InetAddress.getLocalHost()。getHostName()是两者中最好的方法,因为这是开发人员级别上的最佳抽象。


我正在寻找一个答案,该答案涉及一个主机名很多的主机。
山姆·哈斯勒

@SamHasler,您的具体情况是?正如Arnout所解释的,根据您的需求,有不同的方法。
尼克·诺尔森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.