JDK源码看Java域名解析

前言java

在互联网中通讯须要借助 IP 地址来定位到主机,而 IP 地址由不少数字组成,对于人类来讲记住某些组合数字很困难,因而,为了方便你们记住某地址而引入主机名和域名。
早期的网络中的机器数量不多,能很方便地经过 hosts 文件来完成主机名称和 IP 地址的映射,这种方式须要用户本身维护网络上全部主机的映射关系。后来互联网迅猛发展起来,hosts 文件方式已经没法胜任,因而引入域名系统(DNS)来解决主机名称和 IP 地址的映射。
局域网中经常使用来表示 IP 地址的名称更多称为主机名,而互联网上用来表示 IP 地址的名称更多称为域名。核心内容都相同,都是解决名称和 IP 地址间的映射。
Java 中提供了不少互联网主机名称和地址操做相关的接口,如今来看看 JDK 内部对域名解析相关功能的实现。其实,InetAddress 类内部存在一个 NameService 内部接口用于实现域名及IP的映射。
对于 JDK 主要使用了两种映射解析方案,一种是 hosts 文件机制,另一种是操做系统自带的解析方案。
相关类数组

[Java] 纯文本查看 复制代码
?
1
2
3
--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。对应逻辑代码以下:
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
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;
        }
接口定义数据结构

[Java] 纯文本查看 复制代码
?
1
2
3
4
5
6
7
private interface NameService {
 
    InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException;
 
    String getHostByAddr(byte[] addr) throws UnknownHostException;
 
    }
 函数

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

类定义以下:
[Java] 纯文本查看 复制代码
?
1
private static final class HostsFileNameService implements NameService
该类便是对基于 hosts 文件方案的封装,主要看看核心的两个方法,
lookupAllHostAddr方法操作系统

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

  • 根据指定的 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 数组并返回。

[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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方法unix

该方法根据 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,该方法再也不贴出。
  • 一旦找到主机名称后则再也不往下遍历,跳出循环并返回主机名称。

[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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类
类定义以下:

[Java] 纯文本查看 复制代码
?
1
private static final class PlatformNameService implements NameService

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

[Java] 纯文本查看 复制代码
?
1
2
3
4
5
6
7
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 函数将主机名放到新建的字符串对象中。[Java] 纯文本查看 复制代码?010203040506070809101112131415161718192021222324252627282930JNIEXPORT 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;    }

相关文章
相关标签/搜索