ipv4ipv6:让 RMI 程序同时支持 IPv4 和 IPv6

="atitle">IPv6 介绍

IPv4 自发布以来得到广泛认可和应用经受住了互联网从小型发展到如今全球规模考验实战证明它是健壮易于实现并具有很好互操作性但是 IPv4 协议设计仍有些未考虑到地方随着 Internet 飞速发展和新型应用不断涌现这些不足逐渐显露出来

首先近年来 Internet 成指数级数增长而只有 32 位地址 IPv4 引起了迫在眉睫 IP 地址空间耗尽问题;第 2IPv4 路由结构较为扁平使得 Internet 上骨干路由器需要维护庞大路由表;第 3目前 IPv4 实现方案中多数情况需要手工配置或者使用 DHCP 有状态方式配置协议随着越来越多节点要求接入网络需要种简便配置方式;第 4在 IP 级安全方面IPv4 并不强制使用 IPSec ;最后IPv4 协议中使用服务类型 TOS 字段来支持实时通信流传送而 TOS 功能有限因此对实时数据传输 Qos 支持不是很理想

为解决上述问题及其它相关问题互联网工程任务组织 IETF 开发了套新协议和标准即 IP 版本 6(IPv6)它吸纳了很多用于更新 IPv4 新思想在设计时力求对上下层协议造成最小影响和 IPv4 相比IPv6 协议具有以下些新特性:

  1. IPv6 协议头采用了固定长度头部路由器在处理 IPv6 报头时效率会更高
  2. IPv6 具有 128 位巨大地址空间既便为当前所有主机都分配个 IPv6 地址IPv6 仍然有充足地址供以后使用
  3. IPv6 具有即插即用特性 IPv6 引入了无状态地址自动配置方式链路上主机根据路由公告和自身链路地址可以自动生产个 IPv6 地址从而简化了入网主机配置过程实现了 IPv6 即插即用
  4. 提供网络层认证和加密 IPv6 支持 IPSec这为网络安全提供了种基于标准解决方案
  5. IPv6 更好地支持 QoS IPv6 协议头部包含流标签字段使得路由器可以对属于个流数据包进行识别和提供特殊处理;用业务流分类字段来区分通信流优先级因此 IPv6 对 QoS 提供了更好支持
  6. IPv6 更好地支持移动性虽然 IPv4 也有移动特性但是作为 IPv4 扩展实现受到体系结构和连通性限制而 IPv6 移动特性是内置具有较少局限性并具有更强可伸缩性和健壮性可满足将来 Internet 通信需求
  7. IPv6 具有很好可扩展性这可通过在 IPv6 协议头的后添加新扩展协议头实现
="atitle">Java 对 IPv6 支持

Java 从 1.4 开始已经提供了对 IPv6 支持 Java APIs 遵循了如下 IPv6 标准:

  • RFC2373: IPv6 Addressing Architecture
  • RFC2553: Basic Socket Interface Extensions for IPv6
  • RFC2732: Format for Literal IPv6 Address in URL
但是由于安全等原因Java 并没有支持原始套接字除此的外些 IPv6 特性诸如隧道自动配置移动 IP 等Java 都没有提供支持和 C/C 对 IPv6 支持区别Java 对 IPv6 支持是自动和透明也就是说现有 Java 不需要经过修改就可以直接支持 IPv6 以下面代码为例这段代码在 IPv4 上可以正常运行同样也可以工作在 IPv6 上

InetAddress ip = InetAddress.getByName("java.sun.com"); Socket s = Socket(ip, 80);





Java 对 IPv6 支持体现在其 JDK 对 IPv6 支持上当然前提条件是操作系统需要提供对 IPv6 支持以下操作系统已经提供了对 IPv6 支持

表 1. OS 对 IPv6 支持 表 2. JDK 对 IPv6 支持
JDK/OS Windows Linux AIX Solaris HPUX ZOS

Windows:

  • Windows 2000
  • Windows XP
  • Windows NT
  • Windows 2003 server
Linux:

  • Linux kernel 2.2 及以上版本
