RMI規范--第三章
主題:
Stub 與 skeleton
遠程方法調用中的線(xiàn)程使用
遠程對象的垃圾收集
動(dòng)態(tài)類(lèi)的加載
通過(guò)代理服務(wù)器透過(guò)防火墻的 RMI
3.1 Stub 與 skeleton
在與遠程對象的通信過(guò)程中,RMI 將使用標準機制(用于 RPC 系統):stub
與 skeleton。遠程對象的 stub 擔當遠程對象的客戶(hù)本地代表或代理人角色。
調用程序將調用本地 stub 的方法,而本地 stub 將負責執行對遠程對象的方
法調用。在 RMI 中,遠程對象的 stub 與該遠程對象所實(shí)現的遠程接口集相同。
調用 stub 的方法時(shí),將執行下列操作:
初始化與包含遠程對象的遠程虛擬機的連接。
對遠程虛擬機參數的進(jìn)行編組(寫(xiě)入并傳輸)
等待方法調用結果
解編(讀?。┓祷刂祷蚍祷氐漠惓?
將值返給調用程序
為向調用程序展示比較簡(jiǎn)單的調用機制,stub 將參數的序列化和網(wǎng)絡(luò )級通信隱
藏了起來(lái)。
在遠程虛擬機中,每個(gè)遠程對象都可以有相應的 skeleton(純 JDK1.2 環(huán)境中
不需要 skeleton)。skeleton 負責將調用分配給實(shí)際的遠程對象實(shí)現。它在
接收入進(jìn)入方法調用時(shí)執行下列操作:
解編(讀?。┻h程方法的參數
調用實(shí)際遠程對象實(shí)現上的方法
將結果(返回值或異常)編組(寫(xiě)入并傳輸)給調用程序
由于推出于 JDK1.2 及附加的 stub 協(xié)議,使得在純 JDK1.2 環(huán)境中無(wú)需使用
skeleton。相反,應使用通用代碼代替 JDK1.1 中的 skeleton 履行其職責。
stub 和 skeleton 由 rmic 編譯器生成。
3.2 遠程方法調用中的線(xiàn)程使用
RMI 運行時(shí)分配給遠程對象實(shí)現的方法可能在也可能不在獨立的線(xiàn)程中執行。
RMI 運行時(shí)將無(wú)法擔保遠程對象與線(xiàn)程的映射關(guān)系。因為同一個(gè)遠程對象的遠程
方法調用可能會(huì )同時(shí)執行,所以遠程對象實(shí)現需確保其實(shí)現是線(xiàn)程安全的。
3.3 遠程對象的垃圾收集
與在本地系統中相同,在分布式系統中自動(dòng)刪除那些不再被任何客戶(hù)機引用的遠
程對象是令人滿(mǎn)意的。這可以將程序員從跟蹤遠程對象客戶(hù)機以便適時(shí)終止的任
務(wù)中解脫出來(lái)。RMI 使用與 Modula-3 網(wǎng)絡(luò )對象相似的引用計數的垃圾收集算
法(參見(jiàn) 1994 年 5 月數字設備公司系統研究中心技術(shù)報告 115 中 Birrell、
Nelson 和 Owicki 的“網(wǎng)絡(luò )對象”)。
要實(shí)現引用計數垃圾收集,RMI 運行時(shí)需要跟蹤每個(gè) Java 虛擬機內的所有活
動(dòng)引用。當活動(dòng)引用進(jìn)入 Java 虛擬機時(shí),其引用計數將加 1。首次引用某對
象時(shí)會(huì )向該對象的服務(wù)器發(fā)送“referenced”消息。當發(fā)現活動(dòng)引用在本地虛
擬機中并未被引用時(shí),該數將減 1。放棄最后的引用時(shí),未被引用的消息將被發(fā)
送到服務(wù)器。協(xié)議中存在很多微妙之處,其中大部分都與維護引用或未引用消息
的次序有關(guān),可確保對象不被過(guò)早地收集。
當某遠程對象不被任何客戶(hù)機所引用時(shí),RMI運行時(shí)將對其進(jìn)行弱引用。如果不存在該
對象的其它本地引用,則弱引用將允許 Java 虛擬機的垃圾收集器放棄該對象。
通過(guò)保持對對象的常規引用或弱引用,分布式垃圾收集算法可與本地 Java 虛擬
機的垃圾收集器以常規方式進(jìn)行交互。
只要存在對遠程對象的本地引用,就不能將遠程對象當作垃圾進(jìn)行收集,而且該
遠程對象也可在遠程調用中傳送或返回客戶(hù)機。傳遞遠程對象也將同時(shí)把目標虛
擬機的標識符添加到被引用集中。需要未引用通知的遠程對象必須實(shí)現
java.rmi.server.Unreferenced 接口。當這些引用不再存在時(shí),將調用
unreferenced 方法。當發(fā)現引用集為空時(shí),也將調用 unreferenced。因此,
unreferenced 方法可能會(huì )被多次調用。只有當沒(méi)有本地和遠程引用時(shí),才可收集
遠程對象。
注意,如果在客戶(hù)機和遠程服務(wù)器對象之間存在網(wǎng)絡(luò )分區,則可能會(huì )過(guò)早地收集
、遠程對象(因為傳輸可能認為客戶(hù)機已失效)。由于可能會(huì )出現過(guò)早收集的現
象,因此遠程引用將不能保證引用的完整性。換句話(huà)說(shuō),遠程引用實(shí)際上可能指
向不存在的對象。使用此類(lèi)引用時(shí)將拋出必須由應用程序處理的 RemoteExcepti
on。
3.4 動(dòng)態(tài)類(lèi)加載
RMI 允許傳入 RMI 調用中的參數、返回值和異常為任何可序列化對象。RMI 使
用對象序列化機制將數據從一個(gè)虛擬機傳輸到另一個(gè)虛擬機,同時(shí)用相應的位置
信息注釋調用流,以便在接收端上加載類(lèi)定義文件。
當解編遠程方法調用的參數和返回值以使之成為接收虛擬機中的有效對象時(shí),流
中所有類(lèi)型的對象都需要類(lèi)定義。解編進(jìn)程將首先嘗試通過(guò)本地類(lèi)加載上下文(
當前線(xiàn)程的上下文類(lèi)加載器)中的名稱(chēng)來(lái)解析類(lèi)。RMI 也提供動(dòng)態(tài)加載作為參數
和返回值傳送的實(shí)際對象類(lèi)型的類(lèi)定義的手段(其中遠程方法調用的參數和返回
值來(lái)自傳送終點(diǎn)所指定的網(wǎng)絡(luò )位置)。這包括遠程 stub 類(lèi)的動(dòng)態(tài)下載 - 該類(lèi)
對應于特定遠程對象實(shí)現類(lèi)(用于包含遠程引用)及 RMI 調用中通過(guò)值傳送的
任何其它類(lèi)型,例如在解編端的類(lèi)加載上下文中尚不可用的,聲明參數類(lèi)型的子
類(lèi)。
要支持動(dòng)態(tài)類(lèi)加載,RMI 運行時(shí)應使用用于編組、解編 RMI 參數和返回值的編
組流的特定 java.io.ObjectOutputStream 和 java.io.ObjectInputStream
子類(lèi)。這些子類(lèi)覆蓋了 ObjectOutputStream 的 annotateClass 方法和
ObjectInputStream 的 resolveClass 方法,以便就何處定位包含對應于流中
類(lèi)描述符的類(lèi)定義的類(lèi)文件交換信息。
對于每個(gè)寫(xiě)入 RMI 編組流的類(lèi)描述符,annotateClass 方法將把類(lèi)對象調用
java.rmi.server.RMIClassLoader.getClassAnnotation 的結果添加到流中。
該結果可能為空,也可能是表示 codebase URL 路徑(以空格分隔的 URL 列表)
的 String 對象。利用該 codebase URL 路徑,遠程終點(diǎn)可下載所給類(lèi)的定義
文件。
對于從 RMI 編組流中讀取的每個(gè)類(lèi)描述符,resolveClass 方法將從流中讀取
單個(gè)對象。如果該對象是 String(且 java.rmi.server.useCodebaseOnly
屬性不是 true),則 resolveClass 將返回調用 RMIClassLoader.loadClass
的結果,并以所注解的 String 對象作為第一個(gè)參數,以類(lèi)描述符中所需類(lèi)名作
為第二個(gè)參數。否則,resolveClass 將返回調用 RMIClassLoader.loadClass
的結果,并以所需的類(lèi)名作為唯一參數。
有關(guān) RMI 中類(lèi)加載的詳細信息,參見(jiàn)“RMIClassLoader 類(lèi)”(5.6)一節。
3.5 通過(guò)代理服務(wù)器透過(guò)防火墻的 RMI
RMI 傳輸層通常試圖將直接套接字在Internet的主機上打開(kāi)。然而,許多Intranet
的防火墻不允許這樣做。因此,缺省 RMI 傳輸提供兩種基于 HTTP 的機制,可
使防火墻后的客戶(hù)機調用駐留在防火墻外的遠程對象方法。
3.5.1 如何將 RMI 調用包裝在 HTTP 協(xié)議內
要透過(guò)防火墻,傳輸層可在防火墻信任的 HTTP 協(xié)議范圍內嵌入 RMI 調用。將
RMI 調用數據作為 HTTP POST 請求的主體發(fā)送出去后,反饋信息將返回到 HTTP
響應主體內。傳輸層可通過(guò)以下兩種方法構造 POST 請求:
1. 如果防火墻代理服務(wù)器可以把 HTTP 請求定向到主機的任意端口,HTTP
請求就會(huì )被直接轉發(fā)到 RMI 服務(wù)器正在監聽(tīng)的端口上。目標計算機上的缺省RMI
傳輸層可通過(guò)能識別并解碼 POST 請求內的 RMI 調用的服務(wù)器套接字進(jìn)行監聽(tīng)。
2. 如果防火墻代理服務(wù)器只能把 HTTP 請求定向到某個(gè)已知的 HTTP 端口,
該調用就會(huì )被轉發(fā)到正在主機端口 80 上監聽(tīng)的 HTTP 服務(wù)器,而且將執行 CGI
腳本以轉發(fā)對同一計算機上目標 RMI 服務(wù)器端口的調用。
3.5.2 缺省套接字工廠(chǎng)
RMI 傳輸擴展 java.rmi.server.RMISocketFactory 類(lèi)以提供作為客戶(hù)機和服
務(wù)器套接字源提供者的套接字工廠(chǎng)的缺省實(shí)現。該缺省套接字工廠(chǎng)可創(chuàng )建套接字
以透明地提供防火墻通道機制,如下所示:
客戶(hù)機套接字將自動(dòng)嘗試與無(wú)法用直接套接字聯(lián)系的主機進(jìn)行 HTTP 連接。
服務(wù)器套接字將自動(dòng)檢測新近接收的連接是否 HTTP POST 請求,如果是,則只
將請求主體送給傳輸層,同時(shí)將其輸出格式轉化為 HTTP 響應。
工廠(chǎng)的 java.rmi.server.RMISocketFactory.createSocket 方法將提供帶有
此缺省行為的客戶(hù)機端套接字。工廠(chǎng)的
java.rmi.server.RMISocketFactory.createServerSocket
方法將提供帶有此缺省行為的服務(wù)器端套接字。
3.5.3 配置客戶(hù)機
無(wú)需特別配置即可使客戶(hù)機透過(guò)防火墻發(fā)送 RMI 調用。
但如果將 java.rmi.server.disableHttp 屬性的布爾值設置為“true”,客
戶(hù)機即可禁止將 RMI 調用包裝為 HTTP 請求。
3.5.4 配置服務(wù)器
------------------------------------------------------------------
注意 - 主機名不應為主機的 IP 地址,因為某些防火墻代理服務(wù)器不傳送這種
主機名。
------------------------------------------------------------
1. 服務(wù)器主機域外的客戶(hù)機要想調用服務(wù)器遠程對象的方法,則必須找到該
服務(wù)器。因此,服務(wù)器導出的遠程引用必須包含服務(wù)器主機的全名。
本信息可否用于運行服務(wù)器的 Java 虛擬機,取決于服務(wù)器平臺和網(wǎng)絡(luò )環(huán)境。
如果不可用,則啟動(dòng)服務(wù)器時(shí)必須通過(guò) java.rmi.server.hostname 屬性指定主
機的全名。
例如,在 chatsubo.javasoft.com 上可用以下命令啟動(dòng) RMI 服務(wù)器類(lèi)
ServerImpl:
java -Djava.rmi.server.hostname=chatsubo.javasoft.com ServerImpl
2. 如果服務(wù)器不支持防火墻后可傳送到隨意端口的 RMI 客戶(hù)機,則可使用
如下配置:
a. HTTP 服務(wù)器在端口 80 上監聽(tīng)。
b. CGI 腳本的位置為別名 URL 路徑
/cgi-bin/java-rmi.cgi
該腳本:
- 調用本地 Java 解釋程序以執行可將請求傳送到適當 RMI 服務(wù)器端口的傳
輸層內部類(lèi)。
- 在 Java 虛擬機中,以與 CGI 1.0 環(huán)境變量相同的名稱(chēng)和值定義屬性。
用于 Solaris 和 Windows 32 操作系統的 RMI 分布式版本中提供了示例腳
本。注意,腳本必須指定服務(wù)器上 Java 解釋程序的完整路徑。
3.5.5 性能問(wèn)題與局限
在不考慮代理服務(wù)器傳送延遲的情況下,由 HTTP 請求傳送調用至少要比通過(guò)直
接套接字傳送慢一個(gè)數量級。
因為透過(guò)防火墻只能在一個(gè)方向初始化 HTTP 請求,同時(shí)防火墻外的主機也無(wú)法
回調客戶(hù)機的方法調用,所以客戶(hù)機無(wú)法將其自身的遠程對象導到防火墻以外。