IPv 背景介紹
目前我們使用的是第二代互聯網 IPv 技術它的最大問題是網絡地址資源有限從理論上講可以編址 萬個網絡 億台主機但采用 ABC 三類編址方式後可用的網絡地址和主機地址的數目大打折扣以至目前的 IP 地址近乎枯竭網絡地址不足嚴重地制約了全球互聯網的應用和發展
一方面是地址資源數量的限制另一方面是隨著電子技術及網絡技術的發展計算機網絡將進入人們的日常生活可能身邊的每一樣東西都需要連入全球因特網在這種網絡空間匮乏的環境下IPv 應運而生它的產生不但解決了網絡地址資源數量的問題同時也為除電腦外的設備連入互聯網在數量限制上掃清了障礙
如果說 IPv 實現的只是人機對話那麼 IPv 則擴展到任意事物之間的對話它不僅可以為人類服務還將服務於眾多硬件設備如家用電器傳感器遠程照相機汽車等它將是無時不在無處不在的深入社會每個角落的真正的寬帶網它所帶來的經濟效益也將非常巨大
當然IPv 並非十全十美一勞永逸不可能解決所有問題IPv 只能在發展中不斷完善也不可能在一夜之間發生過渡需要時間和成本但從長遠看IPv 有利於互聯網的持續和長久發展目前國際互聯網組織已經決定成立兩個專門工作組制定相應的國際標准
Java 對 IPv 的支持
隨著 IPv 越來越受到業界的重視Java 從 版開始支持 Linux 和 Solaris 平台上的 IPv 版起又加入了 Windows 平台上的支持相對於 C++Java 很好得封裝了 IPv 和 IPv 的變化部分遺留代碼都可以原生支持 IPv而不用隨底層具體實現的變化而變化
那麼 Java 是如何來支持 IPv 的呢? Java 網絡棧會優先檢查底層系統是否支持 IPv以及采用的何種 IP 棧系統如果是雙棧系統那它直接創建一個 IPv 套接字(如圖 )
圖 雙棧結構
對於分隔棧系統Java 則創建 IPv/v 兩個套接字(如圖 )如果是 TCP 客戶端程序一旦其中某個套接字連接成功另一個套接字就會被關閉這個套接字連接使用的 IP 協議類型也就此被固定下來如果是 TCP 服務器端程序因為無法預期客戶端使用的 IP 協議所以 IPv/v 兩個套接字會被一直保留對於 UDP 應用程序無論是客戶端還是服務器端程序兩個套接字都會保留來完成通信
圖 分隔棧結構
如何驗證 IPv 地址
IPv 地址表示
從 IPv 到 IPv 最顯著的變化就是網絡地址的長度IPv 地址為 位長度一般采用 個十六進制數但通常寫做 組每組 個十六進制的形式例如
:db:a:d::ae:: 是一個合法的 IPv 地址如果四個數字都是零則可以被省略
:db:a:::ae:: 等同於 :db:a:::ae::
遵從這些規則如果因為省略而出現了兩個以上的冒號的話可以壓縮為一個但這種零壓縮在地址中只能出現一次因此
:DB::::::ab
:DB::::::ab
:DB::::::ab
:DB:::::ab
:DB:::ab
都是合法的地址並且他們是等價的但 ::de::cade 是非法的(因為這樣會使得搞不清楚每個壓縮中有幾個全零的分組)同時前導的零可以省略因此:DB:de::e 等於 :DB:de::e
IPv 地址校驗
IPv 地址可以很容易的轉化為 IPv 格式舉例來說如果 IPv 的一個地址為 (十六進制為 xBB)它可以被轉化為 ::::::B:B 或者:B:B同時還可以使用混合符號(IPv compatible address)則地址可以為:
在 IPv 的環境下開發 Java 應用或者移植已有的 IPv 環境下開發的 Java 應用到 IPv 環境中來對於 IPv 網絡地址的驗證是必須的步驟尤其是對那些提供了 UI(用戶接口)的 Java 應用
所幸的是從 Java 開始Sun 就增加了對 IPv 網絡地址校驗的 Java 支持程序員可以通過簡單地調用方法 utilIPAddressUtilisIPvLiteralAddress() 來驗證一個 String 類型的輸入是否是一個合法的 IPv 網絡地址
為了更深入一步地了解 IPv 的網絡地址規范及其驗證算法筆者參閱了一些材料包括上文所述的方法 utilIPAddressUtilisIPvLiteralAddress() 的源代碼以及目前網絡上流傳的一些 IPv 網絡地址的正則表達式發現
由於 IPv 協議所允許的網絡地址格式較多規范較寬松(例如零壓縮地址IPv 映射地址等)所以導致了 IPv 網絡地址的格式變化很大
Java 對於 IPv 網絡地址的驗證是通過對輸入字符的循環匹配做到的並沒有采取正則表達式的做法其匹配過程中還依賴於其它的 Java 方法
目前網絡上流傳的 IPv 網絡地址驗證的正則表達式通常都只能涵蓋部分地址格式而且表達式冗長難讀非常不易於理解
基於通用性考慮以及為了使驗證方法盡量簡單易讀筆者嘗試將 IPv 網絡地址的格式簡單分類以後使用多個正則表達式進行驗證
這種做法兼顧了通用性(基於正則表達式所以方便用各種不同的編程語言進行實現)以及易讀性(每個獨立的正則表達式相對簡短);並且根據測試支持目前所有的 IPv 網絡地址格式類型尚未發現例外
以下是筆者用 Java 編寫的對於 IPv 網絡地址的驗證方法此算法可被簡單地用其它編程語言仿照重寫
清單 驗證地址
//IPv address validator matches these IPv formats
//::ffff:: | :db:a:d::ae::
//| ::ae::: | :db:a:d::ae:
//| :db::ae: | :: | ffff::
//And such addresses are invalid
//::ae::: | :idb::: | ::a
//| :::
public static boolean isIPVFormat(String ip) {
ip = iptrim();
//in many cases such as URLs IPv addresses are wrapped by []
if(ipsubstring( )equals([) && ipsubstring(iplength())equals(]))
ip = ipsubstring( iplength());
return ( < pile(:)split(ip)length)
//a valid IPv address should contains no less than
//and no more than : as separators
&& (pile(:)split(ip)length <= )
//the address can be compressed but :: can appear only once
&& (pile(::)split(ip)length <= )
//if a compressed address
&& (pile(::)split(ip)length == )
//if starts with :: – leading zeros are compressed
? (((ipsubstring( )equals(::))
? Patternmatches(^::([\\daf]{}(:)){}(([\\daf]{}(:)[\\daf]{})
|([\\daf]{})|((\\d{}){}\\d{})) ip)
: Patternmatches(^([\\daf]{}(:|::)){}
(([\\daf]{}(:|::)[\\daf]{})|([\\daf]{})
|((\\d{}){}\\d{})) ip)))
//if ends with :: ending zeros are compressed
: ((ipsubstring(iplength())equals(::))
? Patternmatches(^([\\daf]{}(:|::)){} ip)
: Patternmatches(^([\\daf]{}:){}(([\\daf]{}
:[\\daf]{})|((\\d{}){}\\d{})) ip));
}}
如何正規化 IPv 地址
在網絡程序開發中經常使用 IP 地址來標識一個主機例如記錄終端用戶的訪問記錄等由於 IPv 具有有零壓縮地址等多種表示形式因此直接使用 IPv 地址作為標示符可能會帶來一些問題為了避免這些問題在使用 IPv 地址之前有必要將其正規化除了通過我們熟知的正則表達式筆者在開發過程中發現使用一個簡單的 Java API 也可以達到相同的效果
清單 正規化地址
InetAddress inetAddr = InetAddressgetByName(ipAddr);
ipAddr = inetAddrgetHostAddress();
Systemoutprintln(ipAddr);
InetAddressgetByName(String) 方法接受的參數既可以是一個主機名也可以是一個 IP 地址字符串我們輸入任一信息的合法 IPv 地址再通過 getHostAddress() 方法取出主機 IP 時地址字符串 ipAddr 已經被轉換為完整形式例如輸入 :b:eaa::b:eaa上述代碼執行過後零壓縮部分將被還原ipAddr 變為 :b:eaa::::b:eaa
如何獲取本機 IPv 地址
有時為了能夠注冊 listener開發人員需要使用本機的 IPv 地址這一地址不能簡單得通過 InetAddressgetLocalhost() 獲得因為這樣有可能獲得諸如 ::::::: 這樣的特殊地址使用這樣的地址其他服務器將無法把通知發送到本機上因此必須先進行過濾選出確實可用的地址以下代碼實現了這一功能思路是遍歷網絡接口的各個地址直至找到符合要求的地址
清單 獲取本機 IP 地址
public static String getLocalIPvAddress() throws IOException {
InetAddress inetAddress = null;
Enumeration networkInterfaces = NetworkInterface
getNetworkInterfaces();
outer:
while (networkInterfaceshasMoreElements()) {
Enumeration inetAds = networkInterfacesnextElement()
getInetAddresses();
while (inetAdshasMoreElements()) {
inetAddress = inetAdsnextElement();
//Check if its ipv address and reserved address
if (inetAddress instanceof InetAddress
&& !isReservedAddr(inetAddress)) {
break outer;
}
}
}
String ipAddr = inetAddressgetHostAddress();
// Filter network card No
int index = ipAddrindexOf(%);
if (index > ) {
ipAddr = ipAddrsubstring( index);
}
return ipAddr;
}
/**
* Check if its local address or link local address or
* loopbackaddress
*
* @param ip address
*
* @return result
*/
private static boolean isReservedAddr(InetAddress inetAddr) {
if (inetAddrisAnyLocalAddress() || inetAddrisLinkLocalAddress()
|| inetAddrisLoopbackAddress()) {
return true;
}
return false;
}
為了支持 IPvJava 中增加了兩個 InetAddress 的子類InetAddress 和 InetAddress一般情況下這兩個子類並不會被使用到但是當我們需要分別處理不同的 IP 協議時就非常有用在這我們根據 InetAddress 來篩選地址
isReservedAddr() 方法過濾了本機特殊 IP 地址包括LocalAddressLinkLocalAddress和LoopbackAddress讀者可根據自己的需要修改過濾標准
另一個需要注意的地方是在 windows 平台上取得的 IPv 地址後面可能跟了一個百分號加數字這裡的數字是本機網絡適配器的編號這個後綴並不是 IPv 標准地址的一部分可以去除
IPv/IPv 雙環境下網絡的選擇和測試
我們先看一下筆者所在的 IPv/IPv 開發測試環境及其配置方法
筆者所處的 IPv/IPv 雙環境是一個典型的to雙棧網絡其中存在著一個 IPv 到 IPv 的映射機制即任意一個 IPv 地址 :a:fa::a:b:c:d 在路由時會被默認映射為 IPv 地址 abcd所以路由表只有一套
在此環境內IPv 地址與 IPv 地址的一一對應是人工保證的如果一台客戶機使用不匹配的 IPv 和 IPv 雙地址或者同時使用 DHCPv 和 DHCPv(可能會導致 IPv 地址和 IPv 地址不匹配)會導致 IPv 的路由尋址失敗
正因為如此為了配置雙地址環境我們一般使用 DHCPv 來自動獲取 IPv 地址然後人工配置相對應的 IPv 地址
Windows 系統
Windows 及以下不支持 IPv
Windows 和 Windows XP使用 Windows 自帶的 netsh 命令行方式添加 IPv 地址以及 DNS 例如C:\>netsh interface ipv add address Local Area Connection :a:fa::::: 和 C:\>netsh interface ipv add dns Local Area Connection :a:fa::::
Windows 和 Windows Vista既可以使用 Windows 網絡屬性頁面進行配置也可以使用類似 Windows 和 Windows XP 的 netsh 命令行來配置
Linux 系統 (以下是 IPv 的臨時配置方法即不修改配置文件計算機重啟後配置失效)
Redhat Linux最簡單的方法是使用 ifconfig 命令行添加 IPv 地址例如ifconfig eth inet add :a:fa:::::/
SUSE Linux同上
從實踐上講由於 Java 的面向對象特性以及 包對於 IP 地址的良好封裝從而使得將 Java 應用從 IPv 環境移植到 IPv/IPv 雙環境或者純 IPv 環境變得異常簡單通常我們需要做的僅是檢查代碼並移除明碼編寫的 IPv 地址用主機名來替代則可
除此以外對於一些特殊的需求Java 還提供了 InetAddress 的兩個擴展類以供使用InetAddress 和 InetAddress其中封裝了對於 IPv 和 IPv 的特殊屬性和行為然而由於 Java 的多態特性使得程序員一般只需要使用父類 InetAddressJava 虛擬機可以根據所封裝的 IP 地址類型的不同在運行時選擇正確的行為邏輯所以在多數情況下程序員並不需要精確控制所使用的類型及其行為一切交給 Java 虛擬機即可
具體的新增類型及其新增方法請具體參閱 Sun 公司的 JavaDoc
另外在 IPv/IPv 雙環境中對於使用 Java 開發的網絡應用比較值得注意的是以下兩個 IPv 相關的 Java 虛擬機系統屬性
preferIPvStack=<true|false>
preferIPvAddresses=<true|false>
preferIPvStack(默認 false)表示如果存在 IPv 和 IPv 雙棧Java 程序是否優先使用 IPv 套接字默認值是優先使用 IPv 套接字因為 IPv 套接字可以與對應的 IPv 或 IPv 主機進行對話;相反如果優先使用 IPv則只不能與 IPv 主機進行通信
preferIPvAddresses(默認 false)表示在查詢本地或遠端 IP 地址時如果存在 IPv 和 IPv 雙地址Java 程序是否優先返回 IPv 地址Java 默認返回 IPv 地址主要是為了向後兼容以支持舊有的 IPv 驗證邏輯以及舊有的僅支持 IPv 地址的服務
總結
從計算機技術的發展因特網的規律和網絡的傳輸速率來看IPV 都已經不適用了其中最主要的問題就是 IPV 的 比特的 IP 地址空間已經無法滿足迅速膨脹的因特網規模但是 IPv 的引入為我們解決了 IP 地址近乎枯竭的問題本文對 IPv 地址做了一些基本的介紹著重介紹了如何使用 Java 開發兼容 IPv 的網絡應用程序包括如何驗證 IPv 地址如何正規化 IPv 地址的表示如何獲取本機 IPv 的地址以及在 IPv/IPv 雙地址環境下的網絡選擇和測試同時作者結合在日常工作中使用的 Java 代碼片段希望呈現給讀者一個全方位的具有較強實用性的文本介紹也希望本文能給讀者在以後使用 Java 開發 IPv 兼容程序的過程中帶來一些幫助
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26767.html