建议在Java中获取主机名的方法

Mah*_*dra 262 java network-programming hostname environment-variables

以下哪项是获取Java当前计算机主机名的最佳和最便携方式?

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

VS

InetAddress.getLocalHost().getHostName()

A.H*_*.H. 326

严格来说 - 除了hostname(1)在Unix上调用之外你别无选择gethostname(2).这是您的计算机的名称.任何通过IP地址确定主机名的尝试都是这样的

InetAddress.getLocalHost().getHostName()
Run Code Online (Sandbox Code Playgroud)

在某些情况下必然会失败:

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

另外,请勿将IP地址的名称与主机名(主机名)混淆.一个比喻可能会让它更清晰:

有一个叫做"伦敦"的大城市(服务器).城墙内部发生了很多事情.这个城市有几个门(IP地址).每个门都有一个名字("北门","河门","南安普顿门"......),但门的名称不是城市的名称.此外,你不能通过使用门的名称来推断城市的名称 - "北门"将捕获一半的大城市,而不只是一个城市.然而 - 一个陌生人(IP包)沿着河边走来问当地人:"我有一个奇怪的地址:'Rivergate,第二个左边,第三个房子'.你能帮助我吗?" 当地人说:"当然,你走在正确的道路上,只需前进,你将在半小时内到达目的地."

这说明了我的想法.

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

如果剩下的角落情况,您必须使用此配置设置的权威来源 - 这是C函数gethostname(2).该功能也由程序调用hostname.

  • 不是我希望的答案,但现在我知道如何提出更好的问题,谢谢. (9认同)
  • InetAddress.getLocalHost().getHostName()的实现实际上是非常确定的:)如果你遵循源代码,它最终在Windows上调用gethostname(),在Unixy系统上调用getaddrinfo().结果与使用OS hostname命令相同.现在主机名可能会提供您不想使用的答案,这可能有很多原因.通常,软件应该从配置文件中获取用户的主机名,这样,它始终是正确的主机名.如果用户未提供值,则可以使用InetAddress.getLocalhost().getHostName()作为默认值. (5认同)
  • 这是对任何软件(不仅仅是Java程序)在确定世界主机名称时所具有的限制的一个很好的描述.但请注意,getHostName是根据底层操作系统实现的,大概与hostname/gethostname相同.在"普通"系统上,InetAddress.getLocalHost().getHostName()等同于调用hostname/gethostname,所以你不能真正说出一个失败而另一个失败. (4认同)
  • 另见http://stackoverflow.com/questions/6050011/how-do-i-get-the-local-hostname-if-unresolvable-through-dns-in-java (3认同)

Nic*_*son 91

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

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

以下是关于SO的其他几个相关答案:

编辑:您应该看看AH的答案Arnout Engelen的答案,详细说明为什么这可能无法按预期工作,具体取决于您的情况.作为这个特别要求携带的人的答案,我仍然觉得getHostName()很好,但是他们提出了一些应该考虑的好点.

  • 运行getHostName()会导致错误.例如,以下是我在Amazon EC2 AMI Linux实例中获得的内容:java.net.UnknownHostException:名称或服务未知java.net.InetAddress.getLocalHost(InetAddress.java:1438) (11认同)
  • 此外,在某些情况下,`InetAddress.getLocalHost()` 会返回环回设备。在这种情况下,`getHostName()` 返回“localhost”,这不是很有用。 (2认同)

Mal*_*alt 51

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

由于遗憾的是这个问题在2018年仍然存在,我想与您分享我的网络独立解决方案,并在不同系统上进行一些测试.

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

  • 在Windows上

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

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

  • 在Linux上

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

    2. 执行hostname并阅读响应

    3. 读取/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() : "";
    }
}
Run Code Online (Sandbox Code Playgroud)

不同操作系统的结果:

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:""
Run Code Online (Sandbox Code Playgroud)

OpenSuse 13.1

Unix-like computer name through env:"machinename"
Unix-like computer name through exec:"machinename
"
Unix-like computer name through /etc/hostname:""
Run Code Online (Sandbox Code Playgroud)

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
"
Run Code Online (Sandbox Code Playgroud)

编辑:根据legolas108,System.getenv("HOSTNAME")如果你运行,可以在Ubuntu 14.04上运行export HOSTNAME在执行Java代码之前.

Windows 7的

Windows computer name through env:"MACHINENAME"
Windows computer name through exec:"machinename
"
Run Code Online (Sandbox Code Playgroud)

Windows 10

Windows computer name through env:"MACHINENAME"
Windows computer name through exec:"machinename
"
Run Code Online (Sandbox Code Playgroud)

