深入InetAddress源码

InetAddress这个类是Java网络编程的一个核心类。之前有粗略地读过这个类的源码,不过那时我的网络知识非常差,看得云里雾里。当遇到一些网络Exception的时候,还是无从下手。我最近一直在看计算机网络的书,对于TCP/IP逐渐有了一些新的认识,所以重新回来尝试深入地去研读一下它的源码,了解它的实现和方方面面。目的是以后再遇到相关问题能快速地定位原因找到解决方法。

InetAddress类在java.net包里。正如它的Java Doc第一句所说的:"This Class represents an Internet Protocol (IP) address.", 这个类代表了一个IP地址。关于IP地址,随便一本计算机网络的书都能找到详尽的介绍,自不赘言。

InetAddress有以下三个类变量:

static final int IPv4 = 1; static final int IPv6 = 2; static transient boolean preferIPv6Address = false;

前面两个分别指所谓的address family, IPv4或则IPv6; preferIPv6Address是指该JVM是否指定了优先使用IPv6地址, 它由一段静态代码段从JVM的参数"java.net.preferIPv6Addresses"中取得:

static{ preferIPv6Address = java.security.AccessController.doPrivileged(new GetBooleanAction("java.net.preferIPv6Addresses")).booleanValue(); AccessController.doPrivileged(new LoadLibraryAction("net")); init(); }

从上面的静态代码段,我们看到还一个加载net这个包,然后init。其中init是一个本地方法(native method), 它主要perform class load-time initialization.

InetAddress有一个静态内部类, InetAddressHolder, 从它的名字Holder我们就可以推断它是一个应该是持有了这个InetAddress类的一些关键的信息,我们看一下它的构造方法,果不其然:

InetAddressHolder(String hostName, int address, int family) { this.hostName = hostName;<br> this.address = address;<br> this.family = family;<br> }

在读InetAddress其他方法之前,我们先读一下它另外一个静态代码段(因为静态代码段会在JVM加载类的时候会执行,所以它对这个类的表现非常重要):

