下面哪一种方法是在Java中获得当前计算机主机名的最佳和最可移植的方法?
Runtime.getRuntime().exec(“hostname”)
vs
InetAddress.getLocalHost().getHostName()
下面哪一种方法是在Java中获得当前计算机主机名的最佳和最可移植的方法?
Runtime.getRuntime().exec(“hostname”)
vs
InetAddress.getLocalHost().getHostName()
当前回答
虽然这个话题已经有了答案,但还有更多要说的。
首先,显然我们需要一些定义。InetAddress.getLocalHost(). gethostname()提供了从网络角度看的主机名称。这种方法的问题在其他答案中有很好的记录:它通常需要DNS查找,如果主机有多个网络接口,它是不明确的,有时它只是失败(见下文)。
但在任何操作系统上都有另一个名称。在引导过程的早期定义的主机名,远早于网络初始化。Windows将其称为计算机名,Linux将其称为内核主机名,Solaris则使用节点名。我最喜欢计算机名这个词,所以从现在开始我就用这个词了。
查找计算机名
在Linux/Unix上,计算机名是从C函数gethostname()中获得的,或者是从shell中获得的主机名命令或Bash-like 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环境中,其中计算机名和环回接口名之间存在差异。例如,人们报告亚马逊EC2中的问题。
错误/ RFE报告
稍微搜索一下就可以看到这个RFE报告:link1, link2。然而,从对该报告的评论来看,这个问题似乎在很大程度上被JDK团队误解了,所以它不太可能得到解决。
我喜欢RFE与其他编程语言的比较。
其他回答
正如其他人所指出的,根据DNS解析获取主机名是不可靠的。
不幸的是,由于这个问题在2018年仍然相关,我想与大家分享我的网络独立解决方案,并在不同的系统上进行一些测试。
下面的代码尝试执行以下操作:
在Windows上 通过System.getenv()读取环境变量COMPUTERNAME。 执行hostname.exe并读取响应 在Linux上 通过System.getenv()读取HOSTNAME环境变量 执行主机名并读取响应 读取/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上工作,如果你在执行Java代码之前运行export 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
"
机器名称已被替换,但我保留了大写和结构。注意在执行主机名时额外的换行符,在某些情况下可能必须考虑到它。
InetAddress.getLocalHost(). gethostname()是更可移植的方式。
Exec(“hostname”)实际上调用操作系统来执行hostname命令。
以下是关于SO的一些其他相关答案:
Java当前机器名和登录用户? 获取本地机器的DNS名称,就像远程机器看到的那样
编辑:你应该看看A.H.的回答,或者Arnout Engelen的回答,详细说明为什么这可能不会像预期的那样工作,这取决于你的情况。作为对这个特别要求可移植的人的回答,我仍然认为getHostName()很好,但它们提出了一些值得考虑的优点。
如果您不反对使用来自maven中心的外部依赖,我编写了gethostname4j来为自己解决这个问题。它只是使用JNA来调用libc的gethostname函数(或在Windows上获取ComputerName)并将其作为字符串返回给您。
https://github.com/mattsheppard/gethostname4j
虽然这个话题已经有了答案,但还有更多要说的。
首先,显然我们需要一些定义。InetAddress.getLocalHost(). gethostname()提供了从网络角度看的主机名称。这种方法的问题在其他答案中有很好的记录:它通常需要DNS查找,如果主机有多个网络接口,它是不明确的,有时它只是失败(见下文)。
但在任何操作系统上都有另一个名称。在引导过程的早期定义的主机名,远早于网络初始化。Windows将其称为计算机名,Linux将其称为内核主机名,Solaris则使用节点名。我最喜欢计算机名这个词,所以从现在开始我就用这个词了。
查找计算机名
在Linux/Unix上,计算机名是从C函数gethostname()中获得的,或者是从shell中获得的主机名命令或Bash-like 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环境中,其中计算机名和环回接口名之间存在差异。例如,人们报告亚马逊EC2中的问题。
错误/ RFE报告
稍微搜索一下就可以看到这个RFE报告:link1, link2。然而,从对该报告的评论来看,这个问题似乎在很大程度上被JDK团队误解了,所以它不太可能得到解决。
我喜欢RFE与其他编程语言的比较。
InetAddress.getLocalHost().getHostName()是更好的(由Nick解释),但仍然不是很好
一个主机可以使用许多不同的主机名。通常,您将在特定的上下文中查找主机名。
例如,在web应用程序中,您可能要查找发出您当前正在处理的请求的人所使用的主机名。如何最好地找到它取决于你的web应用程序使用的框架。
在其他一些面向互联网的服务中,你会希望你的服务从“外部”可用的主机名。由于代理、防火墙等原因,这甚至可能不是安装服务的机器上的主机名——你可能会尝试提出一个合理的默认值,但你一定要让安装它的人都可以配置它。