Unix:

  • AIX 4.3 及以上版本
  • Solaris 8 及以上版本
  • HP-UX 11i 及以上版本
  • BSD/OS 4.0 及以上版本
  • True64 Unix 4.0D 及以上版本
  • FreeBSD 4.0 及以上版本
  • NetBSD
  • OpenBSD 2.7 及以上版本
Other OS:

  • OS/390, Z/OS V1R4 及以上版本
  • OS400 V5R2 及以上版本
  • Mac OS X
下面列出了各个版本 JDK 对 IPv6 支持情况

="atitle">RMI 对 IPv6 支持

既然 Java 对 IPv6 支持是透明那么 RMI 理论上就应该同时支持 IPv4 和 IPv6但测试结果告诉我们只有在 RMI 服务器端套接字不绑定 IP 地址情况下这种结论才成立

考虑下面这样个例子个支持双栈服务器同时配置了 IPv4 地址和 IPv6 地址服务器应用用如下代码创建了个服务器套接字等待客户端连接由于 Java 对 IPv6 透明支持IPv4 和 IPv6 客户端都可以正常连接到这台服务器上


清单 1. 无 IP 绑定服务器套接字
try { port = 2000; ServerSocket srv = ServerSocket(port); Socket = srv.accept; } catch (IOException e) { e.prStackTrace; }





通过natestat – na可以看出服务器监听在 0.0.0.0:2000 上这样任何客户端都可以连接到服务器上其中包括 IPv6 客户端用服务器 IPv6 地址也能顺利连接

Proto Local Address Foreign Address State TCP 0.0.0.0:2000 0.0.0.0:0 LISTENING





但是很多时候应用从安全等角度考虑常常需要将服务器套接字绑定在某个具体 IP 上现在我们假设把服务器套接字绑定在个 IPv4 地址上如下所示:


