以下以JDK8为例说明对IPv4和IPv6是如何处理的。
一、常用代码
一般情况下,使用如下代码可以获取到域名/主机名对应的多个IP,其中部分是IPv4的,部分是IPv6的:
try {
InetAddress[] addrs = InetAddress.getAllByName(host);
for (InetAddress addr : addrs) {
System.out.println(addr);
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
TEST-NOTEBOOK/172.16.109.99
TEST-NOTEBOOK/25.38.108.104
TEST-NOTEBOOK/fe80:0:0:0:cc19:d6ad:fa78:db29%14
TEST-NOTEBOOK/fe80:0:0:0:81e:a44d:98e5:62cf%8
如果添加了JVM参数-Djava.net.preferIPv4Stack=true,则执行结果为:
TEST-NOTEBOOK/172.16.109.99
TEST-NOTEBOOK/25.38.108.104
设置java.net.preferIPv4Stack=false与不设置java.net.preferIPv4Stack效果一样
二、执行过程分析
下面一起来分析一下整个执行过程:
1.InetAddress.getAllByName(..)内部实际调用的是红框中的方法
2.上述方法中实际获取IP的方法如下:
3.上述方法实际调用的方法定义如下:
4.impl是在类的static方法中初始化的:
5.InetAddressImplFactory的代码如下
可以看出impl最终采用Inet6AddressImpl还是Inet4AddressImpl,实际是根据native方法isIPv6Supported()决定的。另外Inet6AddressImpl支持IPv6和IPv4双栈,Inet4AddressImpl只支持IPv栈。
6.从JDK源码InetAddressImplFactory.c中可以找到实际调用的代码:
从中可看到实际是调用方法:ipv6_available()
7.从JDK源码net_util.c中可以找到ipv6_available()的定义:
从中可以看到ipv6_available()的返回值是由常量IPv6_available决定的,常量IPv6_available的值 是由IPv6_supported() 和 preferIPv4Stack决定的,
若配置JVM参数-Djava.net.preferIPv4Stack=false,则IPv6_available=0
若JDK所在系统不支持IPv6,则IPv6_available=0
8.判断系统是否支持IPv6的代码在net_util_md.c中
可以看到是通过尝试创建IPv6的socket来判断系统是否支持IPv6的
三、其它问题说明
1.JVM参数java.net.preferIPv6Addresses的用途
java.net.preferIPv6Addresses也是在java.net.InetAddress中定义的:
会在java.net.Inet6AddressImpl中使用到:
主要是用来生成IPv6格式的通配符地址和回路地址
2.域名缓存时长配置方式
(1)JDK中提供机制对找得到IP地址的域名和找不到IP地址的域名分别缓存,代码在java.net.Inet6AddressImpl中:
(2)这两种缓存的默认过期时长不同,代码在sun.net.InetAddressCachePolicy中:
a、找得到IP地址的域名的缓存:30s
b、找不到IP地址的域名的缓存:10s
可以通过如下代码分别获取到过期时长:
sun.net.InetAddressCachePolicy.get();
sun.net.InetAddressCachePolicy.getNegative();
可以通过如下JVM参数进行修改
a、找得到IP地址的域名的缓存
networkaddress.cache.ttl
sun.net.inetaddr.ttl
b、找不到IP地址的域名的缓存
networkaddress.cache.negative.ttl
sun.net.inetaddr.negative.ttl
参考源码:
java.net.InetAddress
java.net.Inet6Address
java.net.Inet4Address
sun.net.InetAddressCachePolicy
net_util.c
net_util_md.c
InetAddressImplFactory.c
参考文档