1. 程式人生 > >InetAddress之域名解析

InetAddress之域名解析

否則 eric ref final local 可能 lookup 獲取 循環

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.ttl=-1(配置文件)
        }
    }

    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; // 域名解析失敗時使用的地址
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); // {0.0.0.0, 0.0.0.0} || {::, ::}
    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. 否則準備拋異常(success = false & 記錄uhe) & 繼續循環
                    ... ...
                }
            }
            // 若reqAddr不為空 && addresses中存在與reqAddr相等的InetAddress,則將該InetAddress挪到第一個位置
            ... ...

            cacheAddresses(host, addresses, success); // 緩存記錄

            // 
            ... ...

        } 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) { // 在緩存中未找到記錄
        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

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");
}

3. 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;
}

InetAddress之域名解析