RMI System Overview
          

        注:主要内容参考官方文档。同时已经有人把翻译过Sun上RMI的lesson,如果想学习技术实例,可以查看,参考资源给出了链接。

3.1 Stubs and Skeletons

RMI uses a standard mechanism (employed in RPC systems) for communicating with remote objects: stubs and
skeletons. A stub for a remote object acts as a client's local representative or proxy for the remote object
. The caller invokes a method on the local stub which is responsible for carrying out the method call on the
 remote object. In RMI, a stub for a remote object implements the same set of remote interfaces that a
remote object implements.

RMI为远程对象的通讯使用一个标准的机制(在RPC系统中使用):stubs和skeletons.一个远程对象的stub为远程对象作为一个客户端本地的代表或者代理.当调用者在本地的stub中调用一个方法,stub负责实现调用远程对象的方法.在RMI中,一个远程对象的stub实现了所有远程对象实现的接口.


When a stub's method is invoked, it does the following:
•    initiates a connection with the remote JVM containing the remote object,
•    marshals (writes and transmits) the parameters to the remote JVM,
•    waits for the result of the method invocation,
•    unmarshals (reads) the return value or exception returned, and
•    returns the value to the caller.


当一个stub的方法被调用时,它遵循下面步骤:
    与包含远程对象的JVM之间创建一个连接.
    传递(写入和发送)参数到远程的JVM.
    等待方法调用的结果
    读取返回值或者异常.
    返回结果给调用者.


The stub hides the serialization of parameters and the network-level communication in order to present a
simple invocation mechanism to the caller.
In the remote JVM, each remote object may have a corresponding skeleton (in Java 2 platform-only
environments, skeletons are not required). The skeleton is responsible for dispatching the call to the
actual remote object implementation. When a skeleton receives an incoming method invocation it does the
following:
•    unmarshals (reads) the parameters for the remote method,
•    invokes the method on the actual remote object implementation, and
•    marshals (writes and transmits) the result (return value or exception) to the caller.

Stub隐藏了参数的序列化和网络级别的通讯,调用者看到的是个简单的调用机制.
在远程的JVM中,每个远程对象可能有一个相同的skeleton(仅仅在java2平台中,skeletons不是必要的).skeleton的职责是分发调用到实际远程对象的实现.一个skeleton接收到一个方法的调用,它执行下面步骤:

   读取这个为调用远程方法传递过来的参数.
   调用实际的远程对象实现的方法.
•   返回结果给调用者(值或者异常)


In the Java 2 SDK, Standard Edition, v1.2 an additional stub protocol was introduced that eliminates the
need for skeletons in Java 2 platform-only environments. Instead, generic code is used to carry out the
duties performed by skeletons in JDK1.1. Stubs and skeletons are generated by the rmic compiler.

在Java2 SE v1.2中附加了一个stub协议介绍:在java2平台环境中,不再需要skeletons. 在JDK1.1中还是通过skeletons来操作,Stubs和skeletons通过rmic编译器生成.



3.2 Thread Usage in Remote Method Invocations

A method dispatched by the RMI runtime to a remote object implementation may or may not execute in a
separate thread. The RMI runtime makes no guarantees with respect to mapping remote object invocations to
threads. Since remote method invocation on the same remote object may execute concurrently, a remote object implementation needs to make sure its implementation is thread-safe.


一个方法由RMI运行期分发给一个远程对象的实现,有可能启用的不是单独的线程.RMI运行期不保准远程方法的调用与线程联系起来,一旦远程方法调用同一个对象可能会并发执行,所以要确保远程对象的实现是线程安全的.



3.3 Garbage Collection of Remote Objects

In a distributed system, just as in the local system, it is desirable to automatically delete those remote
objects that are no longer referenced by any client. This frees the programmer from needing to keep track
of the remote objects' clients so that it can terminate appropriately. RMI uses a reference-counting garbage
 collection algorithm similar to Modula-3's Network Objects. (See "Network Objects" by Birrell, Nelson, and
 Owicki, Digital Equipment Corporation Systems Research Center Technical Report 115, 1994.)

