天天看點

RMI 入門

RMI : remote method invocation,遠端方法調用。

有Client object, client helper(Stub), Service Helper(Skeleton), service object四個參與者。前兩個是用戶端,後兩個是伺服器端的,兩個helper都是用于對資訊的包裝(pack)和解包(unpack),以友善網絡傳輸。

五個步驟:

一,make a remote interface(MyService.java)。定義伺服器端提供的方法。遠端客戶就是調用這個接口的方法。Stub和Skeleton都會實作這個接口。

二,make a remote implementation(MyServiceImpl.java)。這是實際的服務類,實際的操作由這個類來實作。

三,generate the stubs and skeletons using rmic。在cmd下輸入: rmic MyServiceImpl,會産生MyServiceImpl_Stub.class 和 MyServiceImpl_Skel.class兩個類檔案,rmic是jdk自帶的一個工具。

四,start the RMI registry(rmiregistry),在cmd輸入:rmiregistry,啟動這項服務後,用戶端就可以通過這項服務找對對應的Proxy/client helper(stub)。

五,start the remote service。cmd輸入:java MyServiceImpl,就會在rmi registry裡注冊這個服務,令這服務可以被遠端調用。

以建立一個向伺服器端請求擷取一個String為執行個體,具體說明:

初次嘗試可以在同一機器上進行,但在實際應用中,應在不同機器上,才能展現rmi的用處

在伺服器端的操作:

1.建立遠端接口。

package chapter11.proxy.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface MyRemote extends Remote{
	public String sayHello() throws RemoteException;
}
           

 說明:遠端接口,是用戶端調用伺服器端的接口,就是這個接口提供什麼方法,那用戶端也就隻能調用遠端對象的這些方法。接口需要繼承Remote類,檢視Remote類源碼,可知它隻是個空的接口,沒任何方法在裡面,在rmi調用中,這是作為一個标志接口("marker" interface)。另外每個遠端方法都必須抛出RemoteException,因為每個遠端方法都被認為是不安全的。還有遠端方法的傳回值和參數的類型必須是可序列化的(serializable,int,char,long等所有原始類型和jdk自帶的集合類型都是可序列化,如果是自己建立的類,就必須extendsSerializable接口)。本例中的sayHello()的傳回值String是可序列化的。

2.實作遠端接口。

package chapter11.proxy.rmi;

import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;


public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{

	public MyRemoteImpl() throws RemoteException{
	}
	
	@Override
	public String sayHello() throws RemoteException {
		return "server syas, 'hey'";
	}
	
	public static void main(String[] args) {
		try{
			MyRemote service = new MyRemoteImpl();
			Naming.rebind("RemoteHello", service);
		}catch(Exception e){
			e.printStackTrace();
		}
	}

}
           

 建立一個類實作遠端接口。繼承UnicastRemoteObject 是為了繼承一些作為一個遠端服務對象的功能,該類提供四個靜态方法:

RemoteStub exportObject(Remote obj);(其中一個),可導出(export)遠端對象。

另外因為UnicastRemoteObject類的構造器抛出RemoteException,是以其子類的構造器都需要抛出同樣的異常(Java文法所定,因為執行個體化子類前,jvm會執行個體化其父類)。

main方法是在Rmi Registry裡注冊這個服務的,Main方法可以放到另外一個類裡執行,不一定要放要實作類裡,這裡為了示範簡單而放這。

注冊通過java.rmi.Naming類的靜态方法rebing(String name, Remote obj)。name是用戶端擷取的服務的名稱,obj是service類。

3.生成stubs和skeleton(在Jdk5以後,預設是-v1.2[rmic 的stub協定版本],隻會生成stub)。

這一步,需要要用到jdk自帶的工具:rmic。先确定你系統的Path環境變量裡已經設了%java_home%\bin(或不設,就把工具的路徑全稱寫全)。rmic指令如下說明:

