1. getByName
public static InetAddress getByName(String host) throws UnknownHostException { return InetAddress.getAllByName(host)[0]; } public static InetAddress[] getAllByName(String host) throws UnknownHostException { return getAllByName(host, null); } private static InetAddress[] getAllByName(String host, InetAddress reqAddr) throws UnknownHostException { // 1. host == null || host.length() == 0 // loopbackAddress(與系統屬性java.net.preferIPv6Addresses相關):Inet4Address(localhost/127.0.0.1) || Inet6Address(localhost/::1) // 2. host表示一個IP地址 // Inet4Address(null/IPAddressUtil.textToNumericFormatV4(host)) || Inet6Address(null/IPAddressUtil.textToNumericFormatV6(host)) ... ... return getAllByName0(host, reqAddr, true); } private static InetAddress[] getAllByName0 (String host, InetAddress reqAddr, boolean check) throws UnknownHostException { ... ... InetAddress[] addresses = getCachedAddresses(host); // 在緩存中查找記錄 if (addresses == null) { addresses = getAddressesFromNameService(host, reqAddr); // 請求域名解析 } if (addresses == unknown_array) // 緩存中存在失敗記錄 || 域名解析失敗 throw new UnknownHostException(host); return addresses.clone(); }
1)InetAddress.Cache
static final class CacheEntry { CacheEntry(InetAddress[] addresses, long expiration) { this.addresses = addresses; this.expiration = expiration; } InetAddress[] addresses; // 地址 long expiration; // 過期時間 } static final class Cache { private LinkedHashMap<String, CacheEntry> cache; private Type type; enum Type {Positive, Negative}; public Cache(Type type) { this.type = type; cache = new LinkedHashMap<String, CacheEntry>(); } private int getPolicy() { // 緩存過期時間 if (type == Type.Positive) { return InetAddressCachePolicy.get(); // -Dsun.net.inetaddr.ttl=-1(啟動參數) || networkaddress.cache.ttl=-1(配置文件) } else { return InetAddressCachePolicy.getNegative(); // -Dsun.net.inetaddr.negative.ttl=10(啟動參數) || networkaddress.cache.negative.ttl=10(配置文件) } } public Cache put(String host, InetAddress[] addresses) { int policy = getPolicy(); if (policy == InetAddressCachePolicy.NEVER) { // 0:不緩存 return this; } if (policy != InetAddressCachePolicy.FOREVER) { // 非永久緩存:需要清除過期記錄 LinkedList<String> expired = new LinkedList<>(); long now = System.currentTimeMillis(); for (String key : cache.keySet()) { CacheEntry entry = cache.get(key); if (entry.expiration >= 0 && entry.expiration < now) { // 當前記錄已過期 expired.add(key); } else { // 當前記錄未過期 break; // 后續記錄的插入時間肯定比當期記錄晚 } } for (String key : expired) { // 清除過期記錄 cache.remove(key); } } long expiration; if (policy == InetAddressCachePolicy.FOREVER) { // -1:永久緩存 expiration = -1; } else { // 非永久緩存 expiration = System.currentTimeMillis() + (policy * 1000); // 過期時間:policy秒 } CacheEntry entry = new CacheEntry(addresses, expiration); cache.put(host, entry); // 添加記錄 return this; } public CacheEntry get(String host) { int policy = getPolicy(); if (policy == InetAddressCachePolicy.NEVER) { // 一定沒有緩存記錄 return null; } CacheEntry entry = cache.get(host); // 獲取記錄 if (entry != null && policy != InetAddressCachePolicy.FOREVER) { // 非永久緩存 if (entry.expiration >= 0 && entry.expiration < System.currentTimeMillis()) { // 檢查是否過期 cache.remove(host); entry = null; } } return entry; } }
2)getCachedAddresses
private static boolean addressCacheInit = false; // 緩存未初始化 static InetAddress[] unknown_array; // 域名解析失敗時使用的地址:任意地址{0.0.0.0, 0.0.0.0} || {::, ::} private static Cache addressCache = new Cache(Cache.Type.Positive); // 正面緩存 private static Cache negativeCache = new Cache(Cache.Type.Negative); // 負面緩存 private static final HashMap<String, Void> lookupTable = new HashMap<>(); private static InetAddress[] getCachedAddresses(String hostname) { hostname = hostname.toLowerCase(); synchronized (addressCache) { cacheInitIfNeeded(); CacheEntry entry = addressCache.get(hostname); // 在正面緩存中查找記錄 if (entry == null) { entry = negativeCache.get(hostname); // 在負面緩存中查找記錄 } if (entry != null) { return entry.addresses; // return 緩存中記錄的地址 } } return null; // 在緩存中未找到記錄 } private static void cacheInitIfNeeded() { assert Thread.holdsLock(addressCache); if (addressCacheInit) { return; } unknown_array = new InetAddress[1]; // 大小為1 unknown_array[0] = impl.anyLocalAddress(); // Inet4Address(0.0.0.0/0.0.0.0) || Inet6Address(::/::) addressCache.put(impl.anyLocalAddress().getHostName(), unknown_array); addressCacheInit = true; }
3)getAddressesFromNameService
private static InetAddress[] getAddressesFromNameService(String host, InetAddress reqAddr) throws UnknownHostException { ... ... if ((addresses = checkLookupTable(host)) == null) { // 是否正在解析該host ? 等待 try { for (NameService nameService : nameServices) { try { addresses = nameService.lookupAllHostAddr(host); // 向該域名服務請求域名解析 success = true; break; // 只要有一個域名服務成功解析host則退出循環 } catch (UnknownHostException uhe) { // 1. 若host = localhost,則addresses = [loopbackAdress] & 退出循環 // 2. 否則准備拋異常(addresses = unknown_array & success = false & 記錄uhe) & 繼續循環 ... ... } } // 若reqAddr不為空 && addresses中存在與reqAddr相等的InetAddress,則將該InetAddress挪到第一個位置 ... ... cacheAddresses(host, addresses, success); // 緩存記錄 // 域名解析失敗則拋異常uhe ... ... } finally { updateLookupTable(host); // 喚醒所有等待解析該host的線程 } } return addresses; } private static InetAddress[] checkLookupTable(String host) { synchronized (lookupTable) { if (lookupTable.containsKey(host) == false) { // 其它線程是否也在請求解析該host lookupTable.put(host, null); // 告知之后請求解析該host的線程:正在進行域名解析 return null; // return & 進行域名解析 } while (lookupTable.containsKey(host)) { // 域名解析尚未完成 try { lookupTable.wait(); // 等待 } catch (InterruptedException e) { } } } InetAddress[] addresses = getCachedAddresses(host); // 在緩存中查找記錄 if (addresses == null) { // 在緩存中未找到記錄(之前線程對host進行域名解析失敗:addresses == unknown_array) synchronized (lookupTable) { lookupTable.put(host, null); return null; // return & 繼續進行域名解析 } } return addresses; // 在緩存中找到記錄 } private static void cacheAddresses(String hostname, InetAddress[] addresses, boolean success) { hostname = hostname.toLowerCase(); synchronized (addressCache) { cacheInitIfNeeded(); if (success) { // 域名解析成功 addressCache.put(hostname, addresses); // 在正面緩存中記錄 } else { // 域名解析失敗 negativeCache.put(hostname, addresses); // 在負面緩存中記錄 } } } private static void updateLookupTable(String host) { synchronized (lookupTable) { lookupTable.remove(host); // 告知所有等待解析該host的線程:域名解析完畢 lookupTable.notifyAll(); // 喚醒所有等待解析該host的線程 } }
2. getByAddress
getByName會立刻請求解析域名為IP地址,而getByAddress不會立刻請求解析IP地址為域名,獲取域名在調用getHostName時執行。
public static InetAddress getByAddress(byte[] addr) throws UnknownHostException { return getByAddress(null, addr); } public static InetAddress getByAddress(String host, byte[] addr) throws UnknownHostException { // host截子串:[host] -> host ... ... if (addr != null) { if (addr.length == Inet4Address.INADDRSZ) { // 4字節:IPv4地址 return new Inet4Address(host, addr); } else if (addr.length == Inet6Address.INADDRSZ) { // 16字節:IPv6地址 byte[] newAddr = IPAddressUtil.convertFromIPv4MappedAddress(addr); // 轉IPv4地址 if (newAddr != null) { // 轉換成功 return new Inet4Address(host, newAddr); } else { // 轉換失敗 return new Inet6Address(host, addr); } } } throw new UnknownHostException("addr is of illegal length"); }
getHostName
public String getHostName() { return getHostName(true); } String getHostName(boolean check) { if (holder().getHostName() == null) { holder().hostName = InetAddress.getHostFromNameService(this, check); // 根據IP地址請求域名 } return holder().getHostName(); } private static String getHostFromNameService(InetAddress addr, boolean check) { String host = null; for (NameService nameService : nameServices) { try { host = nameService.getHostByAddr(addr.getAddress()); // 根據IP地址向該域名服務請求域名 ... ... // prevent spoofing??? InetAddress[] arr = InetAddress.getAllByName0(host, check); // 反向由域名獲取InetAddress:可能將進行域名解析 boolean ok = false; if(arr != null) { for(int i = 0; !ok && i < arr.length; i++) {// 在arr中查找與addr(IP地址)相等的InetAddress ok = addr.equals(arr[i]); // InetAddress.equals:IP地址相等 } } if (!ok) { // arr == null || 在arr中未找到與addr(IP地址)相等的InetAddress host = addr.getHostAddress(); // IP地址字符串 return host; } break; } catch (SecurityException e) { host = addr.getHostAddress(); // IP地址字符串 break; } catch (UnknownHostException e) { host = addr.getHostAddress(); // IP地址字符串 } } return host; }