程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 使用Java開發兼容IPv6的網絡應用程序

使用Java開發兼容IPv6的網絡應用程序

編輯:關於JAVA
 IPv6 背景介紹

  目前我們使用的是第二代互聯網 IPv4 技術,它的最大問題是網絡地址資源有限,從理論上講,可以編址 1600 萬個網絡、40 億台主機。但采用 A、B、C 三類編址方式後,可用的網絡地址和主機地址的數目大打折扣,以至目前的 IP 地址近乎枯竭。網絡地址不足,嚴重地制約了全球互聯網的應用和發展。

  一方面是地址資源數量的限制,另一方面是隨著電子技術及網絡技術的發展,計算機網絡將進入人們的日常生活,可能身邊的每一樣東西都需要連入全球因特網。在這種網絡空間匮乏的環境下,IPv6 應運而生。它的產生不但解決了網絡地址資源數量的問題,同時也為除電腦外的設備連入互聯網在數量限制上掃清了障礙。

  如果說 IPv4 實現的只是人機對話,那麼 IPv6 則擴展到任意事物之間的對話,它不僅可以為人類服務,還將服務於眾多硬件設備,如家用電器、傳感器、遠程照相機、汽車等,它將是無時不在,無處不在的深入社會每個角落的真正的寬帶網,它所帶來的經濟效益也將非常巨大。

  當然,IPv6 並非十全十美、一勞永逸,不可能解決所有問題。IPv6 只能在發展中不斷完善,也不可能在一夜之間發生,過渡需要時間和成本,但從長遠看,IPv6 有利於互聯網的持續和長久發展。目前,國際互聯網組織已經決定成立兩個專門工作組,制定相應的國際標准。

  Java 對 IPv6 的支持

  隨著 IPv6 越來越受到業界的重視,Java 從 1.4 版開始支持 Linux 和 Solaris 平台上的 IPv6。1.5 版起又加入了 Windows平台上的支持。相對於 C++,Java 很好得封裝了 IPv4 和 IPv6 的變化部分,遺留代碼都可以原生支持 IPv6,而不用隨底層具體實現的變化而變化。

  那麼 Java 是如何來支持 IPv6 的呢? Java 網絡棧會優先檢查底層系統是否支持 IPv6,以及采用的何種 IP 棧系統。如果是雙棧系統,那它直接創建一個 IPv6 套接字(如圖 1)。

  圖 1. 雙棧結構

  圖 1. 雙棧結構

  對於分隔棧系統,Java 則創建 IPv4/v6 兩個套接字(如圖 2)。如果是 TCP 客戶端程序,一旦其中某個套接字連接成功,另一個套接字就會被關閉,這個套接字連接使用的 IP 協議類型也就此被固定下來。如果是 TCP 服務器端程序,因為無法預期客戶端使用的 IP 協議,所以 IPv4/v6 兩個套接字會被一直保留。對於 UDP 應用程序,無論是客戶端還是服務器端程序,兩個套接字都會保留來完成通信。

  圖 2. 分隔棧結構

  圖 2. 分隔棧結構

  如何驗證 IPv6 地址

  IPv6 地址表示

  從 IPv4 到 IPv6 最顯著的變化就是網絡地址的長度,IPv6 地址為 128 位長度,一般采用 32 個十六進制數,但通常寫做 8 組每組 4 個十六進制的形式。例如:

  2001:0db8:85a3:08d3:1319:8a2e:0370:7344 是一個合法的 IPv6 地址。如果四個數字都是零,則可以被省略。

  2001:0db8:85a3:0000:1319:8a2e:0370:7344 等同於 2001:0db8:85a3::1319:8a2e:0370:7344。

  遵從這些規則,如果因為省略而出現了兩個以上的冒號的話,可以壓縮為一個,但這種零壓縮在地址中只能出現一次。因此:

  2001:0DB8:0000:0000:0000:0000:1428:57ab

  2001:0DB8:0000:0000:0000::1428:57ab

  2001:0DB8:0:0:0:0:1428:57ab

  2001:0DB8:0::0:1428:57ab

  2001:0DB8::1428:57ab

  都是合法的地址,並且他們是等價的。但 2001::25de::cade 是非法的。(因為這樣會使得搞不清楚每個壓縮中有幾個全零的分組)。同時前導的零可以省略,因此:2001:0DB8:02de::0e13 等於 2001:DB8:2de::e13。

  IPv6 地址校驗

  IPv4 地址可以很容易的轉化為 IPv6 格式。舉例來說,如果 IPv4 的一個地址為 135.75.43.52(十六進制為 0x874B2B34),它可以被轉化為 0000:0000:0000:0000:0000:0000:874B:2B34 或者::874B:2B34。同時,還可以使用混合符號(IPv4- compatible address),則地址可以為::135.75.43.52。

  在 IPv6 的環境下開發 Java 應用,或者移植已有的 IPv4 環境下開發的 Java 應用到 IPv6 環境中來,對於 IPv6 網絡地址的驗證是必須的步驟,尤其是對那些提供了 UI(用戶接口)的 Java 應用。

  所幸的是,從 Java 1.5 開始,Sun 就增加了對 IPv6 網絡地址校驗的 Java 支持。程序員可以通過簡單地調用方法 sun.Net.util.IPAddressUtil.isIPv6LiteralAddress() 來驗證一個 String 類型的輸入是否是一個合法的 IPv6 網絡地址。

  為了更深入一步地了解 IPv6 的網絡地址規范,及其驗證算法,筆者參閱了一些材料,包括上文所述的方法 sun.Net.util.IPAddressUtil.isIPv6LiteralAddress() 的源代碼,以及目前網絡上流傳的一些 IPv6 網絡地址的正則表達式,發現:

  由於 IPv6 協議所允許的網絡地址格式較多,規范較寬松(例如零壓縮地址,IPv4 映射地址等),所以導致了 IPv6 網絡地址的格式變化很大。

  Java 對於 IPv6 網絡地址的驗證是通過對輸入字符的循環匹配做到的,並沒有采取正則表達式的做法。其匹配過程中還依賴於其它的 Java 方法。

  目前網絡上流傳的 IPv6 網絡地址驗證的正則表達式通常都只能涵蓋部分地址格式,而且表達式冗長難讀,非常不易於理解。

  基於通用性考慮,以及為了使驗證方法盡量簡單易讀,筆者嘗試將 IPv6 網絡地址的格式簡單分類以後,使用多個正則表達式進行驗證。

  這種做法兼顧了通用性(基於正則表達式,所以方便用各種不同的編程語言進行實現),以及易讀性(每個獨立的正則表達式相對簡短);並且根據測試,支持目前所有的 IPv6 網絡地址格式類型,尚未發現例外。

  以下是筆者用 Java 編寫的對於 IPv6 網絡地址的驗證方法。此算法可被簡單地用其它編程語言仿照重寫。

  清單 1. 驗證地址


 //IPv6 address validator matches these IPv6 formats 
  //::ffff:21:7.8.9.221 | 2001:0db8:85a3:08d3:1319:8a2e:0370:7344 
  //| ::8a2e:0:0370:7344 | 2001:0db8:85a3:08d3:1319:8a2e:100.22.44.55 
  //| 2001:0db8::8a2e:100.22.44.55 | ::100.22.44.55 | ffff:: 
  //And such addresses are invalid 
  //::8a2e:0:0370:7344.4 | 2001:idb8::111:7.8.9.111 | 2001::100.a2.44.55 
  //| :2001::100.22.44.55 
  public static boolean isIPV6Format(String ip) { 
  ip = ip.trim(); 
  //in many cases such as URLs, IPv6 addresses are wrapped by [] 
  if(ip.substring(0, 1).equals("[") && ip.substring(ip.length()-1).equals("]")) 
  ip = ip.substring(1, ip.length()-1); 
  return (1 < Pattern.compile(":").split(ip).length) 
  //a valid IPv6 address should contains no less than 1, 
  //and no more than 7 “:” as separators 
  && (Pattern.compile(":").split(ip).length <= 8) 
  //the address can be compressed, but “::” can appear only once 
  && (Pattern.compile("::").split(ip).length <= 2) 
  //if a compressed address 
  && (Pattern.compile("::").split(ip).length == 2) 
  //if starts with “::” – leading zeros are compressed 
  ? (((ip.substring(0, 2).equals("::")) 
  ? Pattern.matches("^::([\\da-f]{1,4}(:)){0,4}(([\\da-f]{1,4}(:)[\\da-f]{1,4}) 
  |([\\da-f]{1,4})|((\\d{1,3}.){3}\\d{1,3}))", ip) 
  : Pattern.matches("^([\\da-f]{1,4}(:|::)){1,5} 
  (([\\da-f]{1,4}(:|::)[\\da-f]{1,4})|([\\da-f]{1,4})
  |((\\d{1,3}.){3}\\d{1,3}))", ip))) 
  //if ends with "::" - ending zeros are compressed 
  : ((ip.substring(ip.length()-2).equals("::")) 
  ? Pattern.matches("^([\\da-f]{1,4}(:|::)){1,7}", ip) 
  : Pattern.matches("^([\\da-f]{1,4}:){6}(([\\da-f]{1,4} 
  :[\\da-f]{1,4})|((\\d{1,3}.){3}\\d{1,3}))", ip)); 
  }}

 如何正規化 IPv6 地址

  在網絡程序開發中,經常使用 IP 地址來標識一個主機,例如記錄終端用戶的訪問記錄等。由於 IPv6 具有有零壓縮地址等多種表示形式,因此直接使用 IPv6 地址作為標示符,可能會帶來一些問題。為了避免這些問題,在使用 IPv6 地址之前,有必要將其正規化。除了通過我們熟知的正則表達式,筆者在開發過程中發現使用一個簡單的 Java API 也可以達到相同的效果。

  清單 2. 正規化地址


 InetAddress inetAddr = InetAddress.getByName(ipAddr);
  ipAddr = inetAddr.getHostAddress(); 
  System.out.println(ipAddr);

  InetAddress.getByName(String) 方法接受的參數既可以是一個主機名,也可以是一個 IP 地址字符串。我們輸入任一信息的合法 IPv6 地址,再通過 getHostAddress() 方法取出主機 IP 時,地址字符串 ipAddr 已經被轉換為完整形式。例如輸入 2002:97b:e7aa::97b:e7aa,上述代碼執行過後,零壓縮部分將被還原,ipAddr 變為 2002:97b:e7aa:0:0:0:97b:e7aa。

  如何獲取本機 IPv6 地址

  有時為了能夠注冊 listener,開發人員需要使用本機的 IPv6 地址,這一地址不能簡單得通過 InetAddress.getLocalhost() 獲得。因為這樣有可能獲得諸如 0:0:0:0:0:0:0:1 這樣的特殊地址。使用這樣的地址,其他服務器將無法把通知發送到本機上,因此必須先進行過濾,選出確實可用的地址。以下代碼實現了這一功能,思路是遍歷網絡接口的各個地址,直至找到符合要求的地址。

  清單 3. 獲取本機 IP 地址


  public static String getLocalIPv6Address() throws IOException { 
  InetAddress inetAddress = null; 
  Enumeration networkInterfaces = NetworkInterface 
  .getNetworkInterfaces(); 
  outer: 
  while (networkInterfaces.hasMoreElements()) { 
  Enumeration inetAds = networkInterfaces.nextElement() 
  .getInetAddresses(); 
  while (inetAds.hasMoreElements()) { 
  inetAddress = inetAds.nextElement(); 
  //Check if it's ipv6 address and reserved address 
  if (inetAddress instanceof Inet6Address 
  && !isReservedAddr(inetAddress)) { 
  break outer; 
  } 
  } 
  } 
  String ipAddr = inetAddress.getHostAddress(); 
  // Filter network card No 
  int index = ipAddr.indexOf('%'); 
  if (index > 0) { 
  ipAddr = ipAddr.substring(0, index); 
  } 
  return ipAddr; 
  } 
  /** 
  * Check if it's "local address" or "link local address" or 
  * "loopbackaddress" 
  * 
  * @param ip address 
  * 
  * @return result 
  */ 
  private static boolean isReservedAddr(InetAddress inetAddr) { 
  if (inetAddr.isAnyLocalAddress() || inetAddr.isLinkLocalAddress() 
  || inetAddr.isLoopbackAddress()) { 
  return true; 
  } 
  return false; 
  }

  為了支持 IPv6,Java 中增加了兩個 InetAddress 的子類:Inet4Address 和 Inet6Address。一般情況下這兩個子類並不會被使用到,但是當我們需要分別處理不同的 IP 協議時就非常有用,在這我們根據 Inet6Address 來篩選地址。

  isReservedAddr() 方法過濾了本機特殊 IP 地址,包括“LocalAddress”,“LinkLocalAddress”和“LoopbackAddress”。讀者可根據自己的需要修改過濾標准。

  另一個需要注意的地方是:在 Windows 平台上,取得的 IPv6 地址後面可能跟了一個百分號加數字。這裡的數字是本機網絡適配器的編號。這個後綴並不是 IPv6 標准地址的一部分,可以去除。

  IPv4/IPv6 雙環境下,網絡的選擇和測試

  我們先看一下筆者所在的 IPv4/IPv6 開發測試環境及其配置方法。

  筆者所處的 IPv4/IPv6 雙環境是一個典型的“6to4”雙棧網絡,其中存在著一個 IPv6 到 IPv4 的映射機制,即任意一個 IPv6 地址 2002:92a:8f7a:100:a:b:c:d 在路由時會被默認映射為 IPv4 地址 a.b.c.d,所以路由表只有一套。

  在此環境內,IPv4 地址與 IPv6 地址的一一對應是人工保證的。如果一台客戶機使用不匹配的 IPv4 和 IPv6 雙地址,或者同時使用 DHCPv4 和 DHCPv6(可能會導致 IPv4 地址和 IPv6 地址不匹配),會導致 IPv6 的路由尋址失敗。

  正因為如此,為了配置雙地址環境,我們一般使用 DHCPv4 來自動獲取 IPv4 地址,然後人工配置相對應的 IPv6 地址。

  Windows 系統

  Windows 2000 及以下:不支持 IPv6

  Windows 2003 和 Windows XP:使用 Windows 自帶的 netsh 命令行方式添加 IPv6 地址以及 DNS, 例如:C:\>netsh interface ipv6 add address “Local Area Connection” 2002:92a:8f7a:100:10:13:1:2 和 C:\>netsh interface ipv6 add dns “Local Area Connection” 2002:92a:8f7a:100:10::250

  Windows 2008 和 Windows Vista:既可以使用 Windows 網絡屬性頁面進行配置,也可以使用類似 Windows 2003 和 Windows XP 的 netsh 命令行來配置

  Linux 系統 (以下是 IPv6 的臨時配置方法,即不修改配置文件,計算機重啟後配置失效)

  Redhat Linux:最簡單的方法是使用 ifconfig 命令行添加 IPv6 地址,例如:ifconfig eth0 inet6 add 2002:92a:8f7a:100:10:14:24:106/96。

  SUSE Linux:同上。

  從實踐上講,由於 Java 的面向對象特性,以及 Java.Net 包對於 IP 地址的良好封裝,從而使得將 Java 應用從 IPv4 環境移植到 IPv4/IPv6 雙環境,或者純 IPv6 環境變得異常簡單。通常我們需要做的僅是檢查代碼並移除明碼編寫的 IPv4 地址,用主機名來替代則可。

  除此以外,對於一些特殊的需求,Java 還提供了 InetAddress 的兩個擴展類以供使用:Inet4Address 和 Inet6Address,其中封裝了對於 IPv4 和 IPv6 的特殊屬性和行為。然而由於 Java 的多態特性,使得程序員一般只需要使用父類 InetAddress,Java 虛擬機可以根據所封裝的 IP 地址類型的不同,在運行時選擇正確的行為邏輯。所以在多數情況下,程序員並不需要精確控制所使用的類型及其行為,一切交給 Java 虛擬機即可。

  具體的新增類型及其新增方法,請具體參閱 Sun 公司的 JavaDoc。

  另外,在 IPv4/IPv6 雙環境中,對於使用 Java 開發的網絡應用,比較值得注意的是以下兩個 IPv6 相關的 Java 虛擬機系統屬性。


Java.Net.preferIPv4Stack=<true|false> 
Java.Net.preferIPv6Addresses=<true|false> 

  preferIPv4Stack(默認 false)表示如果存在 IPv4 和 IPv6 雙棧,Java 程序是否優先使用 IPv4 套接字。默認值是優先使用 IPv6 套接字,因為 IPv6 套接字可以與對應的 IPv4 或 IPv6 主機進行對話;相反如果優先使用 IPv4,則只不能與 IPv6 主機進行通信。

  preferIPv6Addresses(默認 false)表示在查詢本地或遠端 IP 地址時,如果存在 IPv4 和 IPv6 雙地址,Java 程序是否優先返回 IPv6 地址。Java 默認返回 IPv4 地址主要是為了向後兼容,以支持舊有的 IPv4 驗證邏輯,以及舊有的僅支持 IPv4 地址的服務。

  總結

  從計算機技術的發展、因特網的規律和網絡的傳輸速率來看,IPV4 都已經不適用了。其中最主要的問題就是 IPV4 的 32 比特的 IP 地址空間已經無法滿足迅速膨脹的因特網規模,但是 IPv6 的引入為我們解決了 IP 地址近乎枯竭的問題。本文對 IPv6 地址做了一些基本的介紹,著重介紹了如何使用 Java 開發兼容 IPv6 的網絡應用程序,包括如何驗證 IPv6 地址,如何正規化 IPv6 地址的表示,如何獲取本機 IPv6 的地址,以及在 IPv4/IPv6 雙地址環境下的網絡選擇和測試,同時作者結合在日常工作中使用的 Java 代碼片段,希望呈現給讀者一個全方位的、具有較強實用性的文本介紹,也希望本文能給讀者在以後使用 Java 開發 IPv6 兼容程序的過程中帶來一些幫助。


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved