从JDK源码看Java域名解析

前言

在互联网中通讯须要借助 IP 地址来定位到主机,而 IP 地址由不少数字组成,对于人类来讲记住某些组合数字很困难,因而,为了方便你们记住某地址而引入主机名和域名。java

早期的网络中的机器数量不多,能很方便地经过 hosts 文件来完成主机名称和 IP 地址的映射,这种方式须要用户本身维护网络上全部主机的映射关系。后来互联网迅猛发展起来,hosts 文件方式已经没法胜任,因而引入域名系统(DNS)来解决主机名称和 IP 地址的映射。数组

局域网中经常使用来表示 IP 地址的名称更多称为主机名,而互联网上用来表示 IP 地址的名称更多称为域名。核心内容都相同,都是解决名称和 IP 地址间的映射。bash

Java 中提供了不少互联网主机名称和地址操做相关的接口,如今来看看 JDK 内部对域名解析相关功能的实现。其实,InetAddress 类内部存在一个 NameService 内部接口用于实现域名及IP的映射。服务器

对于 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;

}
复制代码

NameService 接口主要定义了两个方法,用于获取主机名称对应的 IP 地址和 IP 地址对应的主机名称。并发

HostsFileNameService 类

类定义以下:机器学习

private static final class HostsFileNameService implements NameService
复制代码

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

lookupAllHostAddr方法

该方法根据主机名称实现基于 hosts 文件的 IP 地址查找方案。它要完成的逻辑以下:函数

  • 根据指定的 hosts 文件路径扫描每一行,若是不存在文件则抛出 FileNotFoundException 异常。
  • 遍历每行内容,若是以 # 号开头则表示该行为注释内容,直接忽略,不然继续。
  • 标准状况下内容能够为 127.0.0.1 localhost #local,# 号后面为注释内容,因此调用 removeComments 方法去掉 #local,该方法再也不贴出。
  • 处理后的内容为127.0.0.1 localhost,接着看是否包含了传进来的主机名称有的话则说明是该主机名称映射的 IP 地址,经过 extractHostAddr 方法提取IP地址,值为 127.0.0.1,该方法再也不贴出。
  • 处理后的内容为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方法

该方法根据 IP 地址实现基于 hosts 文件的主机名称查找方案。它要完成的逻辑以下:

  • 传入的参数为 IP 地址的字节数组,好比new byte[] {127, 0, 0, 1},先调用 addrToString 方法将其转换为"127.0.0.1"字符串,该方法再也不贴出。
  • 根据指定的 hosts 文件路径扫描每一行,若是不存在文件则抛出 FileNotFoundException 异常。
  • 遍历每行内容,若是以 # 号开头则表示该行为注释内容,直接忽略,不然继续。
  • 标准状况下内容能够为 127.0.0.1 localhost #local,# 号后面为注释内容,因此调用 removeComments 方法去掉 #local,该方法再也不贴出。
  • 处理后的内容为127.0.0.1 localhost,接着看是否包含了传进来的 IP 地址,有的话则说明是该 IP 地址对应的主机名称,经过 extractHost 方法提取主机名称localhost,该方法再也不贴出。
  • 一旦找到主机名称后则再也不往下遍历,跳出循环并返回主机名称。
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方法

该本地方法中要完成的工做主要就是先经过操做系统提供的主机名称服务接口来获取对应的 IP 地址,而后再生成 InetAddress 对象数组,即要生成 Java 层的数据结构。

Windows 和 unix-like 操做系统实现的代码都比较长,这里再也不贴出,核心就是经过 getaddrinfo 函数来实现名称解析,获取到主机名对应的全部地址。而后经过 JNI 的 NewObjectArray 函数建立对象数组,接着再经过 JNI 的 NewObject函数建立 InetAddress 对象并设置地址和主机名称的属性值,最后经过 JNI 的 SetObjectArrayElement 函数逐一将 InetAddress 对象放入数组中。

getaddrinfo 函数用于名称解析,可将域名转成对应的 IP 地址和端口。它查找时可能会去 DNS 服务器上查找指定域名对应的地址,也可能会在本地的 hosts 文件,也可能在其余的命名服务。并且通常每一个域名都会对应多个 IP 地址。经过该函数获取到的结果为 addrinfo 结构体指针。

结构 参数

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方法

该本地方法用于根据 IP 地址获取主机名,传入的参数为 byte[],返回为字符串。它要完成的工做就是经过操做系统提供的主机名称服务接口获取主机名,而后返回字符串。

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;
}
复制代码

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

个人2017文章汇总——机器学习篇

个人2017文章汇总——Java及中间件

个人2017文章汇总——深度学习篇

个人2017文章汇总——JDK源码篇

个人2017文章汇总——天然语言处理篇

个人2017文章汇总——Java并发篇

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

公众号的菜单已分为“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。

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

为何写《Tomcat内核设计剖析》

欢迎关注:

这里写图片描述
相关文章
相关标签/搜索