`

Java Rmi Specification -- Chapter 3 RMI System Overview(RMI系统概述)

    博客分类:
  • RMI
阅读更多

原文地址:

 

http://download.oracle.com/javase/1.5.0/docs/guide/rmi/spec/rmi-arch.html

 

3.1 Stubs and Skeletons

 

RMI在远程对象的stubs和skeletons对象之间使用标准的通信机制。远程对象的stub对象就像是远程对象在本地的代理。调用者调用本地stub对象的方法,然后由stub来进行对远程对象的方法调用。在RMI中,stub实现了跟远程对象一样的远程接口集。

当调用stub的方法,stub将执行如下的工作:

 

  • 初始化一个到包含远程对象的远程JVM的连接,
  • 排列marshals(写入并且传送)参数到远程JVM,
  • 等待方法调用返回的结果,
  • 反排列unmarshals(读)放回的结果值或者异常
  • 返回结果值给调用者
stub隐藏了参数的序列化和网络级别的通信,这样给调用者呈现一种简单的方法调用机制。

在远程JVM,每个远程对象可能都有对象的skeleton()。skeleton负责分派调用到实际的远程对象实现上。当skeleton接收到一个方法调用的时候,skeleton将执行如下的工作:
  • 反排列(读)参数,
  • 调用实际远程对象上的方法
  • 排列(写并且传输)结果(方法结果或者异常)
在Java 2 SDK, Standard Edition, v1.2,引入一个附加的stub协议,这样消除了在Java 2 platform-only 环境中对skeleton的需要。因此,普通的代码就能承担在JDK1.1种skeleton的职责。stub和skeleton由rmic编译器生成。

3.2 远程方法调用中的线程机制

调派到远程对象的方法可能在单独的线程中执行,或者不是。RMI不能保证映射远程对象的调用到单独线程中。因为远程对象上的远程方法可能被并发执行,所以远程对象的实现必须保证线程安全。

3.3 远程对象的垃圾回收

在分布式的系统中,也应该跟本地系统一样自动回收不再被客户端引用的远程对象。这样程序员就不用费事跟踪远程对
象的客户端。RMI使用了类似Modula-3的网络对象的引用计数的垃圾回收算法。(See "Network Objects" by Birrell, Nelson, and Owicki, Digital Equipment Corporation Systems Research Center Technical Report 115, 1994.)
RMI保持对所有在每个JVM中的活动引用,以此来实现引用计数的垃圾回收。当一个活动引用进入JVM,那么它的引用计数就增加一。对象的第一个引用发送“referenced”信息给服务器。当一个活动引用不在对对象进行引用,那么计数将减一。当最后的引用失效后,将会给服务器发送一个"unreferenced"信息。这种垃圾回收机制中存在很多微妙之处;很多的设计被用来维护引用和去引用信息的顺序,这样对象就不会被提前回收。
当一个远程对象不在被任何一个客户端引用,RMI把这个远程对象表示为弱引用。弱引用允许JVM的垃圾回收机制回收这个对象,当这个对象没有任何本地引用。分布式系统的垃圾回收算法跟本地JVM的垃圾回收进行交互,一起维护对象的正常引用和弱引用。
只要远程对象的本地引用还存在,它就不能被垃圾回收,并且它能被远程调用或者返回给客户端。传递一个远程对象将在JVM的引用集中添加一个引用。一个远程对象必须实现java.rmi.server.Unreferenced接口来实现unreferenced通告功能。当引用都失效后,unreferenced方法将被调用。unreferenced方法可能被不止一次调用,因为引用集为空就会被调用。远程对象只有在既没有本地也没有远程引用的时候,才被回收。
注意如果客户端和服务器之前存在网络故障(a network partition),那么远程对象可能将被提前回收(因为传输中可能相信客户端已经崩溃了)。因为有提前回收的可能性,那么远程对象的引用不能保证引用的完整性;换句话说,很有可能一个远程对象的引用已经失效。如果使用这样的一个引用那么会产生一个RemoteException异常。

3.4 动态类加载

RMI调用中的参数,返回值,异常可以是任何序列化的对象。RMI使用对象序列化机制传输数据从一个JVM到另外一个,并且使用相应的定位信息注释调用流这样在RMI的客户端能够加载相应的类文件。
当在一个远程方法调用中的参数和返回值被反排列并成为一个活动对象在接收的JVM侧的时候,在调用流里面的所有对象都需要为其加载类的定义。反排列的进程会首先尝试通过雷鸣在本地的类加载上下文中加载类文件。RMI同样也提从网络上加载类定义的机制,加载类的网络地址由传输的末端数据指定。在反序列侧,会加载调用参数的子类(没有本地定义),这其中包括下载对应远程对象具体实现类的stub类对象。
为了支持动态类的加载,RMI在排列和反排列参数和返回值的流里面使用了java.io.ObjectOutputStream和java.io.ObjectInputStream类。这些子类分别重载了ObjectOutputStream的annotateClass方法和ObjectInputStream的resolveClass方法,以此来确定在流里面声明的类的类定义文件的位置。
调用java.rmi.server.RMIClassLoader.getClassAnnotation方法会返回null,或者是表示类代码URL的字符串,远处的客户端可以通过这个URL加载到具体类的定义。
resolveClass从RMI的排列流读取一个单一的对象。如果对象是一个字符串(并且java.rmi.server.useCodebaseOnly property不是true),那么resolveClass通过将annotated字符串对象作为第一个参数,需要的类的名称作为第二个参数调用RMIClassLoader.loadClass方法,并返回其结果。否则的话,resolveClass通过将需要的类的名称作为唯一参数调用RMIClassLoader.loadClass方法,并返回其结果。
更为详细的关于RMI中类加载的章节请见“The RMIClassLoader Class"”

3.5 RMI使用代理传越防火墙
RMI传输层正常的情况下会打开到网络上主机的socket。但是在很多的网络中的防火墙禁止这样的行为。所以,默认的RMI
传输会提供第二种基于http的机制使得防火墙内的客户端能够调用防火墙外面的远程对象的方法。
传输层这种应用RMI方法调用的基于http的机制,只适用于防火墙中带http代理服务的情况。

3.5.1 RMI调用如何在HTTP协议中被打包

为了穿过防火墙,传输层把RMI的调用嵌入到防火墙相信的HTTP协议当中。RMI方法调用的数据被作为HTTP POST的主体被发送,并且在HTTP的响应的主体中获取返回信息。传输层将使用以下两种方式中的一种生成POST请求:
  1. 如果防火墙代理可以提交HTTP的请求直接到主机上的任意端口,那么它将把该请求直接提交到RMI服务监听的端口上。目标机器上默认的RMI传输层有个服务socket,可以接收和解析封装在POST请求中RMI调用;
  2. 如果防火墙代理只能提交HTTP请求到一个指定的HTTP端口上,那么调用被提交到HTTP服务的监听端口上,然后由一个CGI脚本去提交调用到RMI服务指定的端口上。
3.5.2 默认scoket工厂

RMI传输实现包括了java.rmi.server.RMISocketFactory类的扩展,这个类产生客户端和服务端的socket去接收和发送RMI调用;这个默认的soket工厂可用通过java.rmi.server.RMISocketFactory.getDefaultSocketFactory获得。这个默认的socket工厂创建socket,这些socket提供了防火墙隧道机制:

  • 客户端socket首先尝试建立一个直接的socket连接。当不能建立直接连接的时候(当抛出java.net.NoRouteToHostException 或者java.net.UnknownHostException),客户端socket会自动尝试创建一个到主机的HTTP连接。如果创建直接的socket连接的时候,抛出了任何java.io.IOException类型的异常,例如java.net.ConnectException,那么可能也会尝试创建一个HTTP的连接。
  • 服务端socket自动发现接收到的请求是否是一个HTTP POST请求,如果是,那么返回一个scoket,通过这个socket传输HTTP的主体(实际的RMI调用),最后将方法返回封装在HTTP的响应中。
调用工厂的java.rmi.server.RMISocketFactory.createSocket方法生成一个默认行为的客户端侧的socket。调用工厂的java.rmi.server.RMISocketFactory.createServerSocket方法生成一个默认行为的服务侧的socket。

3.5.3 配置客户端

通过设置java.rmi.server.disableHttp为true,可以禁止客户端将RMI打包成HTTP请求。

3.5.4 配置服务器

注意:不要把主机的IP地址设置为主机名,因为一些防火墙代理不会转发到这样的主机名。
  1. 为了使客户端在服务器所处的域外调用服务器对象的方法,那么客户端必须要能够找到server.为此,服务导出的远程引用必须包含完整的服务主机的名称。
因为服务的平台和网络环境的不同,那么这个信息在服务运行的JVM中不一定是可用的。如果是不用,那么需要通过在启动服务的时候在java.rmi.server.hostname指定主机的完整名称。

举个例子,使用下面的命令在主机chatsubo.javasoft.com上开启RMI服务ServerImpl:

java -Djava.rmi.server.hostname=chatsubo.javasoft.com ServerImpl

2.如果服务不支持防火墙内的RMI客户端转发到任意的端口,使用如下的配置:
  1. 一个HTTP服务,监听端口为80。
  2. 一个CGI脚本
/cgi-bin/java-rmi.cgi

脚本:
  • 调用本地java语言的解释器去执行传输层的一个类,来将请求转发到相应的RMI服务端口上。
  • 像在CGI1.0定义环境变量的方式在JVM定义相同的名称和值。
这个例子脚本可以在Solaris和WIndows32的环境中执行。注意脚本必须指定完整的路径到java语言解释器。

3.5.5 性能问题和限制

通过发送HTTP请求的调用在速度上比直接通过socket连接的方式慢一个数量级,在没有考虑代理转发延
迟的情况下。
因为HTTP请求只能在一个方向被初始化通过防火墙,客户端不能再防火墙之外导出它的远程对象,因为防
火墙外的主机不能初始化一个方法调用返回给客户端。
 
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics