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