清单 2. 绑定 IP 服务器套接字
try { port = 2000; ServerSocket srv = ServerSocket(port); srv.bind( InetSocketAddress( “ 9.181.27.34 ” , port) Socket = srv.accept; } catch (IOException e) { e.prStackTrace; }





通过natestat – na我们可以发现监听方式已改变:

Proto Local Address Foreign Address State TCP 9.181.27.34:2000 0.0.0.0:0 LISTENING





从套接字定义看出它由 IP 和端口组成它们唯确定了个套接字同时也限定了访问套接字方式在访问由固定 IP 和端口组成套接字时客户端必须指定服务器 IP 和端口才能正常连接在服务器绑定 IPv4 地址情况下IPv6 客户端就无法用服务器 IPv6 地址进行访问当然 IPv4 客户端能通过服务器 IPv4 地址 9.181.27.34 进行连接访问

上面分析了 Java 对 IPv6 支持以及服务器套接字如何影响客户端连接接下来我们用例子分析 RMI 对 IPv6 支持我们搭建了如下实验环境:


图 1. 实验环境


接下来我们设计了个基本 RMI 应用下面这段是 RMI 服务器:


清单 3. RMI 服务器



view plaincopy to clipboardpr?

    ="dp-c">
  1. ="alt">importjava.rmi.*;
  2. ="">importjava.rmi.server.*;
  3. ="alt">importjava.rmi.registry.*;
  4. ="">
  5. ="alt">="keyword">public="keyword">SampleServerImplextendsUnicastRemoteObject
  6. ="">implementsSampleServer
  7. ="alt">{
  8. ="">SampleServerImplthrowsRemoteException
  9. ="alt">{
  10. ="">super;
  11. ="alt">}
  12. ="">
  13. ="alt">="keyword">public="keyword">sum(="keyword">a,="keyword">b)throwsRemoteException
  14. ="">{
  15. ="alt">="keyword">a+b;
  16. ="">}
  17. ="alt">
  18. ="">="keyword">public="keyword">="keyword">void(Stringargs)
  19. ="alt">{
  20. ="">="keyword">try
  21. ="alt">{
  22. ="">="comment">//createalocalinstanceoftheobject
  23. ="alt">SampleServerImplServer=="keyword">SampleServerImpl;
  24. ="">
  25. ="alt">="comment">//putthelocalinstanceheregistry
  26. ="">Naming.rebind(="">"rmi://9.181.27.34/SAMPLE-SERVER",Server);
  27. ="alt">
  28. ="">.="keyword">out.prln(="">"Serverwaiting.....");
  29. ="alt">}
  30. ="">="keyword">catch(java.net.MalformedURLExceptionme)
  31. ="alt">{
  32. ="">.="keyword">out.prln(="">"MalformedURL:"+me.toString);
  33. ="alt">}
  34. ="">
  35. ="alt">="keyword">catch(RemoteExceptionre)
  36. ="">{
  37. ="alt">.="keyword">out.prln(="">"Remoteexception:"+re.toString);
  38. ="">}
  39. ="alt">
  40. ="">}
  41. ="alt">}



编译源启动rmiregistry然后运行 RMI 服务器通过netstat – na我们可以看见RMI 监听方式如下尽管我们在中用了” ="boldcode">Naming.rebind("rmi://9.181.27.34/SAMPLE-SERVER" , Server) ”:

Proto Local Address Foreign Address State TCP 0.0.0.0:1099 0.0.0.0:0 LISTENING



那么根据我们上面有关套接字对客户端连接影响分析我们可以看出在这种情况下IPv4 RMI 客户端和 IPv6 RMI 客户端都应该能够顺利连接 RMI 服务器下面我们就看下 IPv4 客户端连接:


清单 4. RMI 客户端



view plaincopy to clipboardpr?

    ="dp-c">
  1. ="alt">importjava.rmi.*;
  2. ="">importjava.rmi.server.*;
  3. ="alt">
  4. ="">="keyword">public="keyword">SampleClient
  5. ="alt">{
  6. ="">="keyword">public="keyword">="keyword">void(Stringargs)
  7. ="alt">{
  8. ="">="comment">//gettheremoteobjectfromtheregistry
  9. ="alt">="keyword">try
  10. ="">{
  11. ="alt">="comment">//usingRMIserver’sIPv4addresstoconnect
  12. ="">Stringurl=="">"//9.181.27.34/SAMPLE-SERVER";
  13. ="alt">
  14. ="">SampleServerremoteObject=(SampleServer)Naming.lookup(url);
  15. ="alt">.="keyword">out.prln(="">"Gotremoteobject");
  16. ="">
  17. ="alt">.="keyword">out.prln(="">"1+2="+remoteObject.sum(1,2));
  18. ="">}
  19. ="alt">="keyword">catch(RemoteExceptionexc){
  20. ="">.="keyword">out.prln(="">"Errorinlookup:"+exc.toString);}
  21. ="alt">="keyword">catch(java.net.MalformedURLExceptionexc){
  22. ="">.="keyword">out.prln(="">"MalformedURL:"+exc.toString);}
  23. ="alt">="keyword">catch(java.rmi.NotBoundExceptionexc){
  24. ="">.="keyword">out .prln(="">"NotBound:"+exc.toString);
  25. ="alt">}
  26. ="">
  27. ="alt">}
  28. ="">}



编译并运行该可以看到正常连接到 RMI 服务器运行结果如下:

Got remote object 1 + 2 = 3





下面我们在 IPv4 RMI 客户端基础上作下改动用 RMI 服务器 IPv6 地址进行连接修改如下:

String url = "//2001:251:1a05::6/SAMPLE-SERVER"; SampleServer remoteObject = (SampleServer)Naming.lookup(url);





编译修改的后在 IPv6 RMI 客户端上运行同样可以正常连接 RMI 服务器运行结果如下:

Got remote object 1 + 2 = 3





通过这个例子我们可以看出基本 RMI 对 IPv6 支持是透明它可以同时支持 IPv4 和 IPv6

通过这个例子我们也可以看出它实际上不能绑定 IP 地址这在安全性要求比较高企业级应用中并不是很合适它们通常采用UnicastRemoteObject类exportObject(Remote obj, port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf)思路方法来绑定 RMI IP 地址在RMIServerSocketFactory例子中创建 RMI 服务器套接字并绑定 IP 地址在RMIClientSocketFactory例子中通过服务器绑定 IP 地址进行访问

在 RMI 服务器绑定 IP 地址情况如果让 RMI 同时支持 IPv4 和 IPv6 呢?下面给出了个解决方案既然要让 RMI 同时支持 IPv4 和 IPv6那么在服务器端我们就要同时创建两个套接字个绑定在 IPv4 地址上个绑定在 IPv6 地址上

首先我们应该创建两个类IPv4RMIServerSocket和IPv6RMIServerSocket他们都实现RMIServerSocketFactory接口IPv4RMIServerSocket创建个服务器套接字并绑定在 RMI 服务器 IPv4 地址上;IPv6RMIServerSocket创建个服务器套接字并绑定在 RMI 服务器 IPv6 地址上

其次在创建两个类IPv4RMIClientSocket和IPv6RMIClientSocket他们都实现RMIClientSocketFactory接口IPv4RMIClientSocket创建个客户端套接字并通过 RMI 服务器 IPv4 地址进行连接;IPv6RMIClientSocket创建个客户端套接字并通过 RMI 服务器 IPv6 地址进行连接

然后在创建好我们需要服务器和客户端类的后服务器应用需要两次exportObject思路方法将远程对象导出但是有个问题出现了个远程对象不能同时导出两次如何解决这个问题呢?办法就是我们需要对远程对象作个wrapper现在假设有个远程对象Kernel类定义如下:


清单 5. Kernel 类定义



view plaincopy to clipboardpr?

    ="dp-c">
  1. ="alt">="keyword">public="keyword">KernelextendsRemote{
  2. ="">
  3. ="alt">="keyword">public="keyword">voidaddWebServer(StringhostName,="keyword">port)throwsRemoteException{
  4. ="">="comment">//Functionimplementationcode
  5. ="alt">}
  6. ="">
  7. ="alt">="keyword">public="keyword">voidchangeLogLevel(="keyword">level)throwsRemoteException{
  8. ="">="comment">//Functionimplementationcode
  9. ="alt">}
  10. ="">}



Kernel Wrapper 定义如下:


清单 6. Kernel Wrapper 类定义



view plaincopy to clipboardpr?

    ="dp-c">
  1. ="alt">="keyword">public="keyword">KernelWrapperextendsRemote{
  2. ="">transientKernelkernel_;
  3. ="alt">="keyword">publicKernelWrapper(Kernelkernel)throwsRemoteException,IOException{
  4. ="">super;
  5. ="alt">kernel_=kernel;
  6. ="">}
  7. ="alt">="keyword">public="keyword">voidaddWebServer(StringhostName,="keyword">port)throwsRemoteException{
  8. ="">kernel_.addWebServer(hostName,port);
  9. ="alt">}
  10. ="">="keyword">public="keyword">voidchangeLogLevel(="keyword">level)throwsRemoteException{
  11. ="alt">kernel_.changeLogLevel(level);
  12. ="">}
  13. ="alt">}



在应用时候例子化个 Kernel 例子并将它作为参数例子化两个 KernelWrapper 例子如下所示:


清单 7. KernelWrapper 例子化
kernelObj = Kernel; //remote kernel object for IPv4 clients ipv4kernelObj = KernelWrapper (kernelObj); //remote kernel object for IPv6 clients ipv6kernelObj = KernelWrapper (kernelObj);





最后应用需要将 ipv4kernelObj 和 ipv6kernelObj 远程对象导出如下所示:


清单 8. 远程对象导出
//export remote object for IPv4 client UnicastRemoteObject.exportObject( ipv4kernelObj, 1099, IPv4RMIClientSocket, IPv4RMIServerSocket ) //export remote object for IPv6 client UnicastRemoteObject.exportObject( ipv6kernelObj, 1099, IPv6RMIClientSocket, IPv6RMIServerSocket )





这样 IPv4 客户端通过服务器 IPv4 地址进行访问而 IPv6 客户端通过服务器 IPv6 地址进行访问从而成功使得 RMI 服务器在绑定 IP 地址情况下同时支持 IPv4 和 IPv6

="atitle">结束语

本文在分析服务器套接字对 IPv4 和 IPv6 客户端影响基础上介绍了两种区别 RMI 应用对 IPv6 支持情况同时给出了种 RMI 服务器在需要绑定 IP 地址情况下如何同时支持 IPv4 和 IPv6 客户端解决方案

Tags:  ipv4访问ipv6网站 ipv6访问ipv4 ipv4与ipv6 ipv4ipv6

延伸阅读

最新评论

发表评论