分布式編程技術的基本思想:客戶計算機產生一個請求,然后將這個請求通過網絡發送到服務器。服務器處理這個請求,並發送回一個針對該客戶端的響應,供客戶端進行分析。
客戶端和服務端之間用代理進行通訊,客戶端調用代理進行常規的方法調用,而客戶端代理與服務端代理進行聯系,服務端代理以常規方式調用服務器對象上的方法。
客戶端和服務端之間用代理進行通訊,客戶端調用代理進行常規的方法調用,而客戶端代理與服務端代理進行聯系,服務端代理以常規方式調用服務器對象上的方法。
代理之間通信技術:
1.RMI,Java的遠程方法調用技術,支持Java的分布式對象之間的方法調用。
2.CORBA,通過對象請求代理架構,支持任何編程語言編寫的對象之間的方法調用。CORBA使用Internet Inter-ORB協議(IIOP)支持對象間通信。
3.Web服務架構是一個協議集,有時統一描述為WS-*。獨立於編程語言,使用基於XML的通信格式。用於傳輸對象的格式則是簡單對象訪問協議(SOAP)。
若互相通信的程序都是由Java實現的,那么CORBA與WS-*的通用性和復雜性統統是不需要的。遠程方法調用(RMI)專門針對Java應用之間的通信。
1.RMI,Java的遠程方法調用技術,支持Java的分布式對象之間的方法調用。
2.CORBA,通過對象請求代理架構,支持任何編程語言編寫的對象之間的方法調用。CORBA使用Internet Inter-ORB協議(IIOP)支持對象間通信。
3.Web服務架構是一個協議集,有時統一描述為WS-*。獨立於編程語言,使用基於XML的通信格式。用於傳輸對象的格式則是簡單對象訪問協議(SOAP)。
若互相通信的程序都是由Java實現的,那么CORBA與WS-*的通用性和復雜性統統是不需要的。遠程方法調用(RMI)專門針對Java應用之間的通信。
分布式計算的關鍵是遠程方法調用。在一台機器(稱為客戶端)上的某些代碼希望調用在另一台機器(遠程對象)上的某個對象的一個方法。要實現這點,方法的參數必須以某種方式傳遞到另一台機器上,而服務器必須得到通知,去定位遠程對象並執行要調用的方法,並且必須將返回值傳遞回去。
存根和參數編組
當客戶代碼要在遠程對象上調用一個遠程方法時,實際上調用的是代理對象上的一個普通方法,此代理對象稱為存根(stub)。
存根位於客戶端機器上,而非服務器上,它知道如何通過網絡與服務器聯系。存根將遠程方法所需的參數打包成一組字節。隊參數編碼的過程叫參數編組,參數編組的目的的將參數轉換成適合在虛擬機之間進行傳遞的格式。在RMI協議中,對象是使用序列化機制來進行編碼的,在SOAP協議中,對象被編碼為XML。
客戶端的存根方法構造一個信息塊,由以下部分組成:
被使用的遠程對象的標識符;
被調用的方法的描述;
編組后的參數。
然后存根將此消息發送給服務器。服務器接收對象執行以下動作:
定位要調用的遠程對象;
調用所需的方法,並傳遞客戶端提供的參數;
捕獲返回值或該調用參生的異常;
將返回值編組,打包返回給客戶端存根。
客戶端存根對來自服務器端的返回值或異常進行反編組,就成為了調用存根的返回值。
當客戶代碼要在遠程對象上調用一個遠程方法時,實際上調用的是代理對象上的一個普通方法,此代理對象稱為存根(stub)。
存根位於客戶端機器上,而非服務器上,它知道如何通過網絡與服務器聯系。存根將遠程方法所需的參數打包成一組字節。隊參數編碼的過程叫參數編組,參數編組的目的的將參數轉換成適合在虛擬機之間進行傳遞的格式。在RMI協議中,對象是使用序列化機制來進行編碼的,在SOAP協議中,對象被編碼為XML。
客戶端的存根方法構造一個信息塊,由以下部分組成:
被使用的遠程對象的標識符;
被調用的方法的描述;
編組后的參數。
然后存根將此消息發送給服務器。服務器接收對象執行以下動作:
定位要調用的遠程對象;
調用所需的方法,並傳遞客戶端提供的參數;
捕獲返回值或該調用參生的異常;
將返回值編組,打包返回給客戶端存根。
客戶端存根對來自服務器端的返回值或異常進行反編組,就成為了調用存根的返回值。
RMI編程模型簡單實例
接口
import java.rmi.Remote; import java.rmi.RemoteException; /** * 簡單倉庫的遠程借口 * 遠程對象的借口必須擴展Remote接口。接口中的所有方法必須聲明拋出RemoteException異常,因為遠程調用總是存在失敗的可能。 */ public interface Warehouse extends Remote { double getPrice(String description) throws RemoteException; }
實現接口
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.HashMap; import java.util.Map; /** * 實現簡單倉庫遠程借口的類 *該類是遠程方法調用的目標。繼承UnicastRemoteObject,這個類的構造器可以讓它的對象供遠程訪問。 *有時候不繼承UnicastRemoteObject,在這種情況下需手動初始化遠程對象,並將它們傳給靜態的exportObject方法。在遠程對象的構造器中調用exportObject方法:UnicastRemoteObjcet.exportObject(this,0); */ public class WarehouseImpl extends UnicastRemoteObject implements Warehouse { private Map<String,Double> prices; public WarehouseImpl() throws RemoteException{ prices = new HashMap<String,Double>(); prices.put("Blackwell Toaster",24.95); prices.put("ZapXpress Microwave", 49.95); } public double getPrice(String description) throws RemoteException { Double price = prices.get(description); return price == null ? 0 : price; } }
構造並注冊WarehouseImpl對象
import java.rmi.RemoteException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; /** * 第一個遠程對象總要通過某種方式進行定位,JDK提供了自舉注冊服務。 * 服務器程序使用自舉注冊服務來注冊至少一個遠程對象,要注冊一個遠程對象,需要一個RMI URL和一個對實現對象的引用。 * RMI的URL以rmi:開頭,后接服務器以及一個可選的端口號,接着是遠程對象的名字。例如:rmi://regserver.mycompany.com:99/central_warehouse * 默認情況下,主機名是localhost,端口為1099。服務器告訴注冊表在給定位置將該對象關聯或“綁定”到這個名字。 * 下面代碼將一個WarehouseImpl對象注冊到了同一個服務器上的RMI注冊表中: WarehouseImpl centralWarehouse = new WarehouseImpl(); Context namingContext = new InitialContext(); namingContext.bind("rmi:central_warehouse", centralWarehouse); * */ public class WarehouseServer { public static void main(String[] args) throws RemoteException,NamingException{ System.out.println("Constructing server implementation..."); WarehouseImpl centralWarehouse = new WarehouseImpl(); System.out.println("Binding server implementation to registry..."); Context namingContext = new InitialContext(); namingContext.bind("rmi:central_warehouse", centralWarehouse); System.out.println("Waiting for invocations from clients..."); } }
客戶端獲得遠程對象
import java.rmi.RemoteException; import java.util.Enumeration; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NameClassPair; import javax.naming.NamingException; public class WarehouseClient { public static void main(String[] args) throws NamingException,RemoteException { Context namingContext = new InitialContext(); System.out.println("RMI registry bindings:"); //通過下面的調用枚舉所有注冊過的RMI對象 //NameClassPair是一個助手類,它包含綁定對象的名字和該對象所屬類的名字 Enumeration<NameClassPair> e = namingContext.list("rmi://localhost/"); while(e.hasMoreElements()) { System.out.println(e.nextElement().getName());//打印注冊對象的名字 } //客戶端通過下面方式來指定服務器和遠程對象的名字,以此獲得訪問遠程對象所需的存根 String url = "rmi://localhost/central_warehouse";; Warehouse centralWarehouse = (Warehouse)namingContext.lookup(url); String descr = "Blackwell Toaster"; double price = centralWarehouse.getPrice(descr); System.out.println(descr+":"+price); } }
部署RMI簡單實例
創建兩個目錄分別用於啟動服務器和客戶端的類
server/
WarehouseServer.class
Warehouse.class
WarehouseImpl.class
client/
WarehouseClient.class
Warehouse.class
部署RMI應用時,通常需要動態地將類交付為運行程序,其中一個例子就是RMI注冊表。注冊表的一個實例要服務許多不同的RMI應用。當注冊表啟動時,無法預測將來會參生的所有注冊表請求。RMI注冊表會動態地加載之前從未遇到過的所有遠程接口的類文件。
動態交付的類文件是通過標准的Web服務器發布的,服務器程序需使Warehouse.class文件對於RMI注冊表來說是可獲得的,所以將該文件放到第三個目錄download中:
download/
Warehouse.class
部署應用,服務器、RMI注冊表、Web服務器和客戶端可以定位到四台不同的機器上。(由於一台機器部署測試例子沒看懂,所以具體一台機器測試沒做筆記)
擴展
javax.naming.InitialContext InitialContext() 構建一個命名上下文,用來訪問RMI注冊表。 javax.naming.Context static Object lookup(String name) 返回給定名字的對象。如果該名字尚未綁定則拋出NamingException異常。 static void bind(String name,Object obj) 將name和obj對象綁定。如果該對象已經綁定則拋出NameAlreadyBoundException異常。 static void unbind(String name) 解除該名字的綁定。解除一個不存在的綁定是合法的。 static void rebind(String name,Object obj) 將name和obj對象綁定。替換掉以前的綁定。 NamingEnumeration<NameClassPair> list(String name) 返回一個枚舉列表,列出所有匹配的綁定對象。使用“rmi:”調用該方法可以列出所有MRI對象。 javax.naming.NameClassPair String getName() 獲取已命名對象的名字。 String getClassName() 獲取已命名對象所屬的類名。 java.rmi.Naming static Remote lookup(String url) 返回URL對應的遠程對象。如果該名字尚未綁定,拋NotBoundException異常。 static void bind(String name,Remote obj) 將name和遠程對象obj綁定。如果該對象已經綁定拋AlreadyBoundException異常。 static void unbind(String name) 解除該名字的綁定。如果該名字沒有綁定拋NotBound異常。 static void rebind(String name,Remote obj) 將name和遠程對象obj綁定。替換掉以前的綁定。 static String[] list(String url) 參數url指定了某個注冊表,返回注冊表中的所有URL組成的字符串數組。