絕大部分知識與實例來自O’REILLY的《Java網絡編程》(Java Network Programming,Fourth Edition,by Elliotte Rusty Harold(O’REILLY))。
InetAddress類簡介
InetAddress類位於java.net包中,是Java對IP地址(包括IPv4和IPv6)的高層表示。許多其他網絡相關類,包括Socket、ServerSocket、URL等都用到了這個類。這個類的每個實例包含一個IP地址(IP address)以及這個IP地址對應的主機名(host name)。
獲取InetAddress實例
InetAddress類沒有public的構造器,想要獲取它的實例,需要通過它的一些靜態工廠方法。最常用的一個是InetAddress.getByName(),能夠根據域名或者IP地址獲取InetAddress實例。這個方法會建立與本地DNS服務器的一個連接,並查找主機名和IP地址。如果DNS找不到這個地址,方法會拋出一個UnknownHostException,IOException的一個子類。
實例1:查詢某個域名的IP地址和主機名
public static void ShowAddress(String domainName){
InetAddress address;
try {
address = InetAddress.getByName(domainName);
System.out.println(address);
} catch (UnknownHostException e) {
System.out.println("Could not find" + domainName);
e.printStackTrace();
}
}
public static void main(String[] args) {
ShowAddress("www.baidu.com");
}
輸出:
www.baidu.com/183.232.231.172
另外一個經常使用的是InetAddress.getLocalHost,用於返回運行代碼的主機對應的InetAddress對象。
實例2:查找本地機器的地址
public static void ShowLocalAddress(){
try {
InetAddress address = InetAddress.getLocalHost();
System.out.println(address);
} catch (UnknownHostException e) {
System.out.println("Could not find this computer's address.");
}
}
public static void main(String[] args) {
ShowLocalAddress();
}
另外,有時一個主機名可能有多個地址,如果想要全部獲得,可以使用getAllByName()方法。該方法會返回一個InetAddress數組。
最後的兩個構造器,用於根據原始IP地址和主機名構建一個InetAddress對象:
public static InetAddress getByAddress(byte[] addr) throws UnknownHostException
public static InetAddress getByAddress(String hostName,byte[] addr) throws UnknownHostException
需要注意三點:(1)參數中的主機名不一定能和IP地址對應。(2)byte[]遵循big-endian,即IP地址的高位存儲在數組的低位。(3)Java中的byte爲有符號字節,因此數值上不一定和int的值對應(比如168轉爲byte後值爲-88)。
補充一下關於int和有符號字節的轉換:有符號字節的取值範圍爲-128~127,而8位無符號整型的取值範圍爲0~255。轉換方法如下:
- 無符號整型轉有符號字節:0~127不變,128~255用256減去原值。
- 有符號字節轉無符號整型:非負數不變,負數加上256。
緩存
由於DNS查找開銷很大,InetAddress類會對查找結果進行緩存,一旦得到一個給定主機的地址,就不會再次查找。這樣做可以使得可能會進行重複查找的程序的性能得到提升。不過,這種機制也帶來了一些問題,比如無法察覺主機地址的改變等。
可以通過系統屬性networkaddress.cache.ttl和networkaddress.cache.negative.ttl控制成功的DNS查找和失敗的DNS查找的結果的緩存時間,若設置爲-1則永不過期。
按IP地址查找
使用InetAddress.getByName傳入一個IP地址作爲參數時,不會檢查DNS,只有當請求主機名(getHostName())時纔會真正完成DNS查找。這意味着創建出來的InetAddress對象可能不對應任何主機。如果無法成功查找到主機名,主機名會和傳入的IP地址保持一致,而不會拋出UnknownHostException異常。
相對來講,主機名比IP地址穩定的多。在構建InetAddress對象時,應優先考慮使用主機名作爲參數傳入。
get方法
InetAddress包含4個get方法,如下:
- String getHostName():返回主機名(主機別名)
- String getCanonicalHostName():返回主機全名
- byte[] getAddress():獲得IP地址的原始字節碼
- String getHostAddress():獲得IP地址
主機全名是主機的全稱,主機別名是爲了方便好記等目的,爲主機起的一個“暱稱”。下面是用www.oracle.com構建的InetAddress對象調用getHostName()和getCanonicalHostName()的結果:
www.oracle.com
a23-77-22-123.deploy.static.akamaitechnologies.com
至於查看原始字節碼的方法,很多時候主要是用來判斷協議類型(IPv4地址4字節、IPv6地址16個字節)。
實例3:判斷IP地址使用的協議版本
public static int getIpVersion(InetAddress ia){
byte[] address = ia.getAddress();
if(address.length == 4){
return 4;
}else if (address.length == 16) {
return 6;
}else {
return -1;
}
}
public static void main(String[] args) {
try {
InetAddress ia = InetAddress.getByName("192.168.0.0");
System.out.println(getIpVersion(ia));
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
輸出:
4
判斷地址類型
有許多IP地址有着特殊的含義,比如127.0.0.1是本地回送地址,224.0.0.0~239.255.255.255是組播地址等。InetAddress類提供了以下方法對地址類別進行辨別:
- isAnyLocalAddress():判斷是否是通配地址。IPv4中通配地址爲0.0.0.0,IPv6中通配地址爲0:0:0:0:0:0:0:0(或寫作::)。通配地址可以匹配本機系統中的任何地址。
- isLoopbackAddress():判斷是否是回送地址。IPv4中回送地址爲127.0.0.1,IPv6中回送地址爲0:0:0:0:0:0:0:1(或寫作::1)。回送地址在網絡層連接計算機和它自身。
- isLinkLocalAddress():判斷是否是IPv6本地鏈接地址。IPv6本地鏈接地址可以用於幫助IPv6網絡實現自配置,與IPv4網絡上的DHCP類似,但不需要使用服務器。路由器不會把發送給本地鏈接地址的包轉發到本地子網以外。所有本地連接地址都用8字節FE80:0000:0000:0000開頭,後8字節用本地地址填充,這個地址通常從以太網卡生產商分配的以太網MAC地址複製。
- isSiteLocalAddress():判斷是否是IPv6本地網站地址。本地網站地址與本地鏈接地址類似,不過本地網站地址可以由路由器在網站或校園內轉發,但不應轉發到網站以外。本地網站地址以8字節FEC0:0000:0000:0000開頭,後8字節用本地地址填充,這個地址通常從以太網卡生產商分配的以太網MAC地址複製。
- isMulticastAddress():判斷是否是組播地址。IPv4中組播地址範圍是224.0.0.0~239.255.255.255,IPv6中組播地址以FF開頭。
- isMCGlobal():判斷是否是全球組播地址。全球組播地址可能在全世界都有訂購者。IPv6中全球組播地址以FF0E或FF1E開頭,取決於這個組播地址是已知的永久分配地址還是一個臨時地址。IPv4中所有組播地址都是全球範圍的,範圍依靠TTL(Time To Live,生存時間)控制。
- isMCOrgLocal():判斷是否是組織範圍組播地址。組織範圍組播地址可能在公司或組織的所有網站都有訂購者,但不包括組織外。以FF08或FF18開頭,取決於是已知的永久分配地址還是臨時地址。
- isMCSiteLocal():判斷是否是網站範圍組播地址。發送到網站範圍組播地址的包只會在本地網站內傳輸。以FF05或FF15開頭,取決於是已知的永久分配地址還是臨時地址。
- isMCLinkLocal():判斷是否是子網範圍組播地址。發送到子網範圍組播地址的包只會在子網內傳輸。以FF02或FF12開頭,取決於是已知的永久分配地址還是臨時地址。
- isMCNodeLocal():判斷是否是本地接口組播地址。發送到本地接口組播地址的包不能發送到最初的網絡接口以外,即使是相同節點上的不同網絡接口也不行,主要用於網絡調試。以FF01或FF11開頭,取決於是已知的永久分配地址還是臨時地址。
測試可達性
InetAddress類還提供了兩個isReachable()方法,用於測試節點對當前主機是否可達,原理是traceroute方法。
public boolean isReachable(int timeout) throws IOException
public boolean isReachable(NetworkInterface interface int ttl,int timeout) throws IOException
如果主機在timeout毫秒內響應則返回true,否則返回false。如果出現網絡錯誤則拋出IOException異常。第二個方法允許指定從哪個本地網絡接口建立連接,以及生存時間TTL。
Object方法
- equals():只要兩個InetAddress對象指向同一個IP地址就返回true,不考慮主機名。
- hashcode():同樣只根據IP地址計算。
- toString():格式爲主機名/IP地址,若主機名不存在則將主機名置空。
Inet4Address與Inet6Address
Inet4Address與Inet6Address是InetAddress的兩個子類,用於區分IPv4地址和IPv6地址。一般來講,編程人員處於應用層,不需要了解IP地址的細節,因此這兩個子類很少用到。
NetworkInterface類
NetworkInterface類表示一個本地IP地址,可能是一個物理接口(額外的以太網卡),也可能是一個虛擬接口(與其他IP地址綁定到同一個物理硬件)。NetworkInterface提供了枚舉本地地址的方法。
示例4:列出所有網絡接口
public static void listAllNetworkInterfaces(){
try {
Enumeration<NetworkInterface> interfaces =
NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface =
(NetworkInterface) interfaces.nextElement();
System.out.println(networkInterface);
}
} catch (SocketException e) {
e.printStackTrace();
}
}
除此之外,NetworkInterface類還提供了兩個靜態工廠方法,用於根據名稱或IP地址獲取NetworkInterface對象:
public static NerworkInterface getByName(String name) throws SocketException
public static NetworkInterface getByInetAddress(InetAddress address) throws SocketException
獲得了NetworkInterface對象後,就可以利用它的一系列get方法獲取IP地址、名稱等信息。