乐趣区

关于ipv6:Java对IPv6的支持详解支持情况相关API演示代码等

本文由朱益盛、杨晖、傅啸分享,来自 IBM Developer 社区,原题“应用 Java 开发兼容 IPv6 的网络应用程序”,本次收录时有改变。

1、引言

前几天,有个群友跟我探讨用 MobileIMSDK 写的 IM 服务端想反对 IPv6 的问题。因为众所周之的起因,IPv4 早就不够用,当初国内从国家层面都在大力推广 IPv6 的遍及,所以包含事业单位、国企在内,当初搞信息化倡议,都要思考 IPv6 的反对。

我突然感觉这个问题很难答复,因为对于一般的网络通信程序开发者来说,目前真正的 IPv6 的开发和测试环境并不容易失去,所以想要真正说分明 Java 对于 IPv6 地反对状况,只能借助系统的材料和网贴,可能并不残缺和筹备。

实践上,Java 对 IPv6 的反对对于程序员来说都是通明的,简直不须要代码层面的解决。但它到底是怎么反对的?反对到什么水平?对 JDK 版本有什么要求?对操作系统有什么要求?等等,我认为还是有必要具体钻研理解一下。

本文将用通俗易懂的文字,来解说 Java 对 IPv6 的反对现状,包含关的技术原理、能够应用的 API、以及一些能够运行的演示代码片段等,心愿能让你更直观的理解 Java 对于 IPv6 的反对状况。

浏览提醒:限于篇幅,本文假如你已理解 IPv6 技术是什么,如您对它无所不知,倡议先浏览文言式入门文章:《一文读懂什么是 IPv6》。

(本文同步公布于:http://www.52im.net/thread-3236-1-1.html)

2、举荐材料

《IPv6 技术详解:基本概念、利用现状、技术实际(上篇)》
《IPv6 技术详解:基本概念、利用现状、技术实际(下篇)》

3、技术背景

目前咱们应用的是第二代互联网 IPv4 技术,它的最大问题是网络地址资源无限,从实践上讲,能够编址 1600 万个网络、40 亿台主机。但采纳 A、B、C 三类编址形式后,可用的网络地址和主机地址的数目大打折扣,以至目前的 IP 地址近乎枯竭。网络地址有余,重大地制约了寰球互联网的利用和倒退。

▲ 本图援用自《网络编程懒人入门(十一):一文读懂什么是 IPv6》

一方面是地址资源数量的限度,另一方面是随着电子技术及网络技术的倒退,计算机网络将进入人们的日常生活,可能身边的每一样货色都须要连入寰球因特网。在这种网络空间匮乏的环境下,IPv6 应运而生。它的产生岂但解决了网络地址资源数量的问题,同时也为除电脑外的设施连入互联网在数量限度上扫清了阻碍。

如果说 IPv4 实现的只是人机对话,那么 IPv6 则扩大到任意事物之间的对话,它不仅能够为人类服务,还将服务于泛滥硬件设施,如家用电器、传感器、近程照相机、汽车等,它将是无时不在,无处不在的深刻社会每个角落的真正的宽带网,它所带来的经济效益也将十分微小。

当然,IPv6 并非美中不足、一劳永逸,不可能解决所有问题。IPv6 只能在倒退中不断完善,也不可能在一夜之间产生,过渡须要工夫和老本,但从长远看,IPv6 有利于互联网的继续和短暂倒退。目前,国内互联网组织曾经决定成立两个专门工作组,制订相应的国际标准。

4、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 – 分隔栈构造:

5、如何验证 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));
    }}

6、如何正规化 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。

7、如何获取本机 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 规范地址的一部分,能够去除。

8、IPv4/IPv6 双环境下,网络的抉择和测试

咱们先看一下笔者所在的 IPv4/IPv6 开发测试环境及其配置办法。

笔者所处的 IPv4/IPv6 双环境是一个典型的”6to4”双栈网络,其中存在着一个 IPv6 到 IPv4 的映射机制,即任意一个 IPv6 地址 2002:92a:8f7aac: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:8f7a10:13:1:2 和 C:>netsh interface ipv6 add dns“Local Area Connection”2002:92a:8f7a10::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:8f7a10: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 地址的服务。

9、写在最初

本文对 IPv6 地址做了一些根本的介绍,着重介绍了如何应用 Java 开发兼容 IPv6 的网络应用程序,包含如何验证 IPv6 地址,如何正规化 IPv6 地址的示意,如何获取本机 IPv6 的地址,以及在 IPv4/IPv6 双地址环境下的网络抉择和测试。

同时作者联合在日常工作中应用的 Java 代码片段,心愿出现给读者一个全方位的、具备较强实用性的文本介绍,也心愿本文能给读者在当前应用 Java 开发 IPv6 兼容程序的过程中带来一些帮忙。

10、参考资料

[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

退出移动版