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

353

在Java中获取当前计算机主机名的最佳且最便携的方法是哪个?

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

InetAddress.getLocalHost().getHostName()


这是什么技术栈? - tom redfern
我认为唯一真正支持 uname (uts_name) 的名称来自于 RMI/JMX VMID,但这取决于具体的实现。 - eckes
可能会感兴趣的是:在GNU/Linux(以及其他系统)上,命令hostname(1)实际上调用了系统调用uname(3)(在POSIX中定义),该调用可以在/usr/include/sys/utsname.h中找到。(通过运行strace hostname来尝试一下)。相应的命令是uname(1)。因此,要获取所谓的“主机名”,实际上是“节点名”,也可以使用命令uname --nodename。这通常是可以在/etc/hostname中找到的字符串。 - David Tonhofer
12个回答

424

严格来说,你只能调用hostname(1)或在Unix上调用gethostname(2)来获取计算机的名称。这就是你的计算机名。任何试图通过IP地址来确定主机名的尝试都是无效的。

InetAddress.getLocalHost().getHostName()

在某些情况下,绑定将失败:

  • IP地址可能无法解析为任何名称。这可能是不良的DNS设置、系统设置或提供者设置的原因。
  • DNS中的名称可以有许多别名称为CNAME。这些只能在一个方向上正确解析:从名称到地址。反向方向是不明确的。哪个是“官方”名称?
  • 主机可以有许多不同的IP地址 - 每个地址都可以有许多不同的名称。两种常见情况是:一个以太网端口具有几个“逻辑”IP地址或计算机具有几个以太网端口。它可以配置它们是否共享IP或具有不同的IP。这被称为“multihomed”。
  • DNS中的一个名称可以解析为多个IP地址。而且并非所有这些地址都必须位于同一台计算机上!(用例:负载平衡的简单形式)
  • 更不要谈动态IP地址了。

还不要混淆IP地址的名称和主机名(主机名)。比喻可能会更清晰:

有一个名为“伦敦”的大城市(服务器)。城市墙内发生了许多业务。城市有几个门(IP地址)。每个门都有一个名称(“North Gate”、“River Gate”、“Southampton Gate”...),但门的名称不是城市的名称。您也不能通过使用门的名称来推断城市的名称 - “North Gate”将捕获更大的城市的一半,而不仅仅是一个城市。但是 - 一个陌生人(IP数据包)沿着河流走,并问当地人:“我有一个奇怪的地址:‘Rivergate,第二个左,第三个房子’。你能帮我吗?”当地人说:“当然,你走在正确的路上,只需继续前进,你将在半小时内到达目的地。”

我认为这很清楚了。

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

在剩下的角落情况中,您必须使用此配置设置的确定性源 - 这是C函数gethostname(2)。该功能也由程序hostname调用。


15
虽然不是我所期望的答案,但现在我知道如何提出更好的问题了,谢谢。 - Sam Hasler
3
请参阅https://dev59.com/l2025IYBdhLWcg3wW0yx。 - Raedwald
8
这是对任何软件(不仅仅是Java程序)在确定主机名方面的限制的很好的说明。然而,请注意,getHostName是根据底层操作系统实现的,可能与hostname/gethostname相同。在“正常”系统上,InetAddress.getLocalHost().getHostName()等价于调用hostname/gethostname,因此你不能真正地说其中一个会失败而另一个不会。 - Peter Cardona
19
InetAddress.getLocalHost().getHostName() 的实现实际上非常确定。如果您跟踪源代码,它最终会在 Windows 上调用 gethostname(),在 Unixy 系统上调用 getaddrinfo()。结果与使用操作系统 hostname 命令的结果相同。现在,hostname 可能提供您不想使用的答案,这是可能的原因有很多。一般来说,软件应该从用户的配置文件中获取主机名,这样它总是正确的。如果用户没有提供值,可以将 InetAddress.getLocalhost().getHostName() 用作默认值。 - Greg
1
还要注意,InetAddress.getLocalHost javadoc指出: “返回本地主机的地址。这是通过从系统检索主机名,然后将该名称解析为InetAddress来实现的。” 这表明首先找到主机名,然后从中获取IP地址(而不是相反)。此外,请注意Runtime.exec()涉及安全管理器检查,因此可能会根据安全策略被阻止。 - Mr Weasel
显示剩余2条评论

