在互聯網中通訊須要藉助 IP 地址來定位到主機,而 IP 地址由不少數字組成,對於人類來講記住某些組合數字很困難,因而,爲了方便你們記住某地址而引入主機名和域名。java
早期的網絡中的機器數量不多,能很方便地經過 hosts 文件來完成主機名稱和 IP 地址的映射,這種方式須要用戶本身維護網絡上全部主機的映射關係。後來互聯網迅猛發展起來,hosts 文件方式已經沒法勝任,因而引入域名系統(DNS)來解決主機名稱和 IP 地址的映射。數組
局域網中經常使用來表示 IP 地址的名稱更多稱爲主機名,而互聯網上用來表示 IP 地址的名稱更多稱爲域名。核心內容都相同,都是解決名稱和 IP 地址間的映射。bash
Java 中提供了不少互聯網主機名稱和地址操做相關的接口,如今來看看 JDK 內部對域名解析相關功能的實現。其實,InetAddress 類內部存在一個 NameService 內部接口用於實現域名及IP的映射。服務器
對於 JDK 主要使用了兩種映射解析方案,一種是 hosts 文件機制,另一種是操做系統自帶的解析方案。網絡
--java.lang.Object
--java.net.InetAddress$HostsFileNameService
--java.net.InetAddress$PlatformNameService
複製代碼
以上兩種主機名稱 IP 映射機制,JDK 是怎樣選擇的呢?其實就是根據 jdk.net.hosts.file
系統屬性來肯定的,默認狀況下使用基於操做系統的 PlatformNameService 方案,而若是配置了jdk.net.hosts.file
系統屬性則使用基於 hosts 文件的 HostsFileNameService 方案,好比能夠在啓動時配置參數 -Djdk.net.hosts.file=/etc/hosts
。對應邏輯代碼以下:數據結構
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;
}
複製代碼
private interface NameService {
InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException;
String getHostByAddr(byte[] addr) throws UnknownHostException;
}
複製代碼
NameService 接口主要定義了兩個方法,用於獲取主機名稱對應的 IP 地址和 IP 地址對應的主機名稱。併發
類定義以下:機器學習
private static final class HostsFileNameService implements NameService
複製代碼
該類便是對基於 hosts 文件方案的封裝,主要看看核心的兩個方法,分佈式
該方法根據主機名稱實現基於 hosts 文件的 IP 地址查找方案。它要完成的邏輯以下:函數
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 對象,該方法再也不貼出。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;
}
複製代碼
該方法根據 IP 地址實現基於 hosts 文件的主機名稱查找方案。它要完成的邏輯以下:
new byte[] {127, 0, 0, 1}
,先調用 addrToString 方法將其轉換爲"127.0.0.1"字符串,該方法再也不貼出。127.0.0.1 localhost #local
,# 號後面爲註釋內容,因此調用 removeComments 方法去掉 #local
,該方法再也不貼出。127.0.0.1 localhost
,接着看是否包含了傳進來的 IP 地址,有的話則說明是該 IP 地址對應的主機名稱,經過 extractHost 方法提取主機名稱localhost
,該方法再也不貼出。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;
}
複製代碼
類定義以下:
private static final class PlatformNameService implements NameService
複製代碼
該類便是對操做系統自帶的解析方案的封裝,核心的兩個方法以下,由於這兩個方法與操做系統相關,因此經過它們經過 InetAddressImpl 接口調用了對應的本地方法,本地方法分別爲 lookupAllHostAddr 和 getHostByAddr。
public InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException{
return impl.lookupAllHostAddr(host);
}
public String getHostByAddr(byte[] addr) throws UnknownHostException{
return impl.getHostByAddr(addr);
}
複製代碼
該本地方法中要完成的工做主要就是先經過操做系統提供的主機名稱服務接口來獲取對應的 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. |
該本地方法用於根據 IP 地址獲取主機名,傳入的參數爲 byte[],返回爲字符串。它要完成的工做就是經過操做系統提供的主機名稱服務接口獲取主機名,而後返回字符串。
Windows 和 unix-like 操做系統實現的代碼都差很少,這裏只貼出 Windows的,基本的邏輯爲:先經過 JNI 的 GetByteArrayRegion 函數獲取傳入的4個字節,這裏由於字節多是負數,因此須要進行移位操做;而後經過 getnameinfo 函數獲取主機名;最後經過 JNI 的 NewStringUTF 函數將主機名放到新建的字符串對象中。
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);
}
}
return ret;
}
複製代碼
-------------推薦閱讀------------
------------------廣告時間----------------
公衆號的菜單已分爲「分佈式」、「機器學習」、「深度學習」、「NLP」、「Java深度」、「Java併發核心」、「JDK源碼」、「Tomcat內核」等,可能有一款適合你的胃口。
鄙人的新書《Tomcat內核設計剖析》已經在京東銷售了,有須要的朋友能夠購買。感謝各位朋友。
歡迎關注: