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

下面哪一种方法是在Java中获得当前计算机主机名的最佳和最可移植的方法?

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

vs

InetAddress.getLocalHost().getHostName()

416980 次浏览

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

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

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

你应该看看A.H.的回答Arnout Engelen的回答,以了解为什么这可能无法正常工作的详细信息,这取决于你的情况。作为对这个特别要求便携式的人的回答,我仍然认为getHostName()很好,但他们提出了一些应该考虑的优点。

InetAddress.getLocalHost().getHostName()更好(由Nick解释),但仍然不是很好

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

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

在其他一些面向互联网的服务中,你会希望你的服务从“外部”可用的主机名。由于代理、防火墙等原因,这甚至可能不是安装服务的机器上的主机名——你可能会尝试提出一个合理的默认值,但你一定要让安装它的人都可以配置它。

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

严格地说-你没有选择,只能在Unix上调用hostname(1)或-。这是你电脑的名字。任何试图通过IP地址确定主机名的尝试,就像这样

InetAddress.getLocalHost().getHostName()

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

  • IP地址可能无法解析为任何名称。糟糕的DNS设置,糟糕的系统设置或糟糕的提供商设置可能是造成这种情况的原因。
  • DNS中的名称可以有许多别名,称为CNAMEs。这些问题只能在一个方向上正确地解决:姓名到地址。相反的方向是不明确的。哪一个是“官方”?的名字吗?
  • 一台主机可以有许多不同的IP地址,而且每个地址可以有许多不同的名称。两种常见的情况是:一个以太网端口有几个“逻辑”;IP地址或计算机有多个以太网端口。它们是否共享一个IP或具有不同的IP是可配置的。这就是所谓的“多家庭”。
  • DNS中的一个名称可以解析为多个IP地址。而且不是所有这些地址都必须位于同一台计算机上!(用例:负载平衡的简单形式)
  • 我们甚至不讨论动态IP地址。

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

有一个大城市叫做“伦敦”。在城墙内,很多生意都在进行。城市有几个门(IP地址)。每扇门都有一个名字(“北门”、“河门”、“南安普顿门”),但门的名字并不是城市的名字。同样,你也不能用城门的名字来推断城市的名字——“北门”。能赶上一半的大城市,而不仅仅是一个城市。然而,一个陌生人(IP包)沿着河边走,问当地人:“我有一个奇怪的地址:‘河门,第二个左边,第三个房子’。”你能帮帮我吗?当地人说:“当然,你走对了,往前走,半小时内你就会到达目的地。”

我认为这很好地说明了这一点。

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

在其余角落的情况下,您必须使用此配置设置的明确的源——即C函数gethostname(2)。该函数也由程序hostname调用。

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();
}
}
}
}

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

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

看: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;

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

不幸的是,由于这个问题在2018中仍然相关,我想与你分享我的网络独立解决方案,并在不同的系统上运行一些测试。

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

  • 在Windows上< p >

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

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

    3. 李< / ol > < / >
    4. 在Linux上的< p >

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

      2. 执行hostname并读取响应

      3. 读取/etc/hostname(为了做到这一点,我正在执行cat,因为代码段已经包含要执行和读取的代码。不过,简单地阅读文件会更好)。

      4. 李< / ol > < / >

代码:

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代码之前运行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时额外的换行符,在某些情况下你可能不得不考虑它。

虽然这个话题已经有了答案,但还有更多要说的。

首先,显然我们需要一些定义。InetAddress.getLocalHost().getHostName()给出了主机的名称从网络的角度来看。这种方法的问题在其他答案中有很好的记录:它通常需要DNS查找,如果主机有多个网络接口,它是不明确的,有时它只是失败(见下文)。

但在任何操作系统上都有另一个名称。在引导过程的早期定义的主机名,远早于网络初始化。Windows将其称为computername, Linux将其称为内核主机名, Solaris使用节点名这个词。我最喜欢computername这个词,所以从现在开始我将使用这个词。

查找计算机名

  • 在Linux/Unix上,计算机名是从C函数gethostname()或shell中的hostname命令或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报告:link1link2。然而,从对该报告的评论来看,这个问题似乎在很大程度上被JDK团队误解了,所以它不太可能得到解决。

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

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

https://github.com/mattsheppard/gethostname4j

只有一句话……跨平台(Windows-Linux-Unix-Mac(Unix))[始终工作,不需要DNS]:

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

你完蛋了!!

Dan Ortega的回答是的基础上,我创建了一个通用的executeCommand(String)方法,它以command作为参数。

import java.io.*;


public class SystemUtil {
public static void main(String[] args) throws IOException {
System.out.println(retrieveHostName());
}
     

public static String retrieveHostName() throws IOException {
return executeCommand("hostname");
}
     

private static String executeCommand(String command) throws IOException {
return new BufferedReader(
new InputStreamReader(Runtime.getRuntime().exec(command).getInputStream()))
.readLine();
}
}