JDK源码看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 类

类定义如下:

[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获取

原文地址:https://www.cnblogs.com/zhuxiaopijingjing/p/12271918.html

时间: 2024-10-07 05:36:07

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

JDK源码笔记-java.util.HashMap

HashMap 的存储实现 当程序试图将多个 key-value 放入 HashMap 中时,以如下代码片段为例: Java代码 HashMap<String , Double> map = new HashMap<String , Double>(); map.put("语文" , 80.0); map.put("数学" , 89.0); map.put("英语" , 78.2); HashMap 采用一种所谓的&quo

从源码看Java集合之ArrayList

Java集合之ArrayList - 吃透增删查改 从源码看初始化以及增删查改,学习ArrayList. 先来看下ArrayList定义的几个属性: private static final int DEFAULT_CAPACITY = 10; private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; tra

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

三种工厂模式的详解: 简单工厂模式: 适用场景:工厂类负责创建的对象较少,客户端只关心传入工厂类的参数,对于如何创建对象的逻辑不关心 缺点:如果要新加产品,就需要修改工厂类的判断逻辑,违背软件设计中的开闭原则,且产品类多的话,就会使得简单工厂类比较复杂 在jdk源码中的具体实例(注意看代码中的中文注释) private static Calendar createCalendar(TimeZone zone,Locale aLocale) { CalendarProvider provider

深入源码看java类加载器ClassLoader

ClassLoader类加载器是负责加载类的对象.ClassLoader 类是一个抽象类.如果给定类的二进制名称(即为包名加类名的全称),那么类加载器会试图查找或生成构成类定义的数据.一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的"类文件".java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例.除此之外,ClassLoad

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

定义: 将一个类的接口转换成客户期望的另外一个接口(重点理解适配的这两个字),使得接口不兼容的类可以一起工作适用场景: 已经存在的类,它的方法和需求不匹配的时候 在软件维护阶段考虑的设计模式 详解 首先来从生活中的常见场景来看,一个电源插座输出都是220V,而我们一些电子设备,比如手机,MP3,MP4,所需要的电压不一样,也不可能直接就是220接上,这就需要一个中间的转换器,每个厂家不同,对应的充电线也有可能不同.这个不同的充电线就可以理解为一个适配器.而220V的输出电压可以看做是我们做好的一

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

前言: 相信很多人都听过一个问题:把大象关进冰箱门,需要几步? 第一,把冰箱门打开:第二,把大象放进去:第三,把冰箱门关上.我们可以看见,这个问题的答案回答的很有步骤.接下来我们介绍一种设计模式--模板方法模式,你会发现,它与这个问题的答案实际上有很多共同之处. 一.定义 定义一个算法骨架,允许子类为一个或多个步骤提供实现.模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤. 二.适用场景 一次性实现一个算法的不变的部分,将可变的行为留给子类实现 也就是将各子类中公共行为被提取

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

在开始详细的说明问题之前,我们先看一段代码 1 public static void compare1(){ 2 Integer i1 = 127, i2 = 127, i3 = 128, i4 = 128; 3 System.out.println(i1 == i2); 4 System.out.println(i1.equals(i2)); 5 System.out.println(i3 == i4); 6 System.out.println(i3.equals(i4)); 7 } 这段代

JDK 源码阅读 —— Java 内存模型

零. 为什么需要 Java 内存模型 为了让程序员忽略掉各种硬件和操作系统的内存访问差异, 也既无需关心不同架构上内存模型的差异, Java 在代码和硬件内存模型间又提供了一个 Java 内存模型. 一. 并发模型的分类 在并发编程中,需要处理两个关键问题:线程之间如何通信(线程之间以何种机制来交换信息, 有两种方式:共享内存和消息传递)及线程之间如何同步. 在共享内存的并发模型里(如 Java),线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态(主存)来隐式(对程序员透明)进行通

结合JDK源码看设计模式——装饰者模式

定义 在不改变原有对象的基础之上,将功能附加到对象上 适用场景 扩展一个类的功能 动态的给对象增加功能,当功能不需要的时候能够动态删除 详解 在看到定义的时候,可能很多人会想,这不就是继承吗?的确很像,不过是比继承更加有弹性的替代方案.就像原型模式和new之间的关系一样,有区别,但是区别又不是特别大.装饰者一个很重要的词就是动态,他可以灵活的选择要这个功能还是不要.在装饰者中要有四个角色:抽象的实体类,具体的实体类,抽象的装饰者,具体的装饰者.下面画一个大致的UML图 实体类创建之后,如果想扩展