Java對IPv6的支持詳解:支持狀況、相關API、演示代碼等

本文由朱益盛、楊暉、傅嘯分享,來自IBM Developer社區,原題「使用 Java 開發兼容 IPv6 的網絡應用程序」,本次收錄時有改動。html

一、引言

前幾天,有個羣友跟我討論用 MobileIMSDK 寫的IM服務端想支持IPv6的問題。由於衆所周之的緣由,IPv4早就不夠用,如今國內從國家層面都在大力推廣IPv6的普及,因此包括事業單位、國企在內,如今搞信息化建議,都要考慮IPv6的支持。java

我突然感受這個問題很難回答,由於對於普通的網絡通訊程序開發者來講,目前真正的IPv6的開發和測試環境並不容易獲得,因此想要真正說清楚Java對於IPv6地支持狀況,只能藉助零碎的資料和網貼,可能並不完整和準備。程序員

理論上,Java對IPv6的支持對於程序員來講都是透明的,幾乎不須要代碼層面的處理。但它究竟是怎麼支持的?支持到什麼程度?對JDK版本有什麼要求?對操做系統有什麼要求?等等,我認爲仍是有必要詳細研究瞭解一下。面試

本文將用通俗易懂的文字,來說解Java對IPv6的支持現狀,包括關的技術原理、可使用的API、以及一些能夠運行的演示代碼片斷等,但願能讓你更直觀的瞭解Java對於IPv6的支持狀況。正則表達式

閱讀提示:限於篇幅,本文假設你已瞭解IPv6技術是什麼,如您對它一無所知,建議先閱讀白話式入門文章:《一文讀懂什麼是IPv6》。算法

二、推薦資料

IPv6技術詳解:基本概念、應用現狀、技術實踐(上篇)編程

IPv6技術詳解:基本概念、應用現狀、技術實踐(下篇)windows

三、技術背景

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

▲ 本圖引用自《網絡編程懶人入門(十一):一文讀懂什麼是IPv6服務器

一方面是地址資源數量的限制,另外一方面是隨着電子技術及網絡技術的發展,計算機網絡將進入人們的平常生活,可能身邊的每同樣東西都須要連入全球因特網。在這種網絡空間匱乏的環境下,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 - 雙棧結構:

對於分隔棧系統,Java 則建立 IPv4/v6 兩個套接字(如圖 2):

  • 1)若是是 TCP 客戶端程序:一旦其中某個套接字鏈接成功,另外一個套接字就會被關閉,這個套接字鏈接使用的 IP 協議類型也就此被固定下來;
  • 2)若是是 TCP 服務器端程序:由於沒法預期客戶端使用的 IP 協議,因此 IPv4/v6 兩個套接字會被一直保留;
  • 3)對於 UDP 應用程序:不管是客戶端仍是服務器端程序,兩個套接字都會保留來完成通訊。

圖 2 - 分隔棧結構:

五、如何驗證 IPv6 地址

5.1 IPv6 地址表示

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

IPv6地址組成以下圖所示:

▲ 本圖引用自《網絡編程懶人入門(十一):一文讀懂什麼是IPv6

例如:

  • 1)2001:0db8:85a3:08d3:1319:8a2e:0370:7344 是一個合法的 IPv6 地址。若是四個數字都是零,則能夠被省略;
  • 2)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

5.2 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 開始,Java就增長了對 IPv6 網絡地址校驗的支持。程序員能夠經過簡單地調用方法 sun.net.util.IPAddressUtil.isIPv6LiteralAddress() 來驗證一個 String 類型的輸入是不是一個合法的 IPv6 網絡地址。

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

  • 1)因爲 IPv6 協議所容許的網絡地址格式較多,規範較寬鬆(例如零壓縮地址,IPv4 映射地址等),因此致使了 IPv6 網絡地址的格式變化很大;
  • 2)Java 對於 IPv6 網絡地址的驗證是經過對輸入字符的循環匹配作到的,並無採起正則表達式的作法。其匹配過程當中還依賴於其它的 Java 方法;
  • 3)目前網絡上流傳的 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<NetworkInterface> networkInterfaces = NetworkInterface

        .getNetworkInterfaces();

    outer:

    while(networkInterfaces.hasMoreElements()) {

        Enumeration<InetAddress> inetAds = networkInterfaces.nextElement()

        .getInetAddresses();

        while(inetAds.hasMoreElements()) {

            inetAddress = inetAds.nextElement();

            //Check if it's ipv6 address and reserved address

            if(inetAddress instanceofInet6Address

                && !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 系統:

1)Windows 2000 及如下:不支持 IPv6

2)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

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

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

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

2)SUSE Linux:同上。

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

