如何让 RMI 程序同时支持 IPv4 和 IPv6

本文探讨了IPv6的特性和Java对其的支持,并深入分析了RMI在IPv4和IPv6环境下的表现,提出了解决方案。

IPv6 简介

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

首先,近年来 Internet 成指数级数增长,而只有 32 位地址的 IPv4 引起了迫在眉睫的 IP 地址空间耗尽的问题;第二,IPv4 路由结构较为扁平,使得 Internet 上骨干路由器需要维护庞大的路由表;第三,目前 IPv4 实现方案中,多数情况需要手工配置或者使用 DHCP 有状态方式配置协议,随着越来越多的节点要求接入网络,需要一种简便的配置方式;第四,在 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 协议头之后添加新的扩展协议头实现。

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 = new Socket(ip, 80);

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

表 1. OS 对 IPv6 的支持 表 2. JDK 对 IPv6 的支持

JDK/OSWindowsLinuxAIXSolarisHPUXZOS
IBM JDK 1.4NOYESYES  NO
IBM JDK 1.5YESYESYES  YES
SUN JDK 1.4NOYES YES  
SUN JDK 1.5YESYES YES  
JRockit JDK 1.4NOYES    
JRockit JDK 1.5YESYES    
HP-UX JDK 1.4    YES 
HP-UX JDK 1.5    YES 

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 的支持情况。

 

 

RMI 对 IPv6 的支持

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

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


清单 1. 无 IP 绑定的服务器套接字程序

				
 try { 
 int port = 2000; 
     ServerSocket srv = new ServerSocket(port); 
     Socket socket = srv.accept(); 
 } catch (IOException e) { 
     e.printStackTrace(); 
 }

通过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 { 
 int port = 2000; 
 ServerSocket srv = new ServerSocket(port); 
      srv.bind(new InetSocketAddress( “ 9.181.27.34 ” , port) 
      Socket socket = srv.accept(); 
 } catch (IOException e) { 
      e.printStackTrace(); 
 }

通过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 服务器程序

编译源程序,启动rmiregistry,然后运行 RMI 服务器程序。通过netstat – na我们可以看见,RMI 监听方式如下,尽管我们在程序中用了” 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 客户端程序

编译并运行该程序,可以看到正常连接到 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, int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf)方法来绑定 RMI IP 地址。在RMIServerSocketFactory实例中,创建 RMI 服务器套接字并绑定 IP 地址,在RMIClientSocketFactory实例中通过服务器绑定的 IP 地址进行访问。

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

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

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

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


清单 5. Kernel 类定义

Kernel 的 Wrapper 定义如下:


清单 6. Kernel 的 Wrapper 类定义

在应用程序初始化的时候,实例化一个 Kernel 实例,并将它作为参数实例化两个 KernelWrapper 实例,如下所示:


清单 7. KernelWrapper 实例化

				
 kernelObj = new Kernel(); 
 //remote kernel object for IPv4 clients 
 ipv4kernelObj = new KernelWrapper (kernelObj); 
 //remote kernel object for IPv6 clients 
 ipv6kernelObj = new 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 。

 

结束语

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

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值