在分布式系统中和本地系统一样,自动删除不再被客户端使用的远程对象是合符要求的,要释放这些程序必须追踪远程对象的客户端,以确保什么时候释放是合适的.RMI使用一个reference-counting垃圾回收的计算规则.和Modula-3's Network Objects.相似. (See "Network Objects" by Birrell, Nelson, and Owicki, Digital Equipment Corporation Systems Research Center Technical Report 115, 1994.)


To accomplish reference-counting garbage collection, the RMI runtime keeps track of all live references
within each Java virtual machine. When a live reference enters a Java virtual machine, its reference count
is incremented. The first reference to an object sends a "referenced" message to the server for the object.
As live references are found to be unreferenced in the local virtual machine, the count is decremented. When
 the last reference has been discarded, an unreferenced message is sent to the server. Many subtleties exist
 in the protocol; most of these are related to maintaining the ordering of referenced and unreferenced
messages in order to ensure that the object is not prematurely collected.

为了完成reference-counting垃圾回收,RMI运行期跟踪JVM中所有活动的引用.一旦有一个活动的引用进入JVM,它的引用计算值就自动递增,这个对象的第一个引用发送一个"referenced"消息到服务器端,当活动引用发现没有与本地的JVM关联,这个计算值就自动递减.当最后一个引用被废弃,一条’未引用’的消息就会发送到服务器端.这个协议存在许多细微之处,大部分这些关联是为了维持引用和接触引用的顺序,以确保对象不被过早的回收.


When a remote object is not referenced by any client, the RMI runtime refers to it using a weak reference.
The weak reference allows the Java virtual machine's garbage collector to discard the object if no other
local references to the object exist. The distributed garbage collection algorithm interacts with the local Java virtual machine's garbage collector in the usual ways by holding normal or weak references to objects.

当一个远程对象不被任何客户端引用,RMI运行期使用一个弱引用关联,如果这个对象没有其它本地引用关联,这个弱引用允许JVM的垃圾回收器回收.通过持有对象的普通引用或者弱移用,分布式的垃圾回收规则和本地的垃圾回收器使用正常的方式相互影响.


As long as a local reference to a remote object exists, it cannot be garbage-collected and it can be passed in remote calls or returned to clients. Passing a remote object adds the identifier for the virtual machine to which it was passed to the referenced set. A remote object needing unreferenced notification must
implement the java.rmi.server.Unreferenced interface. When those references no longer exist, the
unreferenced method will be invoked. unreferenced is called when the set of references is found to be empty so it might be called more than once. Remote objects are only collected when no more references, either
local or remote, still exist.

只要一个与远程对象关联的本地引用存在,它就不能被回收,并且能在远程调用或者返回客户端时传递.传递一个远程对象为JVM增加一个标识,且是以引用的方式传递的.远程对象需要未引用的通知必须实现java.rmi.server.Unreferenced接口,当这些引用不在存在时,unreferenced方法就会被调用,当引用的集合发现是空的时候,unreferenced就会被调用,所以它可能不止一次的被调用.远程对象只有当没有任何引用的时候才能被回收,不管是本地或者远程的.



Note that if a network partition exists between a client and a remote server object, it is possible that
premature collection of the remote object will occur (since the transport might believe that the client
crashed). Because of the possibility of premature collection, remote references cannot guarantee referential
 integrity; in other words, it is always possible that a remote reference may in fact not refer to an
existing object. An attempt to use such a reference will generate a RemoteException which must be handled by
 the application.

注意一旦客户端和服务器端的网络断开,预期的远程对象的回收可能会发生.所以不能确保远程引用都是正常的,换句话说,尝试使用这样一个引用将会抛出一个RemoteException


3.4 Dynamic Class Loading

RMI allows parameters, return values and exceptions passed in RMI calls to be any object that is serializable
. RMI uses the object serialization mechanism to transmit data from one virtual machine to another and also annotates the call stream with the appropriate location information so that the class definition files can
be loaded at the receiver.

RMI调用时允许参数,返回值和异常传递给序列化的任何对象.RMI使用对象序列化机制在不同JVM之间传输数据,并且用合适的本地信息给调用流做注解,所以class定义文件可以被接收者加载.

