前言

在互联网中通信需要借助 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 类

类定义如下:
[Java] 纯文本查看 复制代码
1
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 数组并返回。
[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方法

该方法根据 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] 纯文本查看 复制代码
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
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);
            }
        }
更多技术资讯可关注:itheimaGZ获取

JDK源码看Java域名解析的更多相关文章

  1. 结合JDK源码看设计模式——桥接模式

    前言: 在我们还没学习框架之前,肯定都学过JDBC.百度百科对JDBC是这样介绍的[JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Jav ...

  2. 结合JDK源码看设计模式——原型模式

    定义: 指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.不需要知道任何创建的细节,不调用构造函数适用场景: 类初始化的时候消耗较多资源 new产生的对象需要非常繁琐的过程 构造函数比较 ...

  3. 结合JDK源码看设计模式——单例模式

    定义: 保证一个类仅有一个实例,并提供一个全局访问点 适用场景: 确保任何情况下这个对象只有一个实例 详解: 私有构造器 单利模式中的线程安全+延时加载 序列化和反序列化安全, 防止反射攻击 结合JD ...

  4. 结合JDK源码看设计模式——简单工厂、工厂方法、抽象工厂

    三种工厂模式的详解: 简单工厂模式: 适用场景:工厂类负责创建的对象较少,客户端只关心传入工厂类的参数,对于如何创建对象的逻辑不关心 缺点:如果要新加产品,就需要修改工厂类的判断逻辑,违背软件设计中的 ...

  5. JDK源码看ArrayList和Vector的一些区别

    最近在看JDK源码,从源码的角度记录一下ArrayList和Vector的一些区别 1.new a.不指定长度 Vector默认创建10个元素的数组 public Vector() { this(10 ...

  6. 从源码看Java集合之ArrayList

    Java集合之ArrayList - 吃透增删查改 从源码看初始化以及增删查改,学习ArrayList. 先来看下ArrayList定义的几个属性: private static final int ...

  7. 结合JDK源码看设计模式——模板方法模式

    前言: 相信很多人都听过一个问题:把大象关进冰箱门,需要几步? 第一,把冰箱门打开:第二,把大象放进去:第三,把冰箱门关上.我们可以看见,这个问题的答案回答的很有步骤.接下来我们介绍一种设计模式--模 ...

  8. 结合JDK源码看设计模式——适配器模式

    定义: 将一个类的接口转换成客户期望的另外一个接口(重点理解适配的这两个字),使得接口不兼容的类可以一起工作适用场景: 已经存在的类,它的方法和需求不匹配的时候 在软件维护阶段考虑的设计模式 详解 首 ...

  9. 从源码看java中Integer的缓存问题

    在开始详细的说明问题之前,我们先看一段代码 public static void compare1(){ Integer i1 = 127, i2 = 127, i3 = 128, i4 = 128; ...

随机推荐

  1. matlab中画一条折线时怎样显示出每个点折点的数值

    举个例子: num=[5,44,62,154,264,365,398,480,619,705,762,728,669,726,890,731,707,696,558,509,444];date=[1. ...

  2. Go mod graphql-go 的 Replace

    现在在项目中大量的使用 graphql,但用的版本是3年前的版本. 3年前包的url:github.com/neelance/graphql-go 现在的url:github.com/graph-go ...

  3. 思考题:clock类编写

    题目 为便于后文理解,这里先补上这份代码前文开的库以及宏定义: #include<cstdio> #include<iostream> #include<string&g ...

  4. Maven--Maven 安装最佳实践

    1.设置 MAVEN_OPTS 环境变量 运行 mvn 命令实际上是执行了 Java 命令,既然是运行 Java,那么运行 Java 命令可用的参数当然也应该在运行 mvn 命令时使用.这个时候,MA ...

  5. 小白学习之pytorch框架(5)-多层感知机(MLP)-(tensor、variable、计算图、ReLU()、sigmoid()、tanh())

    先记录一下一开始学习torch时未曾记录(也未好好弄懂哈)导致又忘记了的tensor.variable.计算图 计算图 计算图直白的来说,就是数学公式(也叫模型)用图表示,这个图即计算图.借用 htt ...

  6. 12. docker 网络 docker network (docker 网络)

    1. 环境准备 编写 Vagrantfile 为 # -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.require_version "> ...

  7. [WC2010]重建计划(长链剖分+线段树+分数规划)

    看到平均值一眼分数规划,二分答案mid,边权变为w[i]-mid,看是否有长度在[L,R]的正权路径.设f[i][j]表示以i为根向下j步最长路径,用长链剖分可以优化到O(1),查询答案线段树即可,复 ...

  8. Flume(三) —— 断点续传 与 事务

    断点续传 # Name the components on this agent a1.sources = r1 a1.sinks = k1 a1.channels = c1 # Describe / ...

  9. 物联网应用层协议选择和分析--MQTT、CoAP 、HTTP、XMPP、SoAP

    MQTT协议 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)最早是IBM开发的一个即时通讯协议,MQTT协议是为大量计算能力有限且工作在低带宽.不 ...

  10. pom配置项目build jar 包含源码的配置

    <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> ...