除此之外:對於一些特殊的需求,Java 還提供了 InetAddress 的兩個擴展類以供使用:Inet4Address 和 Inet6Address,其中封裝了對於 IPv4 和 IPv6 的特殊屬性和行爲。

然而因爲 Java 的多態特性,使得程序員通常只須要使用父類 InetAddress,Java 虛擬機能夠根據所封裝的 IP 地址類型的不一樣,在運行時選擇正確的行爲邏輯。因此在多數狀況下,程序員並不須要精確控制所使用的類型及其行爲,一切交給 Java 虛擬機便可。

具體的新增類型及其新增方法,請具體參閱Java的API文檔

另外:在 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 地址的服務。

九、寫在最後

本文對 IPv6 地址作了一些基本的介紹,着重介紹瞭如何使用 Java 開發兼容 IPv6 的網絡應用程序,包括如何驗證 IPv6 地址,如何正規化 IPv6 地址的表示,如何獲取本機 IPv6 的地址,以及在 IPv4/IPv6 雙地址環境下的網絡選擇和測試。

同時做者結合在平常工做中使用的 Java 代碼片斷,但願呈現給讀者一個全方位的、具備較強實用性的文本介紹,也但願本文能給讀者在之後使用 Java 開發 IPv6 兼容程序的過程當中帶來一些幫助。

十、參考資料

[1] IPv6 地址技術架構

[2] IPv6 協議技術文檔

[3] Networking IPv6 User Guide for JDK/JRE 5.0

附錄:相關文章

技術往事:改變世界的TCP/IP協議(珍貴多圖、手機慎點)

通俗易懂-深刻理解TCP協議(上):理論基礎

通俗易懂-深刻理解TCP協議(下):RTT、滑動窗口、擁塞處理

計算機網絡通信協議關係圖(中文珍藏版)

P2P技術詳解(一):NAT詳解——詳細原理、P2P簡介

P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解(基本原理篇)

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)

P2P技術詳解(四):P2P技術之STUN、TURN、ICE詳解

通俗易懂:快速理解P2P技術中的NAT穿透原理

高性能網絡編程(一):單臺服務器併發TCP鏈接數到底能夠有多少

高性能網絡編程(二):上一個10年,著名的C10K併發鏈接問題

高性能網絡編程(三):下一個10年,是時候考慮C10M併發問題了

高性能網絡編程(四):從C10K到C10M高性能網絡應用的理論探索

高性能網絡編程(五):一文讀懂高性能網絡編程中的I/O模型

高性能網絡編程(六):一文讀懂高性能網絡編程中的線程模型

高性能網絡編程(七):到底什麼是高併發?一文即懂!

網絡編程懶人入門(二):快速理解網絡通訊協議(下篇)

網絡編程懶人入門(三):快速理解TCP協議一篇就夠

網絡編程懶人入門(四):快速理解TCP和UDP的差別

網絡編程懶人入門(五):快速理解爲何說UDP有時比TCP更有優點

網絡編程懶人入門(六):史上最通俗的集線器、交換機、路由器功能原理入門

網絡編程懶人入門(七):深刻淺出,全面理解HTTP協議

網絡編程懶人入門(八):手把手教你寫基於TCP的Socket長鏈接

網絡編程懶人入門(九):通俗講解,有了IP地址,爲什麼還要用MAC地址?

網絡編程懶人入門(十):一泡尿的時間,快速讀懂QUIC協議

網絡編程懶人入門(十一):一文讀懂什麼是IPv6

網絡編程懶人入門(十二):快速讀懂Http/3協議,一篇就夠!

腦殘式網絡編程入門(一):跟着動畫來學TCP三次握手和四次揮手

腦殘式網絡編程入門(二):咱們在讀寫Socket時,究竟在讀寫什麼?

腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識

腦殘式網絡編程入門(四):快速理解HTTP/2的服務器推送(Server Push)

腦殘式網絡編程入門(五):天天都在用的Ping命令,它究竟是什麼?

腦殘式網絡編程入門(六):什麼是公網IP和內網IP?NAT轉換又是什麼鬼?

腦殘式網絡編程入門(七):面視必備,史上最通俗計算機網絡分層詳解

腦殘式網絡編程入門(八):你真的瞭解127.0.0.1和0.0.0.0的區別?

腦殘式網絡編程入門(九):面試必考,史上最通俗大小端字節序詳解

可能會搞砸你的面試:你知道一個TCP鏈接上能發起多少個HTTP請求嗎?

5G時代已經到來,TCP/IP老矣,尚能飯否?

本文已同步發佈於「即時通信技術圈」公衆號。

▲ 本文在公衆號上的連接是:點此進入,原文連接是:http://www.52im.net/thread-3236-1-1.html

相關文章
相關標籤/搜索