JDK 11和JDK 8中InetAddress.getLocalHost().getHostName()的行为不同

11

我编写了一个简单的Java程序,基本上运行:

System.out.println(InetAddress.getLocalHost().getHostName());

如果我在RHEL 7.7的Java 1.7.231或1.8.221上编译并运行它,它会返回FQDN(computer.domain.com),但在同一台服务器上,使用RHEL JDK 11.0.2编译它将只返回服务器名称。

据我所知,它应该执行反向DNS查找(基本上是hostname -f),但使用JDK 11时行为明显不同。有任何想法为什么会出现这种情况吗?

4个回答

9

这可能是与此处报告的问题相同:InetAddress.getLocalhost()在java7和java8中结果不同

问题的关键在于JDK的更改:

自从http://hg.openjdk.java.net/jdk8/jdk8/jdk/rev/81987765cb81被推出后,我们调用getaddrinfo/getnameinfo来获取本地主机名,而不是旧的(已废弃的)gethostbyname_r/gethostbyaddr_r调用。

较新的调用遵循localhosts /etc/nsswitch.conf配置文件。在这台计算机的情况下,该文件告诉这些调用在引用其他命名服务之前查找文件。

由于/etc/hosts文件包含此主机名/IP组合的显式映射,因此返回的就是它。

在旧版JDK中,gethostbyname_r实际上会忽略本地计算机的设置并立即委托给命名服务。


4
在幕后,为了获取本地主机名,SDK执行了对底层操作系统的本地调用。
涉及的C函数是getLocalHostName。无论是IP版本4还是6,您都可以找到相应的实现:基本上它是相同的源代码,只需最少的更改即可考虑是否使用IP版本6。
例如,假设针对IP版本4的代码。
对于Java 11,相应的本地代码是在Inet4AddressImpl.c中实现的。这就是getLocalHostname的实现方式:
/*
 * Class:     java_net_Inet4AddressImpl
 * Method:    getLocalHostName
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL
Java_java_net_Inet4AddressImpl_getLocalHostName(JNIEnv *env, jobject this) {
    char hostname[NI_MAXHOST + 1];


    hostname[0] = '\0';
    if (gethostname(hostname, sizeof(hostname)) != 0) {
        strcpy(hostname, "localhost");
    } else {
#if defined(__solaris__)
        // try to resolve hostname via nameservice
        // if it is known but getnameinfo fails, hostname will still be the
        // value from gethostname
        struct addrinfo hints, *res;


        // make sure string is null-terminated
        hostname[NI_MAXHOST] = '\0';
        memset(&hints, 0, sizeof(hints));
        hints.ai_flags = AI_CANONNAME;
        hints.ai_family = AF_INET;


        if (getaddrinfo(hostname, NULL, &hints, &res) == 0) {
            getnameinfo(res->ai_addr, res->ai_addrlen, hostname, sizeof(hostname),
                        NULL, 0, NI_NAMEREQD);
            freeaddrinfo(res);
        }
#else
        // make sure string is null-terminated
        hostname[NI_MAXHOST] = '\0';
#endif
    }
    return (*env)->NewStringUTF(env, hostname);
}

作为您可以看到的,当使用与Solaris不同的东西时,代码似乎只依赖于gethostname来获取所需的值。这个限制是在this commit引入的,在this bug的背景下。 这里您可以看到Java 8的类似IP 4版本本地源代码实现。
在那个源代码中,您可以找到与Java 11之前的源代码几个不同之处。
首先,代码分为两个部分,具体取决于以下定义是否适用:
#if defined(__GLIBC__) || (defined(__FreeBSD__) && (__FreeBSD_version >= 601104))
#define HAS_GLIBC_GETHOSTBY_R   1
#endif




#if defined(_ALLBSD_SOURCE) && !defined(HAS_GLIBC_GETHOSTBY_R)

...

#else /* defined(_ALLBSD_SOURCE) && !defined(HAS_GLIBC_GETHOSTBY_R) */

...

如果条件成立不成立,则getLocalHostName的实现方式有所不同。

在我看来,在Redhat的情况下,条件不成立,因此以下代码是运行时使用的代码:

/************************************************************************
 * Inet4AddressImpl
 */


/*
 * Class:     java_net_Inet4AddressImpl
 * Method:    getLocalHostName
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL
Java_java_net_Inet4AddressImpl_getLocalHostName(JNIEnv *env, jobject this) {
    char hostname[NI_MAXHOST+1];


    hostname[0] = '\0';
    if (JVM_GetHostName(hostname, sizeof(hostname))) {
        /* Something went wrong, maybe networking is not setup? */
        strcpy(hostname, "localhost");
    } else {
        struct addrinfo hints, *res;
        int error;


        hostname[NI_MAXHOST] = '\0';
        memset(&hints, 0, sizeof(hints));
        hints.ai_flags = AI_CANONNAME;
        hints.ai_family = AF_INET;


        error = getaddrinfo(hostname, NULL, &hints, &res);


        if (error == 0) {/* host is known to name service */
            getnameinfo(res->ai_addr,
                        res->ai_addrlen,
                        hostname,
                        NI_MAXHOST,
                        NULL,
                        0,
                        NI_NAMEREQD);


            /* if getnameinfo fails hostname is still the value
               from gethostname */


            freeaddrinfo(res);
        }
    }
    return (*env)->NewStringUTF(env, hostname);
}

正如您所看到的,尽管是间接地使用JVM_GetHostName包含在C++代码中),但这最后一次实现仍然首先调用gethostname
JVM_LEAF(int, JVM_GetHostName(char* name, int namelen))
  JVMWrapper("JVM_GetHostName");
  return os::get_host_name(name, namelen);
JVM_END

根据实际操作系统,os::get_host_name将转换为不同的函数。对于linux,它将调用gethostname

inline int os::get_host_name(char* name, int namelen) {
  return ::gethostname(name, namelen);
}

如果调用gethostname成功,则使用gethostname返回的主机名调用getaddrinfo。如果此调用成功,将使用getaddrinfo返回的地址调用getnameinfo以获取最终主机名。
在某种程度上,这似乎对我来说有些奇怪,我感觉我漏掉了什么,但这些差异很可能是您遇到的不同行为的原因;可以使用提供的本地代码并调试系统获得的结果来测试假设。

1
这是来自Oracle文档的答案,可能会对您有所帮助: 在Red Hat Linux安装中,InetAddress.getLocalHost()可能会返回一个与环回地址(127.0.0.1)对应的InetAddress。这是因为默认安装在/etc/hosts中创建了主机名与环回地址之间的关联。为了确保InetAddress.getLocalHost()返回实际的主机地址,请更新/etc/hosts文件或名称服务配置文件(/etc/nsswitch.conf),以在搜索主机之前查询dns或nis。 链接: https://docs.oracle.com/javase/7/docs/technotes/guides/idl/jidlFAQ.html JDK 1.7上类似的错误 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=7166687

0

在JDK 11上,有可能本地主机名(localhostname)会有一个内置的预定义JDK关键字,当检索本地主机名时可以调用它。您可能正在使用自己的变量调用覆盖系统预定义的关键字,在这种情况下,您正在调用本地主机名。因为有时我们会意外地用自己定义的变量覆盖内置变量,导致原始内置关键字失去其值,从而显示为空或其他结果。

这可能不是您问题的最佳答案,但我建议您查看JDK内置关键字和RHEL Linux内置关键字,以获取返回结果中的本地主机名的Inet调用。


1
请问您能否提供一些有关如何检查内置关键字的详细信息?谢谢! - sanastasiadis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接