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.tkk7.com/jht/archive/2007/05/09/116216.html
官方文檔:http://java.sun.com/javase/6/docs/platform/rmi/spec/rmiTOC.html