static``{ //创建impl, impl是InetAddress的一个静态变量,它的类型是InetAddressImpl <br> impl = InetAddressImplFactory.create(); String provider = null;<br> String propPrefix = "sun.net.spi.nameservice.provider.";<br> int n = 1;<br> nameServices = new ArrayList<NameService>();<br>

provider = AccessController.doPrivileged(new GetPropertyAction(propPrefix + n));<br>

//尝试去加载SPI中的NameService Provider,如果你有自己实现了NameService,或者用了第三方的NameService,就会被加载进来。 while(provider != null){<br> NameService ns = createNSProvider(provider);<br> if(ns != null){<br> nameServices.add(ns);<br> }<br> n++;<br> provider = AccessController.doPrivileged(new GetPropertyAction(proPrefix + n));<br> }<br> if(nameService.size() == 0){<br> ·//创建default的NameService Provider· NameService ns = createNSProvider("default"); nameService.add(ns);<br> }<br> }

我们再进去InetAddressImplFactory.create()这个方法里面去看如何创建一个InetAddressImpl:

static InetAddressImpl create(){ return InetAddress.loadImpl(isIPv6Supported()?"Inet6AddressImpl":"Inet4AddressImpl"); }

我们可以看到它是先判断系统是不是支持IPv6,如果支持,它就会去加载一个Inet6AddressImpl,否则就会加载Inet4AddressImpl.

isIPv6Supported()这个方法是InetAddress的内部类InetAddressImplFactory的本地方法(native method):

static native boolean isIPv6Supported();

我们再看InetAddress.loadImpl是怎么实现的:

static InetAddressImpl loadImpl(String implName){ Object impl = null;

String prefix = AccessController.doPrivileged(new GetPropertyAction("impl.prefix", ""));

impl = Class.forName("java.net." + prefix + implName)..newInstance(); //这里我省略了一些代码 }

我们可以看到它是通过反射来拿到了这个InetAddressImpl的实例的。

接下来我们可以从InetAddress最常用的一个静态方法getAllByName(String host)开始。

public static InetAddress[] getAllByName(String host) throws UnknownHostException { return getAllByName(host, null);<br> }

private static InetAddress[] getAllByName(String host, InetAddress reqAddr) throws UnknownHostException{
//如果传进来的host为null或者空字符串,就会返回loopback Address if(host == null || host.length() == 0){ InetAddress[] ret = new InetAddress[1]; ret[0] = impl.loopbackAddress(); return ret; } .... //省略了IPv6和传进来host是一个IP Address的情况 return getAllByName(host, reqAddr, true); }

private InetAddress[] getAllByName0(String host, InetAddress reqAddr, boolean check) throws UnknownHostException{ if(check){ SecurityManager security = System.getSecurityManager(); if(security != null){ security.checkConnect(host, -1); } }

//先从cache里面查找 InetAddress[] addresses = getCachedAddresses(host);

//如果cache里面没有,就去NameService查找 if(addresses == null){ address = getAddressesFromNameService(host, reqAddr); }

if(addresses == unknown_array){ throw new UnknownHostException(host); } return addresses.clone() }

到此,我们已经完成了根据host来查找IP地址的整个过程,但值得注意的是上面两个方法: getCachedAddress(host)和getAddressFromNameService(host,reqAddr)。我们逐个进去看一下实现。

private static InetAddress[] getCachedAddresses(String hostname){ hostname = hostname.toLowerCase(); synchronized(addressCache){ //init cache, 把anyLocalAddress(0.0.0.0)放进cache里面 cacheInitIfNeeded(); CacheEntry entry = addressCache.get(hostname); if(entry == null){ entry = negativeCache.get(hostname); } if(entry != null){ return entry.addresses; } } return null; }

上面的有两种cache,positive cache和negative cache。 Positive Cache指的是host解析成功的,negative cache值解析失败的(其实就是指解析成了0.0.0.0)。

再来看getAddressesFromNameService方法:

private static InetAddress[] getAddressesFromNameService(String host, InetAddress reqAddr) throws UnknownHostException{ InetAddress[] addresses = null; boolean success = false; UnknownHostException ex = null;

//先检查host是否在lookupTable中, //1)调用checkLookupTable,如果host不在lookupTable中,host将会被加入lookupTable中,并返回null,直接去nameservice查找host //2) 如果host在lookupTable里,当前线程就会被block住,直到host被移出lookupTable。然后当前线程就会尝试去查找addressCache。1.如果在addressCache中找到了该host的地址,就会将该地址返回,2.如果addressCache中没有找到该host的地址,就会将该host放进lookupTable中并返回null,然后继续去nameservice中查找! if((addresses = checkLookupTable(host)) == null){ for(NameService nameService : nameServices){ try{ addresses = nameService.lookupAllHostAddr(host); success = true; break; } catch(UnknownHostException uhe){ if(host.equalsIgnoreCase("localhost")){ InetAddress[] local = new InetAddress[]{impl.loopbackAddress()}; addresses = local; success = true; break; } else { addresses = unknown_array; success = false; ex = uhe; } } }

..... //省略一部分代码

//如果success,则加入addressesCache中(positive),否则加入negativeCache中
cacheAddresses(host, addresses, success);
} finally { updateLookupTable(host); } }

最后值得看的一个方法是nameservice.lookupAllHostAddr(host), 因为我们并没有通过SPI配置了其他的NameService Provider,那这里其实是用了default的NameService:

if (provider.equals("default")) { // initialize the default name service nameService = new NameService() { public InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException { return impl.lookupAllHostAddr(host); } public String getHostByAddr(byte[] addr) throws UnknownHostException { return impl.getHostByAddr(addr); } };

从上面代码看到nameservice其实是调用了InetAddressImpl的实现类, Inet4Address或者Inet6Address! 以Inet4Address为例,它的lookupAllHostAddr(host)方法:

public native InetAddress[] lookupAllHostAddr(String hostname) throws UnknownHostException;

我们可以看到,这是一个native方法,具体实现我没有去看实现代码(应该是C++?),根据我的推断,视系统而定,windows它应该就是先去C://windows/system32/driver/etc/hosts查找,如果找不到就会去配置的DNS Server查找, Linux它先去/etc/hosts查找,找不到就会去/etc/resolv.conf文件配置的DNS Server去查找!

Written on 23 September 2016