在 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() : "";
不同操作系统的结果:
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 HOSTNAME
,System.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 中获取主机名的推荐方法的主要内容,如果未能解决你的问题,请参考以下文章