从JDK源码看Java域名解析

Posted 远洋号

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从JDK源码看Java域名解析相关的知识,希望对你有一定的参考价值。

前言

对于 JDK 主要使用了两种映射解析方案,一种是 hosts 文件机制,另外一种是操作系统自带的解析方案。

相关类

--java.lang.Object
 --java.net.InetAddress$HostsFileNameService
 --java.net.InetAddress$PlatformNameService

JDK选择的方案

以上两种主机名称 IP 映射机制,JDK 是怎样选择的呢?其实就是根据 jdk.net.hosts.file系统属性来确定的,默认情况下使用基于操作系统的 PlatformNameService 方案,而如果配置了jdk.net.hosts.file系统属性则使用基于 hosts 文件的 HostsFileNameService 方案,比如可以在启动时配置参数 -Djdk.net.hosts.file=/etc/hosts。对应逻辑代码如下:

    private static NameService createNameService() {
       String hostsFileName =
               GetPropertyAction.privilegedGetProperty("jdk.net.hosts.file");
       NameService theNameService;
       if (hostsFileName != null) {
           theNameService = new HostsFileNameService(hostsFileName);
       } else {
           theNameService = new PlatformNameService();
       }
       return theNameService;
   }

接口定义

private interface NameService {
InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException;
String getHostByAddr(byte[] addr) throws UnknownHostException;
}

HostsFileNameService 类

类定义如下:

private static final class HostsFileNameService implements NameService

该类即是对基于 hosts 文件方案的封装,主要看看核心的两个方法,

lookupAllHostAddr方法

  • 根据指定的 hosts 文件路径扫描每一行,如果不存在文件则抛出 FileNotFoundException 异常。

  • 遍历每行内容,如果以 # 号开头则表示该行为注释内容,直接忽略,否则继续。

  • 标准情况下内容可以为 127.0.0.1 localhost #local,# 号后面为注释内容,所以调用 removeComments 方法去掉 #local,该方法不再贴出。

  • 处理后的内容为127.0.0.1字符串,需要调用 createAddressByteArray 将其转换为 byte 数组以方便得到 InetAddress 对象,该方法不再贴出。

  • 将得到的 添加到 ArrayList

    对象中,最终转换为 InetAddress 数组并返回。
  public InetAddress[] lookupAllHostAddr(String host)
         throws UnknownHostException
{
     String hostEntry;
     String addrStr = null;
     InetAddress[] res = null;
     byte addr[] = new byte[4];
     ArrayList<InetAddress> inetAddresses = null;
     try (Scanner hostsFileScanner = new Scanner(new File(hostsFile), "UTF-8")) {
         while (hostsFileScanner.hasNextLine()) {
             hostEntry = hostsFileScanner.nextLine();
             if (!hostEntry.startsWith("#")) {
                 hostEntry = removeComments(hostEntry);
                 if (hostEntry.contains(host)) {
                     addrStr = extractHostAddr(hostEntry, host);
                     if ((addrStr != null) && (!addrStr.equals(""))) {
                         addr = createAddressByteArray(addrStr);
                         if (inetAddresses == null) {
                             inetAddresses = new ArrayList<>(1);
                         }
                         if (addr != null) {
                             inetAddresses.add(InetAddress.getByAddress(host, addr));
                         }
                     }
                 }
             }
         }
     } catch (FileNotFoundException e) {
         throw new UnknownHostException("Unable to resolve host " + host
                 + " as hosts file " + hostsFile + " not found ");
     }
     if (inetAddresses != null) {
         res = inetAddresses.toArray(new InetAddress[inetAddresses.size()]);
     } else {
         throw new UnknownHostException("Unable to resolve host " + host
                 + " in hosts file " + hostsFile);
     }
     return res;
 }

getHostByAddr方法

  • 根据指定的 hosts 文件路径扫描每一行,如果不存在文件则抛出 FileNotFoundException 异常。

  • 遍历每行内容,如果以 # 号开头则表示该行为注释内容,直接忽略,否则继续。

  • 标准情况下内容可以为 127.0.0.1 localhost #local,# 号后面为注释内容,所以调用 removeComments 方法去掉 #local,该方法不再贴出。

  • 一旦找到主机名称后则不再往下遍历,跳出循环并返回主机名称。