机器名称已被替换,但我保留了大写和结构.请注意执行时的额外换行符hostname,在某些情况下可能需要考虑它.

  • 如果你在Ubuntu 14.04 LTS上运行Java代码之前"导出HOSTNAME",它也会被`System.getenv("HOSTNAME")`返回. (3认同)
  • 是的,这是出于宗教原因永远不会修复的Java错误之一。一个相关的是能够设置进程名称。错误记录将近20年前。 (2认同)

Arn*_*len 23

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

一个主机可以在许多不同的主机名下知道.通常,您将在特定的上下文中查找主机所具有的主机名.

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

在某些其他面向互联网的服务中,您需要通过"外部"提供您的服务的主机名.由于代理,防火墙等,这甚至可能不是您的服务所安装的计算机上的主机名 - 您可能会尝试提供合理的默认设置,但您绝对应该为安装此设备的人配置此设置.


pet*_*erh 14

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

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

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

找到计算机名

  • 在Linux/Unix上,计算机名是从C函数获得的gethostname(),或者是hostname来自shell或HOSTNAME环境变量的命令,类似于Bash的shell.

  • 在Windows上,computername是您从环境变量COMPUTERNAME或Win32 GetComputerName函数获得的.

Java无法获得我所定义的'computername'.当然,还有其他答案中描述的解决方法,比如Windows调用System.getenv("COMPUTERNAME"),但在Unix/Linux上,如果不诉诸JNI/JNA或者没有好的解决方法Runtime.exec().如果您不介意JNI/JNA解决方案,那么gethostname4j很简单且非常容易使用.

让我们继续两个例子,一个来自Linux,另一个来自Solaris,它们演示了如何使用标准Java方法无法获得计算机名的情况.

Linux的例子

在新创建的系统中,安装期间的主机被命名为"chicago",我们现在更改所谓的内核主机名:

$ hostnamectl --static set-hostname dallas
Run Code Online (Sandbox Code Playgroud)

现在内核主机名是'dallas',从hostname命令可以看出:

$ hostname
dallas
Run Code Online (Sandbox Code Playgroud)

但我们还有

$ cat /etc/hosts
127.0.0.1   localhost
127.0.0.1   chicago
Run Code Online (Sandbox Code Playgroud)

这没有错误的配置.它只是意味着主机的网络名称(或者更确切地说是环回接口的名称)与主机的计算机名称不同.

现在,尝试执行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
Run Code Online (Sandbox Code Playgroud)

和/ etc/hosts的内容:

:1 chicago localhost
127.0.0.1 chicago localhost loghost
Run Code Online (Sandbox Code Playgroud)

并且hostname命令的结果将是:

$ hostname
dallas
Run Code Online (Sandbox Code Playgroud)

就像在Linux示例中一样,调用InetAddress.getLocalHost().getHostName()将失败

java.net.UnknownHostException: dallas:  dallas: node name or service name not known
Run Code Online (Sandbox Code Playgroud)

就像Linux的例子一样,你现在卡住了.没有办法既不检索'dallas'值也不检索'chicago'值.

你什么时候真的会挣扎?

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

问题通常发生在PaaS环境中,其中computername和loopback接口的名称之间存在差异.例如,人们报告Amazon EC2中的问题.

Bug/RFE报告

一些搜索揭示了这个RFE报告:link1,link2.但是,从对该报告的评论来看,这个问题似乎在很大程度上被JDK团队误解了,因此不太可能解决.

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


Tho*_*s W 11

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

请参阅: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;
Run Code Online (Sandbox Code Playgroud)

  • 不断手动编写"空"支票是不好的业力.很多项目都会手动完成此类检查(以您的建议方式)并且不一致,导致许多错误.使用图书馆. (23认同)
  • 如果有人看到这个并且实际上被`StringUtils`弄糊涂了,它由Apache commons-lang项目提供.强烈建议使用它或类似的东西. (13认同)
  • StringUtils的?那是什么??(我知道你的意思我只是觉得为这个唯一的目的带来一个外部图书馆是不好的业力...而且最重要的是甚至没有提到它) (6认同)

Dan*_*ega 7

只是单行......跨平台(Windows-Linux-Unix-Mac(Unix))[总是有效,不需要DNS]:

String hostname = new BufferedReader(
    new InputStreamReader(Runtime.getRuntime().exec("hostname").getInputStream()))
   .readLine();
Run Code Online (Sandbox Code Playgroud)

你完成了 !!

  • 这可以工作,但需要 dns 解析,如果您从手机连接到 wifi 网络共享(仅举一个例子),它不会有一个知道本地主机的 DNS,并且它将无法工作。 (2认同)

Des*_*gos 6

在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 除了便携性,这种方法与问题中的方法相比如何?什么是利弊? (8认同)

小智 5

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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Mat*_*ard 5

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

https://github.com/mattsheppard/gethostname4j