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

Posted

技术标签:

【中文标题】在 Java 中获取主机名的推荐方法【英文标题】:Recommended way to get hostname in Java 【发布时间】:2011-11-13 00:07:14 【问题描述】:

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

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

对比

InetAddress.getLocalHost().getHostName()

【问题讨论】:

这是什么技术栈? 我认为唯一真正的 uname (uts_name) 支持的名称来自 RMI/JMX VMID,但这是特定于实现的。 【参考方案1】:

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

InetAddress.getLocalHost().getHostName()

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

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

另外,不要将 IP 地址的名称与主机的名称(主机名)混淆。一个比喻可能会更清楚:

有一个叫“伦敦”的大城市(服务器)。城墙内有很多生意。这座城市有几个大门(IP 地址)。每个门都有一个名字(“北门”、“河门”、“南安普顿门”……)但门的名字并不是城市的名字。此外,您不能通过使用门的名称来推断城市的名称——“北门”将捕获一半的大城市,而不仅仅是一个城市。然而——一个陌生人(IP包)沿着河边走,问一个当地人:“我有一个奇怪的地址:'Rivergate, second left, third house'。你能帮帮我吗?”当地人说:“当然,你在正确的道路上,只要继续前进,半小时内就会到达目的地。”

我认为这很能说明问题。

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

在其余的极端情况下,您必须使用此配置设置的权威源 - 这是 C 函数 gethostname(2)。该函数也被程序hostname调用。

【讨论】:

不是我希望的答案,但现在我知道如何提出更好的问题,谢谢。 另见***.com/questions/6050011/… 这很好地描述了任何软件(不仅仅是 Java 程序)在确定主机名时所具有的局限性。但是,请注意 getHostName 是根据底层操作系统实现的,大概与 hostname/gethostname 相同。在“正常”系统上,InetAddress.getLocalHost().getHostName() 等同于调用主机名/gethostname,所以你不能说一个失败,另一个失败。 System.err.println(Runtime.getRuntime().exec("hostname"));给我这个:java.lang.UNIXProcess@6eb2384f InetAddress.getLocalHost().getHostName() 的实现实际上是非常确定的 :) 如果按照源代码,它最终在 Windows 上调用 gethostname(),在 Unixy 系统上调用 getaddrinfo()。结果与使用您的操作系统主机名命令相同。现在主机名可能会提供您不想使用的答案,这可能有很多原因。通常,软件应该在配置文件中从用户那里获取主机名,这样,它始终是正确的主机名。如果用户不提供值,您可以使用 InetAddress.getLocalhost().getHostName() 作为默认值。【参考方案2】:

InetAddress.getLocalHost().getHostName() 是更便携的方式。

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

以下是关于 SO 的其他一些相关答案:

Java current machine name and logged in user? Get DNS name of local machine as seen by a remote machine

编辑:您应该查看A.H.'s answer 或Arnout Engelen's answer,详细了解为什么这可能无法按预期工作,具体取决于您的情况。作为这个特别要求便携的人的答案,我仍然认为getHostName() 很好,但他们提出了一些应该考虑的优点。

【讨论】:

运行 getHostName() 有时会导致错误。例如,这是我在 Amazon EC2 AMI Linux 实例中得到的:java.net.UnknownHostException: Name or service not known java.net.InetAddress.getLocalHost(InetAddress.java:1438) 另外,InetAddress.getLocalHost() 在某些情况下会返回环回设备。在这种情况下,getHostName() 返回“localhost”,这不是很有用。【参考方案3】:

正如其他人所指出的,根据 DNS 解析获取主机名是不可靠的。

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

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

在 Windows 上

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

    执行hostname.exe并读取响应

在 Linux 上

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

    执行hostname并读取响应

    读取/etc/hostname(为此,我正在执行cat,因为sn-p 已经包含要执行和读取的代码。不过,简单地读取文件会更好)。

代码:

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

不同操作系统的结果:

ma​​cOS 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 代码之前运行export HOSTNAMESystem.getenv("HOSTNAME") 可以在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 时的额外换行符,在某些情况下您可能需要考虑到它。

【讨论】:

如果您在 Ubuntu 14.04 LTS 上运行 Java 代码之前export HOSTNAME 也会由System.getenv("HOSTNAME") 返回。 你必须明确export HOSTNAME 才能让Java 使用它?我认为它应该是像 PATH 这样的默认环境变量。 是的,这是由于宗教原因永远无法修复的 Java 错误之一。相关的一个是能够设置进程名称。大约 20 年前记录的错误。 很好的回答,很透彻!我不知道你为什么使用那个奇怪的分隔符,特别是考虑到每个打印输出都有一个奇怪的换行符。无论如何,我正在更新使用 MacOS 的答案,将 indexOf 缩短为包含调用,并修复 OS 变量名称以更符合标准变量名称约定。 @Charlie \A 分隔符只是使用 Scanner 读取整个 InputStream 的一个技巧。【参考方案4】:

InetAddress.getLocalHost().getHostName() 更好(正如 Nick 解释的那样),但仍然不是很好

可以通过许多不同的主机名知道一个主机。通常,您会在特定上下文中查找主机的主机名。

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