When parameters and return values for a remote method invocation are unmarshalled to become live objects in the receiving JVM, class definitions are required for all of the types of objects in the stream. The unmarshal
ling process first attempts to resolve classes by name in its local class loading context (the context class
 loader of the current thread). RMI also provides a facility for dynamically loading the class definitions
for the actual types of objects passed as parameters and return values for remote method invocations from
network locations specified by the transmitting endpoint. This includes the dynamic downloading of remote
stub classes corresponding to particular remote object implementation classes (and used to contain remote
references) as well as any other type that is passed by value in RMI calls, such as the subclass of a
declared parameter type, that is not already available in the class loading context of the unmarshalling side
.

当远程对象调用的参数和返回值在接收的JVM中被unmarshalled为对象时,在流中所有对象的类型必须要求有class注解. unmarshalling过程首先尝试使用当前线程的class加载器通过名字加载classes.RMI也提供一种动态加载class定义.在从网络中一个位置,特别的是传输末端的远程方法调用传输参数和返回值对象的实际类型.包括动态下载远程stub classes,其与远程对象实现classes一致(包含远程引用),而且包括在RMI调用中其它任何传值类型.比如声明参数类型的子类,在unmarshalling的这边使用类加载context加载没什么用处。

To support dynamic class loading, the RMI runtime uses special subclasses of java.io.ObjectOutputStream and java.io.ObjectInputStream for the marshal streams that it uses for marshalling and unmarshalling RMI parameters
 and return values. These subclasses respectively override the annotateClass method of ObjectOutputStream
and the resolveClass method of ObjectInputStream to communicate information about where to locate class files
 containing the definitions for classes corresponding to the class descriptors in the stream.


为支持动态Class加载,RMI运行期针对marshal流使用特别的java.io.ObjectOutputStream和 java.io.ObjectInputStream子集。Marshal流是用来marshalling和unmarshalling RMI参数和返回值。这个子集分别覆盖了ObjectOutputStream的annotateClass的方法和ObjectInputStream的resolveClass方法传达在流中哪里可以找到包含classes定义以及相应的class修饰符的class文件信息。


For every class descriptor written to an RMI marshal stream, the annotateClass method adds to the stream the
 result of calling java.rmi.server.RMIClassLoader.getClassAnnotation for the class object, which may be null
 or may be a String object representing the codebase URL path (a space-separated list of URLs) from which the
 remote endpoint should download the class definition file for the given class.

写入RMI marshal流中的每一个修饰符,annotateClass方法被加入到流中,对象调用ava.rmi.server.RMIClassLoader.getClassAnnotation的结果可能是null值,也可以能使表示一个codebase URL路径的String对象。远程末端将为制定的class从这个URL中加载class定义。


For every class descriptor read from an RMI marshal stream, the resolveClass method reads a single object
from the stream. If the object is a String (and the value of the java.rmi.server.useCodebaseOnly property is
 not true), then resolveClass returns the result of calling RMIClassLoader.loadClass with the annotated String
 object as the first parameter and the name of the desired class in the class descriptor as the second
parameter. Otherwise, resolveClass returns the result of calling RMIClassLoader.loadClass with the name of
the desired class as the only parameter.

从RMI marshal流中读取的每一个class修饰器,resolveCalss方法从流中读取一个单独的对象。如果这个对象是个String(并且java.rmi.server.useCodebaseOnly的属性值是true),resloveClass调用RMIClassLoader.loadClass,使用这个annotated的String对象作为第一个参数,class修饰器重设想的名字作为第二个参数,并返回结果,否则resolveClass只使用想要的名称作为参数调用RMIClassLoader.loadClass,并返回结果。




3.5 RMI Through Firewalls Via Proxies

The RMI transport layer normally attempts to open direct sockets to hosts on the Internet. Many intranets,
however, have firewalls that do not allow this. The default RMI transport, therefore, provides two alternate
 HTTP-based mechanisms which enable a client behind a firewall to invoke a method on a remote object which
resides outside the firewall.
As described in this section, the HTTP-based mechanism that the RMI transport layer uses for RMI calls only applies to firewalls with HTTP proxy servers.