111

InetAddress.getLocalHost().getHostName()是更可移植的方式。

exec("hostname") 实际上会调用操作系统来执行 hostname 命令。

以下是一些与此相关的答案:

编辑:你应该查看A.H.'s answer 或者 Arnout Engelen's answer 以获取关于为什么可能无法按预期工作的详细信息,这取决于您的情况。作为一个针对特定请求“可移植”的答案,我仍然认为getHostName()是可以的,但他们提出了一些应该考虑的好建议。


14
有时调用getHostName()会出现错误。例如,在Amazon EC2 AMI Linux实例中,我得到了以下结果:java.net.UnknownHostException: Name or service not known java.net.InetAddress.getLocalHost(InetAddress.java:1438) - Marquez
3
有些情况下,InetAddress.getLocalHost() 返回的是回环设备。在这种情况下,getHostName() 会返回 "localhost",这并不是很有用。 - olenz

79

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

由于这个问题在2018年仍然存在,我想分享一下我的网络独立解决方案,并在不同系统上进行一些测试运行。

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

  • 在Windows上

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

    2. 执行hostname.exe并读取响应。

  • 在Linux上

    1. 通过System.getenv()读取HOSTNAME环境变量。

    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的说法,在执行Java代码之前在Ubuntu 14.04上运行export HOSTNAMESystem.getenv("HOSTNAME")可以正常工作。

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 时会出现额外的换行符,在某些情况下您可能需要考虑它。


7
如果在Ubuntu 14.04 LTS上运行Java代码之前,执行export HOSTNAME命令,那么System.getenv("HOSTNAME")也会返回相同的值。请注意,本句中涉及的所有英文词汇已经被正确翻译。 - legolas108
6
是的,这是其中一个由于宗教原因而永远不会修复的Java缺陷。另一个相关的问题是能否设置进程名称,这些缺陷在将近20年前已经被记录。 - Tuntable
1
@Charlie,\A分隔符只是使用Scanner读取整个InputStream的一种hack方法。 - Malt
确定应该使用"\A"而不是"\A"吗? - Charlie
为什么代码期望那个序列?我想象控制台会打印反斜杠和大写字母A...为什么要寻找那个?扫描器中是否存在一些未记录的行为? - Charlie
显示剩余5条评论

28

InetAddress.getLocalHost().getHostName()是更好的选择(正如Nick所解释的),但仍不是非常好的选择。

一个主机可以有许多不同的主机名。通常情况下,您会寻找您的主机在特定上下文中使用的主机名。

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

在某些其他面向互联网的服务中,您将需要从“外部”获得您的服务可用的主机名。由于代理,防火墙等原因,这甚至可能不是安装您的服务的计算机上的主机名 - 您可以尝试提供一个合理的默认值,但一定要让安装此服务的人员可以配置。


25
虽然这个问题已经有了答案,但还有更多需要探讨。首先,我们需要一些定义。`InetAddress.getLocalHost().getHostName()` 从网络方面获取主机名。使用这种方法存在问题,在其他答案中详细说明:通常需要进行 DNS 查询,如果主机有多个网络接口,则不明确,有时甚至无法正常工作(见下文)。
但是,在任何操作系统上都有另一个名称。一个名称非常早在启动过程中就被定义了,在网络初始化之前。Windows 称其为 "computername",Linux 称其为 "kernel hostname",Solaris 使用 "nodename" 这个词。我最喜欢 "computername" 这个词,所以从现在开始我将使用这个词。
寻找计算机名:
- 在 Linux/Unix 上,计算机名是从 C 函数 `gethostname()`,或者从 shell 的 `hostname` 命令或者 Bash-like shells 的 `HOSTNAME` 环境变量中获取的。 - 在 Windows 上,计算机名是从环境变量 `COMPUTERNAME` 或 Win32 的 `GetComputerName` 函数中获取的。
Java 没有办法获得我所定义的“computername”。当然,有一些解决方法,如在 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
dallas