rmic 參數: 
-classpath <path> 指定查找輸入類檔案的位置; 
-d <dir> 指定存放生成檔案的位置

對于沒放在package裡的類:直接進入.class目錄,
運作 rmic MyRemoteImpl,會在.class目錄生成xx_stub.class檔案

對于package chapter11.proxy.rmi下的MyRemoteImpl.class。(本例子就是這情況)
就是bin\chapter11\proxy\rmi\MyRemoteImpl.class。(在eclipse的.class檔案預設放在bin目錄)
進入對應的bin目錄,運作:rmic chapter11.proxy.rmi.MyRemoteImpl


就會在bin\chapter11\proxy\rmi\目錄下生成xx_stub.class檔案。

可以通過 -d <dir>,配置生成stub的目錄,但生成的目錄是上層目錄,在dir下會
根據Package資訊,自動建立目錄層次:dir\chapter11\proxy\rmi\xx_stub.class。
           

我是開始以為對.java檔案調用rmic。在src目錄下(eclipse建的工程),試過很多次後,發現我錯了,原來是要cmd進入到.class的bin目錄裡調用rmic指令。并且rmic 指令中的檔案名不需要.class字尾。調用後會生成

(總之,在這裡提出一點很重要的,所有的指令都要在bin目錄下執行 ,這樣才能保證不浪費時間ClassNotFound之類的錯誤,我在cmd下運作指令時,由于有時切換到不同目錄,導緻浪費很多時間在嘗試。。。人生苦短啊,大家别浪費時間在低級錯誤上)

4.生成xx_stub.class檔案後(stub.class應該在bin目錄中,與MyRemoteImpl同一目錄),在bin目錄中執行指令:

 rmiregistry

這應該在一個新的終端執行。這就在伺服器端啟動了一個“注冊管理器”(我這樣稱呼的,書上是這樣描述的:RMI registry(on server))。“注冊管理器”是負責管理各種服務對象的(MyRemoteImpl)和傳回對應stub對象。

5.啟動服務。在cmd運作: java chapter11.proxy.rmi.MyRemoteImpl 。記住是在bin目錄裡運作該指令。

6.好了,最後就寫用戶端代碼來測試。(接下來的代碼,可以在用戶端寫,不需要在伺服器端寫,但在用戶端需要MyRemote.class和MyRemoteImpl.class兩個類檔案)

package chapter11.proxy.rmi;

import java.rmi.Naming;

public class MyRemoteClient {
	public static void main(String[] args) {
		try {
			MyRemote service = (MyRemote) Naming
					.lookup("rmi://127.0.0.1/RemoteHello");
			String s = service.sayHello();
			System.out.println(s);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}
           

對于用戶端代碼,首先通過java.rmi.Naming的lookup(url)方法來找到伺服器的服務對象,接着rmi registry傳回該對象,這樣用戶端就儲存一個stub(服務對象,相當于MyRemoteImpl的引用) ,用戶端這時就可以調用服務對象的方法sayHello()來傳回結果。

以下是三個cmd的内容:

rmi registry:

D:\My Documents\chow\workspace\headfirstDf\bin>rmiregistry
           

server:(用戶端調用兩次,是以列印兩次request...)

D:\My Documents\chow\workspace\headfirstDf\bin>java chapter11.proxy.rmi.MyRemoteImpl
get a sayHello request...
get a sayHello request...
           

client:

D:\My Documents\chow\workspace\headfirstDf\bin>java chapter11.proxy.rmi.MyRemote
Client
server syas, 'hey'

D:\My Documents\chow\workspace\headfirstDf\bin>java chapter11.proxy.rmi.MyRemote
Client
server syas, 'hey'
           

geek bits:

這裡提到用戶端需要伺服器端的class檔案,可以簡單地從伺服器端發送到用戶端。

在jdk5後,還有一種方法,叫“動态類下載下傳”(dynamic class downloading)。用戶端通過url來找到class檔案,會通過http的get來擷取類檔案。

繼續閱讀