下面哪一种方法是在Java中获得当前计算机主机名的最佳和最可移植的方法?
Runtime.getRuntime().exec(“hostname”)
vs
InetAddress.getLocalHost().getHostName()
下面哪一种方法是在Java中获得当前计算机主机名的最佳和最可移植的方法?
Runtime.getRuntime().exec(“hostname”)
vs
InetAddress.getLocalHost().getHostName()
InetAddress.getLocalHost(). gethostname()是更可移植的方式。
Exec(“hostname”)实际上调用操作系统来执行hostname命令。
以下是关于SO的一些其他相关答案:
Java当前机器名和登录用户? 获取本地机器的DNS名称,就像远程机器看到的那样
编辑:你应该看看A.H.的回答,或者Arnout Engelen的回答,详细说明为什么这可能不会像预期的那样工作,这取决于你的情况。作为对这个特别要求可移植的人的回答,我仍然认为getHostName()很好,但它们提出了一些值得考虑的优点。
InetAddress.getLocalHost().getHostName()是更好的(由Nick解释),但仍然不是很好
一个主机可以使用许多不同的主机名。通常,您将在特定的上下文中查找主机名。
例如,在web应用程序中,您可能要查找发出您当前正在处理的请求的人所使用的主机名。如何最好地找到它取决于你的web应用程序使用的框架。
在其他一些面向互联网的服务中,你会希望你的服务从“外部”可用的主机名。由于代理、防火墙等原因,这甚至可能不是安装服务的机器上的主机名——你可能会尝试提出一个合理的默认值,但你一定要让安装它的人都可以配置它。
严格地说,在Unix上,你没有选择,只能调用hostname(1)或- gethostname(2)。这是你电脑的名字。任何试图通过IP地址确定主机名的尝试,就像这样
InetAddress.getLocalHost().getHostName()
在某些情况下一定会失败:
The IP address might not resolve into any name. Bad DNS setup, bad system setup or bad provider setup may be the reason for this. A name in DNS can have many aliases called CNAMEs. These can only be resolved in one direction properly: name to address. The reverse direction is ambiguous. Which one is the "official" name? A host can have many different IP addresses - and each address can have many different names. Two common cases are: One ethernet port has several "logical" IP addresses or the computer has several ethernet ports. It is configurable whether they share an IP or have different IPs. This is called "multihomed". One Name in DNS can resolve to several IP Addresses. And not all of those addresses must be located on the same computer! (Usecase: A simple form of load-balancing) Let's not even start talking about dynamic IP addresses.
另外,不要将ip地址的名称与主机的名称(hostname)混淆。打个比方可能会更清楚:
There is a large city (server) called "London". Inside the city walls much business happens. The city has several gates (IP addresses). Each gate has a name ("North Gate", "River Gate", "Southampton Gate"...) but the name of the gate is not the name of the city. Also you cannot deduce the name of the city by using the name of a gate - "North Gate" would catch half of the bigger cities and not just one city. However - a stranger (IP packet) walks along the river and asks a local: "I have a strange address: 'Rivergate, second left, third house'. Can you help me?" The local says: "Of course, you are on the right road, simply go ahead and you will arrive at your destination within half an hour."
我认为这很好地说明了这一点。
好消息是:真实的主机名通常是不必要的。在大多数情况下,这台主机上任何解析为IP地址的名称都可以。(陌生人可能会从北门(Northgate)进入城市,但乐于助人的当地人会翻译“左边第二个”部分。)
在其余的情况下,您必须使用该配置设置的最终来源-这是C函数gethostname(2)。该函数也由程序主机名调用。
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上 通过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()提供了从网络角度看的主机名称。这种方法的问题在其他答案中有很好的记录:它通常需要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与其他编程语言的比较。
如果您不反对使用来自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)方法,它接受命令作为参数。
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();
}
}