RMI传输层一般尝试直接打开Internet上的sockets,但是许多intranets,装了防火墙不允许这么做.所以默认的RMI传输提供另外一种基于http的机制允许客户端在防火墙之后调用防火墙之外的远程对象的方法.
RMI传输层使用的基于HTTP的机制仅仅应用于HTTP代理服务器的防火墙.


3.5.1 How an RMI Call is Packaged within the HTTP Protocol

To get outside a firewall, the transport layer embeds an RMI call within the firewall-trusted HTTP protocol.
 The RMI call data is sent outside as the body of an HTTP POST request, and the return information is sent
back in the body of the HTTP response. The transport layer will formulate the POST request in one of two
ways:
1.    If the firewall proxy will forward an HTTP request directed to an arbitrary port on the host machine, then it is forwarded directly to the port on which the RMI server is listening. The default RMI transport
layer on the target machine is listening with a server socket that is capable of understanding and decoding
 RMI calls inside POST requests.
2.    If the firewall proxy will only forward HTTP requests directed to certain well-known HTTP ports, then the call is forwarded to the HTTP server listening on port 80 of the host machine, and a CGI script is
executed to forward the call to the target RMI server port on the same machine.

为了在防火墙之外获取,传输层把一个远程调用嵌入防火墙信任的HTTP协议.
远程调用的数据作为一个HTTP Post请求的body传送出去.返回的信息则作为HTTP回应body返回.传输曾必须规范POST请求在下面方式的一种:
1.    如果防火墙代理使用机器上任意一个端口转交一个HTTP请求,则它会转交给RMI Server上监听的端口,在目标机器上默认的RMI传输层使用一个Server Socket监听,它能解释和解码内部的在POST请求中的RMI调用.
2.    如果防火墙代理使用机器上不指定端口转交一个HTTP请求,则转交给监听80端口的HTTP Server.CGI script将会调用同一台机器上目标RMI server端口.


3.5.2 The Default Socket Factory

The RMI transport implementation includes an extension of the class java.rmi.server.RMISocketFactory, which is the default resource-provider for client and server sockets used to send and receive RMI calls; this
default socket factory can be obtained via the java.rmi.server.RMISocketFactory.getDefaultSocketFactory
method. This default socket factory creates sockets that transparently provide the firewall tunnelling
mechanism as follows:
•  Client sockets first attempt a direct socket connection. Client sockets automatically attempt HTTP
connections to hosts that cannot be contacted with a direct socket if that direct socket connection results in either a java.net.NoRouteToHostException or a java.net.UnknownHostException being thrown. If a direct
socket connection results in any other java.io.IOException being thrown, such as a java.net.ConnectException
, the implementation may attempt an HTTP connection.

•  Server sockets automatically detect if a newly-accepted connection is an HTTP POST request, and if so,
return a socket that will expose only the body of the request to the transport and format its output as an
 HTTP response.



RMI传输实现包括java.rmi.server.RMISocketFactory一个扩展,它是客户端和服务器端发送和接收RMI调用默认的资源提供者.默认的Soket Factory可以通过java.rmi.server.RMISocketFactory.getDefaultSocketFactory方法获得,默认的socket factory创建sockets提供防火墙传输机制.如下:

•  客户端sockets首先尝试直接打开一个socket连接.客户端sockets自动尝试与主机上建立HTTP连接,如果直接soket连接返回的是java.net.NoRouteToHostException或者java.net.UnknownHostException的一种, 则说明主机不能直接被soket直接接触到.如果直接的socket连接返回的是一个其它的java.io.IOException,比如java.net.ConnectException,这个实现则会尝试一个HTTP连接.

•  如果一个新的接受到的连接是一个HTTP POST请求,Server sockets能自动观测到,则返回一个socket,传输只暴露body的请求,并且作为一个HTTP response格式化它的输出。


Client-side sockets, with this default behavior, are provided by the factory's
java.rmi.server.RMISocketFactory.createSocket method. Server-side sockets with this default behavior are
provided by the factory's java.rmi.server.RMISocketFactory.createServerSocket method.

客户端的sockets的默认行为由java.rmi.server.RMISocketFactory.createSocket方法提供.服务器端的默认行为由java.rmi.server.RMISocketFactory.createServerSocket方法提供.


