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