在其他某种面向 Internet 的服务中,您会希望您的服务可以通过“外部”获得主机名。由于代理、防火墙等原因,这甚至可能不是您安装服务的机器上的主机名 - 您可能会尝试提供一个合理的默认值,但您绝对应该为安装它的人设置此配置。

【讨论】:

【参考方案5】:

虽然这个话题已经回答了,但还有很多话要说。

首先:显然我们需要一些定义。 InetAddress.getLocalHost().getHostName() 为您提供从网络角度看的主机名称。这种方法的问题在其他答案中有很好的记录:它通常需要 DNS 查找,如果主机有多个网络接口并且有时会失败(见下文),它会很模糊。

但在任何操作系统上都有另一个名称。在启动过程中很早就定义的主机名称,早在网络初始化之前。 Windows 将其称为 computername,Linux 将其称为 kernel hostname,而 Solaris 使用单词 nodename。我最喜欢计算机名这个词,所以从现在开始我会使用这个词。

查找计算机名

在 Linux/Unix 上,计算机名称是您从 C 函数 gethostname() 或 shell 中的 hostname 命令或 Bash 类 shell 中的 HOSTNAME 环境变量中获得的名称。

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

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。你基本上被卡住了。无法检索值 'dallas' 和值 'chicago'。

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 示例一样。无法检索值 'dallas' 和值 'chicago'。

你什么时候才能真正解决这个问题?

您经常会发现InetAddress.getLocalHost().getHostName() 确实会返回一个等于计算机名的值。所以没有问题(除了名称解析的额外开销)。

问题通常出现在 PaaS 环境中,其中计算机名和环回接口的名称之间存在差异。例如,人们报告 Amazon EC2 中的问题。

错误/RFE 报告

稍作搜索发现此 RFE 报告:link1、link2。但是,从该报告中的 cmets 来看,JDK 团队似乎在很大程度上误解了该问题,因此不太可能得到解决。

我喜欢 RFE 与其他编程语言的比较。

【讨论】:

【参考方案6】:

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

见:https://***.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;

【讨论】:

StringUtils?那是什么?? (我知道你的意思,我只是认为为了这个唯一目的引入一个外部库是不好的业力......而且最重要的是,甚至没有提到它) 不断手动编写“空”支票是不好的业力。很多项目都手动完成了此类检查(按照您的建议)并且不一致,因此产生了许多错误。使用图书馆。 如果有人看到这个并且实际上被 StringUtils 弄糊涂了,它由 Apache commons-lang 项目提供。强烈推荐使用它或类似的东西。 不知何故,System.getenv("HOSTNAME") 在 Mac 上通过 Beanshell for Java 显示为空,但 PATH 被提取正常。我也可以在 Mac 上做echo $HOSTNAME,只是 System.getenv("HOSTNAME") 似乎有问题。奇怪。【参考方案7】:

只是单线...跨平台(Windows-Linux-Unix-Mac(Unix))[始终有效,无需 DNS]:

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

你已经完成了!

【讨论】:

InetAddress.getLocalHost().getHostName() 在 Unix 中不起作用? 这可行,但需要 dns 解析,如果您从手机连接到 wifi 网络共享(仅举一个例子),它不会有一个知道 localhost 机器的 DNS,它不会工作。 【参考方案8】:

在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);
    

【讨论】:

抛开可移植性不谈,这种方法与问题中的方法相比如何?优点/缺点是什么?【参考方案9】:

如果您不反对使用来自 maven Central 的外部依赖项,我编写了 gethostname4j 来为自己解决这个问题。它只是使用 JNA 调用 libc 的 gethostname 函数(或在 Windows 上获取 ComputerName)并将其作为字符串返回给您。

https://github.com/mattsheppard/gethostname4j

【讨论】:

谢谢马特,但这似乎不再起作用了。它在我的 macOS 11.4 上无限期阻塞。 啊 - 感谢您的提醒 - 我会尝试检查一下。我认为我拥有 11.4 的唯一机器是 Apple Silicon,所以这可能会很有趣 :) 我无法重现该问题,但欢迎与任何可以重现该问题的人进行讨论(可能在 github 问题中而不是此处)。【参考方案10】:
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();
            
        
    

【讨论】:

这种方法有什么好处? 这是无效的,它只提供了第一个不是环回适配器的网卡的名称。 实际上它是第一个具有主机名的非环回......不一定是第一个非环回。【参考方案11】:

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

【讨论】:

我正在寻找一个与一个主机名知道的主机打交道的答案。 @SamHasler,你的具体情况是什么?正如 Arnout 解释的那样,根据您的需要,有不同的方法。

以上是关于在 Java 中获取主机名的推荐方法的主要内容,如果未能解决你的问题,请参考以下文章

如何获取本地 Wi-Fi 网络中主机名的 IP?安卓工作室(JAVA)

按主机名的 IPv6 地址 [关闭]

Linux 中改变主机名的 4 种方法

Ubuntu查看并修改主机名的方法#私藏项目实操分享#

CentOS7修改主机名的三种方法

centos 7 修改主机名的方法