3.5.3 Configuring the Client

A client can disable the packaging of RMI calls as HTTP requests by setting the java.rmi.server.disableHttp
 property to equal the boolean value true.

设置java.rmi.server.disableHttp属性为true,客户端可以阻止RMI调用包装成HTTP请求.


3.5.4 Configuring the Server

________________________________________
Note - The host name should not be specified as the host's IP address, because some firewall proxies will
not forward to such a host name.
________________________________________
1.    In order for a client outside the server host's domain to be able to invoke methods on a server's
remote objects, the client must be able to find the server. To do this, the remote references that the
server exports must contain the fully-qualified name of the server host.
Depending on the server's platform and network environment, this information may or may not be available to the Java virtual machine on which the server is running. If it is not available, the host's fully qualified name must be specified with the property java.rmi.server.hostname when starting the server.

For example, use this command to start the RMI server class ServerImpl on the machine chatsubo.javasoft.com:
   java -Djava.rmi.server.hostname=chatsubo.javasoft.com ServerImpl


________________________________________
Note – 主机名不能指定为IP地址,因为一些防火墙代理不能转交给这样一个主机名
________________________________________
1.    为了使服务器主机domain之外的客户端能调用服务器端远程对象的方法,客户端必须能找到这个Server,所以服务器端暴露的远程引用必须包含服务器主机的全名.

依赖服务器平台和网络环境,信息可能会不能到达服务器端运行的JVM.这时,必须在Server启动的时候,设置ava.rmi.server.hostname属性为主机的全名.

例如,在chatsubo.javasoft.com机器上使用这个命令启动RMI server类ServerImpl.
java -Djava.rmi.server.hostname=chatsubo.javasoft.com ServerImpl


2.    If the server will not support RMI clients behind firewalls that can forward to arbitrary ports, use
this configuration:
1.    An HTTP server is listening on port 80.
2.    A CGI script is located at the aliased URL path
               /cgi-bin/java-rmi.cgi
This script:
*    Invokes the local interpreter for the Java programming language to execute a class internal to the
transport layer which forwards the request to the appropriate RMI server port.
*    Defines properties in the Java virtual machine with the same names and values as the CGI 1.0 defined
environment variables.
An example script is supplied in the RMI distribution for the Solaris and Windows 32 operating systems.
Note that the script must specify the complete path to the interpreter for the Java programming language on the server machine.

An example script is supplied in the RMI distribution for the Solaris and Windows 32 operating systems. Note
 that the script must specify the complete path to the interpreter for the Java programming language on the
 server machine.

如果这个Server不支持RMI 客户端绑定随意的端口,使用下面的configuration:
1.    一个监听80端口的HTTP Server
2  .一个CGI script定位于别名的URL路径
                    /cgi-bin/java-rmi.cgi
这个script:
*    为传输层调用一个本地的Java语言的解释器执行一个内部class,为其转交一个请求到一个合适的RMI Server端口.
*    在JVM中定义相同的名字和值的属性作为CGI1.0定义环境变量.

在RMI中有一个针对Solaris和Windows32操作系统的script例子.注意在服务器端的机器上,script必须指定java解释器的完整路径.


3.5.5 Performance Issues and Limitations

Calls transmitted via HTTP requests are at least an order of magnitude slower that those sent through direct
 sockets, without taking proxy forwarding delays into consideration.
Because HTTP requests can only be initiated in one direction through a firewall, a client cannot export its
 own remote objects outside the firewall, because a host outside the firewall cannot initiate a method
invocation back on the client.

通过HTTP传输的调用请求通常比直接通过socket传输慢的多,因为通过socket传输不需要考虑走代理的延迟
由于HTTP请求只能在一个方向上通过防火墙进行初始化.客户端不能在防火墙之外暴露自己的远程对象,因为防火墙之外的主机不能初始化一个客户端方法调用的返回



参考资料:
RMI lesson翻译: http://www.blogjava.net/jht/archive/2007/05/09/116216.html
官方文档:http://java.sun.com/javase/6/docs/platform/rmi/spec/rmiTOC.html