但我们仍然拥有

$ cat /etc/hosts
127.0.0.1   localhost
127.0.0.1   chicago

这并没有错误配置。它只是意味着主机的网络名称(或者说回环接口的名称)与主机的计算机名称不同。

现在,尝试执行InetAddress.getLocalHost().getHostName(),它会抛出java.net.UnknownHostException异常。你基本上被卡住了。无法检索到'dallas'或'chicago'的值。

Solaris示例

以下示例基于Solaris 11.3。

主机已经有意地配置成环回名称 <> nodename。

换句话说,我们有:

$ 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示例一样,你现在被卡住了。无法检索到值“dallas”和值“chicago”。

什么时候会遇到这个问题?

通常情况下,InetAddress.getLocalHost().getHostName()确实会返回等于计算机名称的值。 因此没有问题(除了名称解析的额外开销)。

该问题通常出现在PaaS环境中,其中计算机名称与环回接口的名称不同。例如,人们报告Amazon EC2中出现问题。

Bug/RFE报告

一些搜索显示了此RFE报告:link1link2。然而,根据该报告上的评论,JDK团队似乎大多数误解了这个问题,所以不太可能得到解决。

我喜欢这篇RFE中对其他编程语言的比较。


19

只需要一行代码即可实现跨平台(Windows-Linux-Unix-Mac(Unix))[始终可用,无需 DNS]:

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

你完成了!!


InetAddress.getLocalHost().getHostName() 在 Unix 中无法工作? - Satish Patro
2
这个方法是可行的,但需要进行DNS解析。如果你连接的是手机的WiFi共享网络(只是举个例子),那么它可能没有DNS知道本地主机,因此无法正常工作。 - Dan Ortega
稍微有些不同:Process p = Runtime.getRuntime().exec("hostname"); byte[] bytes = p.getInputStream().readAllBytes(); String hostname = new String(bytes,"ASCII").trim(); 注意使用 .trim() 去除 hostname(或者是 uname --nodename)命令生成的多余 EOL。整个序列需要被 try/catch 包围。 - David Tonhofer

14

环境变量也可能提供一个有用的方法--在Windows上是COMPUTERNAME,在大多数现代Unix/Linux shell上是HOSTNAME

参见:https://dev59.com/4Wsz5IYBdhLWcg3weHgA#17956000

我将这些作为“补充”方法使用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;

7
StringUtils是什么?(我知道你的意思,但我认为仅为此目的引入外部库是不好的因果报应.. 而且还没有提及它) - peterh
30
手动不断书写“空”支票是一种不良的业力。很多项目都采用手动方式(如您所建议的方式)进行此类检查,而且不一致,造成很多错误。请使用一个库。 - Thomas W
20
如果有人看到这个并且对 StringUtils 感到困惑,它是由 Apache commons-lang 项目提供的。强烈建议使用它或类似的工具。 - JBCP
在使用Java的Beanshell时,通过System.getenv("HOSTNAME")获取Mac主机名时出现了null值,但是PATH却可以正常提取。我也可以在Mac上执行echo $HOSTNAME,只是System.getenv("HOSTNAME")似乎有问题。很奇怪。 - David

7
如果您不反对使用来自maven中央仓库的外部依赖,我写了gethostname4j来解决这个问题。它只是使用JNA调用libc的gethostname函数(或在Windows上获取ComputerName),并将其作为字符串返回给您。 https://github.com/mattsheppard/gethostname4j

谢谢Matt,但是这个似乎不再起作用了。在我的macOS 11.4上它会无限制地阻塞。 - Saket
啊,谢谢提醒,我会尽快去查看。我想我唯一拥有11.4版本的机器是一台苹果芯片的电脑,这可能会很有趣 :) - Matt Sheppard
我无法重现这个问题,但是欢迎与任何能够重现它的人进行讨论(也许可以在 GitHub 的问题中进行,而不是在这里)。 - Matt Sheppard

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

5
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
实际上,它是第一个具有主机名的非回环地址...不一定是第一个非回环地址。 - Adam Gent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接