public String getHostByAddr(byte[] addr) throws UnknownHostException {
           String hostEntry;
           String host = null;
           String addrString = addrToString(addr);
           try (Scanner hostsFileScanner = new Scanner(new File(hostsFile), "UTF-8")) {
               while (hostsFileScanner.hasNextLine()) {
                   hostEntry = hostsFileScanner.nextLine();
                   if (!hostEntry.startsWith("#")) {
                       hostEntry = removeComments(hostEntry);
                       if (hostEntry.contains(addrString)) {
                           host = extractHost(hostEntry, addrString);
                           if (host != null) {
                               break;
                           }
                       }
                   }
               }
           } catch (FileNotFoundException e) {
               throw new UnknownHostException("Unable to resolve address "
                       + addrString + " as hosts file " + hostsFile
                       + " not found ");
           }
           if ((host == null) || (host.equals("")) || (host.equals(" "))) {
               throw new UnknownHostException("Requested address "
                       + addrString
                       + " resolves to an invalid entry in hosts file "
                       + hostsFile);
           }
           return host;
       }

PlatformNameService类

类定义如下:

private static final class PlatformNameService implements NameService

该类即是对操作系统自带的解析方案的封装,核心的两个方法如下,因为这两个方法与操作系统相关,所以通过它们通过 InetAddressImpl 接口调用了对应的本地方法,本地方法分别为 lookupAllHostAddr 和 getHostByAddr。

public InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException{
   return impl.lookupAllHostAddr(host);
}
public String getHostByAddr(byte[] addr) throws UnknownHostException{
   return impl.getHostByAddr(addr);
}

lookupAllHostAddr方法

结构 参数

typedef struct addrinfo {

int ai_flags;

int ai_family;

int ai_socktype;

int ai_protocol;

size_t ai_addrlen;

char* ai_canonname;

struct sockaddr* ai_addr;

struct addrinfo* ai_next;

}

ai_addrlen must be zero or a null pointer

ai_canonname must be zero or a null pointer

ai_addr must be zero or a null pointer

ai_next must be zero or a null pointer

ai_flags:AI_PASSIVE,AI_CANONNAME,AI_NUMERICHOST

ai_family: AF_INET,AF_INET6

ai_socktype:SOCK_STREAM,SOCK_DGRAM

ai_protocol:IPPROTO_IP, IPPROTO_IPV4, IPPROTO_IPV6 etc.

getHostByAddr方法

Windows 和 unix-like 操作系统实现的代码都差不多,这里只贴出 Windows的,基本的逻辑为:先通过 JNI 的 GetByteArrayRegion 函数获取传入的4个字节,这里因为字节可能是负数,所以需要进行移位操作;然后通过 getnameinfo 函数获取主机名;最后通过 JNI 的 NewStringUTF 函数将主机名放到新建的字符串对象中。

JNIEXPORT jstring JNICALL
Java_java_net_Inet4AddressImpl_getHostByAddr(JNIEnv *env, jobject this,
                                            jbyteArray addrArray)
{
   jstring ret = NULL;
   char host[NI_MAXHOST + 1];
   jbyte caddr[4];
   jint addr;
   struct sockaddr_in sa;
   memset((char *)&sa, 0, sizeof(struct sockaddr_in));
   (*env)->GetByteArrayRegion(env, addrArray, 0, 4, caddr);
   addr = ((caddr[0] << 24) & 0xff000000);
   addr |= ((caddr[1] << 16) & 0xff0000);
   addr |= ((caddr[2] << 8) & 0xff00);
   addr |= (caddr[3] & 0xff);
   sa.sin_addr.s_addr = htonl(addr);
   sa.sin_family = AF_INET;
   if (getnameinfo((struct sockaddr *)&sa, sizeof(struct sockaddr_in),
                   host, NI_MAXHOST, NULL, 0, NI_NAMEREQD)) {
       JNU_ThrowByName(env, "java/net/UnknownHostException", NULL);
   } else {
       ret = (*env)->NewStringUTF(env, host);
       if (ret == NULL) {
           JNU_ThrowByName(env, "java/net/UnknownHostException", NULL);
       }
   }
   return ret;
}



-------------推荐阅读------------







------------------广告时间----------------

鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有需要的朋友可以购买。感谢各位朋友。


以上是关于从JDK源码看Java域名解析的主要内容,如果未能解决你的问题,请参考以下文章

拼多多探究Java并发底层原理JDK源码解析大揭秘,由浅入深看源码

JDK:java.lang.Integer源码解析

ArrayList源码解析

如何进行 Java 代码阅读分析?

Arrays.sort源码解析

Arrays.sort源码解析