java分布式对象(RMI+部署使用RMI的程序)
【0】README
1)本文文字轉自 core java volume 2, 旨在學習 java 分布式對象的相關知識;
2) RMI 的實例程序為原創;
3) RMI部署步驟的測試用例,參見 http://blog.csdn.net/pacosonswjtu/article/details/50705258
【1】知識背景
1)每過一段時間, 程序員社區就開始考慮“無所不在的對象”作為所有問題的解決之道;
2) 當一臺計算機上的某個對象需要調用另一臺計算機上的某個對象時, 他就會發送一個包含這個請求的詳細信息的網絡消息;
3)一旦該遠程對象得到了客戶端請求的東西, 就將他發送給 客戶端;
4)在本章,我們將聚焦在Java的分布式編程技術上,特別是用于兩個Java虛擬機(可以運行在不同的計算機上)之間通信的遠程方法調用(RMI)協議。
5)RMI: Remote Method Invocation == 遠程方法調用協議;
【2】客戶與服務器的角色
1)所有分布式編程技術的基本思想都很簡單:客戶計算機產生一個請求,然后將這個請求經由網絡發送到服務器。服務器處理這個請求,并發送回一個針對該客戶端的響應,供客戶端進行分析。圖11-1展示了這個過程。
2)problem+solution:
- 2.1)problem:我們真正想要的是這樣一種機制,客戶端程序員以常規的方式進行方法調用,而無需操心數據在網絡上傳輸或者解析響應之類的問題。
- 2.2)solution:解決的辦法是,在客戶端為遠程對象安裝一個代理(proxy)。(干貨——引入代理的概念)
3)代理: 是位于客戶端虛擬機中的一個對象,它對于客戶端程序來說,看起來就像是要訪問的遠程對象一樣。客戶調用此代理,進行常規的方法調用。而客戶端代理負責與服務器進行聯系。
4)problem+solution:
- 4.1)problem:實現服務的程序員也不希望因與客戶端之間的通信而被絆住。
- 4.2)solution:解決方法是在服務器端安裝第二個代理對象。該服務器代理與客戶端代理進行通信,并且它將以常規方式調用服務器對象上的方法(參見圖11-2)。
5)代理之間是如何通信的呢?這要看以什么技術來實現它們。通常有三種選擇(options): (干貨——代理間的通信技術)
- o1) CORBA,通用對象請求代理架構: 支持任何編程語言編寫的對象之間的方法調用。CORBA使用Internet Inter-ORB協議(IIOP)支持對象間的通信。
- o2) Web服務架構是一個協議集,有時統一描述為WS-*。它也獨立于編程語言的,不過它使用基于XML的通信格式。用于傳輸對象的格式則是簡單對象訪問協議(SOAP)。
- o3) RMI,Java的遠程方法調用技術: 支持Java的分布式對象之間的方法調用。 (干貨——引入RMI)
5.1)與RMI不同,CORBA與SOAP都是完全獨立于語言的。客戶端與服務器程序可以由C、C++、Java或者其他語言編寫。
5.2) Sun 開發了一個更簡單的機制, 稱為遠程方法調用-RMI: 專門針對java 應用之間的通信;
【3】遠程方法調用(RMI)
1) 分布式計算的關鍵是遠程方法調用。 在一臺機器(稱為客戶端)上的某些代碼希望調用在另一臺機器(遠程對象)上的某個對象的一個方法。要實現這一點,方法的參數必須以某種方式傳遞到另一臺機器上,而服務器必須得到通知,去定位遠程對象并執行要調用的方法,并且必須將返回值傳遞回去。(干貨——分布式計算的關鍵是遠程方法調用)
【3.1】存根與參數編組
1)存根: 當客戶代碼要在遠程對象上調用一個遠程方法時,實際上調用的是代理對象上的一個普通的方法,我們稱此代理對象為存根(stub)。 (干貨—— 存根是client 上的 agent object)
- 1.1)看個荔枝:
Warehouse centralWarehouse = get stub object;
double price = centralWarehouse.getPrice(“Blackwell Toaster”); - 1.2)存根位于客戶端機器上,而非服務器上。它知道如何通過網絡與服務器聯系。
- 1.3)存根將遠程方法所需的參數打包成一組字節。
- 1.4)參數編組:對參數編碼的過程稱作參數編組(parameter marshalling),參數編組的目的是將參數轉換成適合在虛擬機之間進行傳遞的格式。在RMI協議中,對象是使用序列化機制進行編碼的,第1章描述了這種機制。在SOAP協議中,對象被編碼為XML。 (干貨——參數編組定義)
2)總的來說,客戶端的存根方法構造了一個信息塊,它由以下幾部分組成(parts):
- p1)被使用的遠程對象的標識符;
- p2)被調用的方法的描述;
- p3)編組后的參數;
3)然后,存根將此信息發送給服務器。在服務器一端,接收對象執行以下動作(actions):
- a1)定位要調用的遠程對象;
- a2)調用所需的方法,并傳遞客戶端提供的參數;
- a3)捕獲返回值或該調用產生的異常;
- a4)將返回值編組,打包送回給客戶端存根。
4) 反編組:客戶端存根對來自服務器端的返回值或異常進行反編組,就成為了調用存根的返回值。如果遠程方法拋出了一個異常,那么存根就在客戶端發起調用的處理空間中重新拋出該異常。圖11-3展示了一次遠程方法調用的信息流。
Attention) 這個過程顯然很復雜,不過好消息是,這一切都是完全自動的,而且在很大程度上,它對程序員是透明性。
【4】RMI 編程模型
【4.1 接口實現】
1) 如何實現和啟動服務器與客戶端程序。 (干貨——如何實現和啟動服務器與客戶端程序)
step1)遠程對象的能力是由在客戶端和服務器之間共享的接口所表示的。例如,以下程序的接口描述了遠程倉庫對象所提供的服務:
public interface Warehouse extends Remote {double getPrice(String description) throws RemoteException; }對以上代碼的分析(Analysis):
- A1)遠程對象的接口必須擴展Remote接口,它位于java.rmi包中。
- A2)接口中的所有方法還必須聲明拋出RemoteException異常,這是因為遠程方法調用與生俱來就缺乏本地調用的可靠性,遠程調用總是存在失敗的可能。
step2) 接下來,在服務器端,必須提供這樣的類,它真正實現了在遠程接口中聲明的工作。
public class WarehouseImpl extends UnicastRemoteObject implements Warehouse {public WarehouseImpl() throws RemoteException{prices = new HashMap<String, Double>();prices.put("Blackwell Toaster", 24.95);prices.put("ZapXpress Microwave Oven", 49.95);}public double getPrice(String description) throws RemoteException{Double price = prices.get(description);return price == null ? 0 : price;}private Map<String, Double> prices; }Attention) UnicastRemoteObject類 :你可以看出這個類是遠程方法調用的目標,因為它繼承自UnicastRemoteObject,這個類的構造器使得它的對象可供遠程訪問。
2)problem+solution:
- 2.1)problem:有時候可能不希望服務器類繼承UnicastRemoteObject,也許是因為實現類已經繼承了其他的類。
- 2.2)solution:在這種情況下,讀者需要親自初始化遠程對象,并將它們傳給靜態的exportObject方法。如果不繼承UnicastRemoteObject,可以在遠程對象的構造器中像下面這樣調用exportObject方法。
UnicastRemoteObject.exportObject(this, 0); // 第二個參數是0,表明任意合適的端口都可用來監聽客戶端連接。
Attention) Unicast這個術語是指我們是通過產生對單一的IP地址和端口的調用來定位遠程對象的這一事實。這是Java SE中唯一支持的機制。更復雜的分布式對象系統(諸如JINI)會考慮到對在多個不同的服務器上的遠程對象的”Multicast”查找。 (干貨——這里提到了JINI)
intro to jini ( from http://baike.baidu.com/link?url=2uT-UgPQerl5Xze13Xqyg0e9eVTH5SOI6ucWnFw_nMKHw8754Lc1H_eM94gxiM7U0U9FUttdQd1yZCiBeYvVlK)
Jini(Java Intelligent Network Infrastructure)是Sun公司的研究與開發項目,它能極大擴展Java技術的能力。Jini技術可使范圍廣泛的多種硬件和軟件—即可與網絡相連的任何實體—能夠自主聯網。
Jini可以使人們極其簡單地使用網絡設備和網絡服務,就象今天我們使用電話一樣—通過網絡撥號即插即用。Jini的目標是最大限度地簡化與網絡的交互性。
【4.2】RMI注冊表
1)problem+solution:
- 1.1)problem:要訪問服務器上的一個遠程對象時,客戶端首先需要一個本地的存根對象。可是客戶端如何對該存根發出請求呢?最普通的方法是調用另一個服務對象上的一個遠程方法,以返回值的方式取得存根對象。然而,這就成了先有雞還是先有蛋的問題。
- 1.2)solution:第一個遠程對象總要通過某種方式進行定位。為此,JDK提供了自舉注冊服務(bootstrap registry service)。
2)如何注冊一個遠程對象? (干貨——如何注冊一個遠程對象)
服務器程序使用自舉注冊服務來注冊至少一個遠程對象。要注冊一個遠程對象,需要一個RMI URL和一個對實現對象的引用。RMI的URL以rmi:開頭,后接服務器以及一個可選的端口號,接著是遠程對象的名字。例如: rmi://localhost:99/central_warehouse
3)默認情況下,主機名是localhost,端口為1099。 服務器告訴注冊表在給定位置將該對象關聯或”綁定”到這個名字。
3.1)下面的代碼將一個WarehouseImpl對象注冊到了同一個服務器上的RMI注冊表中: (干貨——綁定遠程對象到server上的RMI注冊表中)
WarehouseImpl centralWarehouse = new WarehouseImpl();
Context namingContext = new InitialContext();
namingContext.bind(“rmi:central_warehouse”, centralWarehouse);3.2) 看個荔枝:下面的程序只是構造并注冊了一個WarehouseImpl對象
Attention) 基于安全原因,一個應用只有當它與注冊表運行在同一個服務器時,該應用才可以綁定、取消綁定,或重綁定注冊對象的引用。
4)客戶端可以通過下面的調用枚舉所有注冊過的RMI對象: (干貨——注意是client 端枚舉 出 server 端 注冊過的RMI對象)
5)NameClassPair是一個助手類: 它包含綁定對象的名字和該對象所屬類的名字。
- 5.1)看個荔枝: 例如,下面的代碼可以顯示所有注冊對象的名字:
while (e.hasMoreElements())
System.out.println(e.nextElement().getName());
6)客戶端可以通過下面的方式,來指定服務器和遠程對象的名字,以此獲得訪問遠程對象所需的存根: (干貨——client端獲得存根)
String url = "rmi://regserver.mycompany.com/central_warehouse"; Warehouse centralWarehouse = (Warehouse) namingContext.lookup(url);Attention) 在一個全局注冊表中,想保持一個名字的惟一性非常困難,因此不應該將此技術作為定位服務器端對象的一般方法。相反,自舉服務只應該用來注冊非常少的遠程對象。然后使用這些對象來定位其他的對象。 (干貨——自舉服務的局限性)
7) 代碼11-4展示了客戶端如何獲得遠程倉庫對象的存根,并調用遠程的getPrice方法。
【4.3】部署程序(you can also refer to http://www.cnblogs.com/leslies2/archive/2011/05/20/2051844.html)
0)部署前的準備
0.1)創建兩個目錄, 分別存放用于啟動server 和 client 的類:
server/
WarehouseServer.class
Warehouse.class
WarehouseImpl.class
client/
WarehouseClient.class
Warehouse.class0.2)當部署RMI 應用時, 通常需要動態地將類交付給運行程序,其中一個例子就是RMI注冊表。請記住注冊表的一個實例要服務許多不同的RMI應用。RMI注冊表需要有權限訪問注冊的服務接口的類文件,但是,當注冊表啟動時,無法預測將來會產生的所有注冊請求。因此,RMI 注冊表會動態地加載之前從未遇到過的所有遠程接口的類文件;
0.3)動態交付的類文件是通過標準的web server發布的。在我們所舉的case中, server程序 需要使用 Warehouse.class 文件 對于 RMI 注冊表來說是可以獲得的,因此將這個文件放到了第三個稱為download 目錄中的:
download/
Warehouse.class0.4)下面,我們用 web server 來訪問這個目錄中的內容(NanoHTTPD web server)
1)當應用被部署時,服務器、RMI注冊表、Web服務器和客戶端可以定位到四臺不同的計算機上,參見圖11-5。但是,出于測試的目的,我們將只使用一臺計算機。
Attention) 由于安全的原因,作為JDK一部分的rmiregistry服務只允許來自同一臺主機的綁定調用。也就是說,服務器和rmiregistry進程需要定位在同一臺計算機上。但是,RMI架構允許更通用的支持多臺服務器的RMI注冊表實現。 (干貨——基于安全原因,服務器和rmiregistry進程需要定位在同一臺計算機上)
2)測試遠程方法調用:
step1)打開一個新的控制臺窗口,轉到download目錄,然后將NanoHTTPD.java(NanoHTTPD web 服務器)復制到這個目錄中。使用下面的命令來編譯該源文件并啟動這個web服務器: java NanoHTTPD 8080
step2)打開另一個控制臺窗口,轉到不包含任何類文件的某個目錄,并啟動RMI注冊表:rmiregistry
Attention)
- A1)在啟動RMI注冊表之前,請確保CLASSPATH環境變量沒有進行任何設置,并仔細檢查當前目錄是否確實不包含任何類文件。否則,RMI注冊表可能會找到一些假冒的類文件,這使得注冊表需要從另一個不同的來源下載額外的類文件時,產生混淆。
- A2)簡單地講,每個存根對象都有一個代碼基項,指出了它是從何處加載的。這個代碼基被用來加載它所依賴的類。如果RMI注冊表在本地找到了這樣的類,那么它的代碼基就會被設置為錯誤的值。
A3)推薦在server 程序中通過代碼注冊通訊端口和注冊通訊路徑 (干貨——不推薦使用 rmiregistry命令行)
// 注冊通訊端口
LocateRegistry.createRegistry(1099);
// 注冊通訊路徑
Naming.rebind(“rmi://localhost:1099/warehouseService”, warehouseService);step3)現在已經準備好啟動服務器了。打開第三個控制臺窗口,轉到server目錄,并執行下面的命令:
java -Djava.rmi.server.codebase=http://localhost:8080/ WarehouseServer // java.rmi.server.codebase屬性指出了服務類文件的URL。服務器程序將這個URL傳遞給RMI注冊表。
- Warning) 確保代碼基URL以斜杠”/”結尾非常重要。
step4)最后,打開第四個控制臺窗口,轉到client目錄,運行:
你將會看到一條短消息,表示運行方法被成功調用, 參見圖11-6所示;
【4.4】記錄 RMI 活動的日志
1) 如果用下面的選項啟動服務器:
-Djava.rmi.server.logCalls=true WarehouseServer & // 那么服務器會在其控制臺上記錄所有的遠程方法調用到日志中。
java -Djava.rmi.server.logCalls=true -Djava.rmi.server.codebase=http://localhost:8080/ com.corejava.chapter11.server.WarehouseServer
2) 如果想查看額外的日志信息,就必須用標準的Java日志API配置日志記錄器。
2.1) 可以用下面的內容創建一個logging.properties文件。
handlers=java.util.logging.ConsoleHandler
sun.rmi.loader.level=FINE
java.util.logging.ConsoleHandler.level=FINE
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter2.2)我們可以對設置進行調整,方法是為每一個記錄器設置單獨的等級而不是設置全局等級。例如,要跟蹤類的加載行為,可以設置為:
sun.rmi.loader.level=FINE
2.3)用以下選項啟動RMI注冊表
-Djava.util.logging.config.file=directory/logging.properties
2.4)用以下選項啟動客戶端與服務器
-Djava.util.logging.config.file=directory/logging.properties
2.5)表11-1 列出了所有的RMI 日志記錄器:
總結
以上是生活随笔為你收集整理的java分布式对象(RMI+部署使用RMI的程序)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《赛博朋克 2077》总监:后续作品采用
- 下一篇: java分布式对象RMI应用测试用例