標籤:傳輸 資料 命令 結果 序列化 vm虛擬機器 轉換 增加 form
最近在學習Dubbo,RMI是很重要的底層機制,RMI(Remote Method Invocation)遠程方法調用是一種電腦之間利用遠程對象互相調用實現雙方通訊的一種通訊機制。使用這種機制,某一台電腦(即JVM虛擬機器)上的對象可以調用另外一台電腦上的對象來擷取遠端資料。
RMI的實現對建立分布式Java應用程式至關重要,是Java體系非常重要的底層技術。
1.RMI的概念和原理
RMI思路是在用戶端安裝一個代理(proxy),代理是位於用戶端虛擬機器中的一個對象,對於用戶端對象,看起來就像訪問的遠程對象一樣,用戶端代理會使用網路通訊協定與伺服器進行通訊。
同樣的服務端也會有一個代理對象來進行通訊的繁瑣工作。
在RMI中,用戶端的代理對象被稱為存根(Stub),存根位於用戶端機器上,它知道如何通過網路與伺服器聯絡。存根會將遠程方法所需的參數打包成一組位元組。對參數編碼的過程被稱為參數編組(parameter marshalling),參數編組的目的是將參數轉換成適合在虛擬機器之間進行傳遞的形式。在RMI協議中,對象時使用序列化機制進行編碼的。
總的來說,用戶端的存根方法構造了一個資訊塊,它由以下幾部分組成
被使用的遠程對象的標識符;
被調用的方法的描述;
編組後的參數;
然後,存根將此資訊發送給伺服器。在伺服器的一端,接收器對象執行以下動作:
定位要調用的遠程對象;
調用所需的方法,並傳遞用戶端提供的參數;
捕獲傳回值或調用產生的異常;
將傳回值編組,打包送回給用戶端存根;
用戶端存根對來自伺服器端的傳回值或異常進行反編組,其結果就成為了調用存根傳回值。
2.RMI體繫結構
樁/架構(Stub/Skeleton)層:用戶端的樁和伺服器端的架構;
遠端參照(remote reference)層:處理遠端參照行為
傳送層(transport):串連的建立和管理,以及遠程對象的跟蹤
3.RMI與代理模式
RMI的實現是典型的代理模式思想。
(1)代理模式的UML圖
代理模式為其他對象提供一種代理以控制對這個對象的訪問,把調用者與被調用者分離開,由代理負責傳遞資訊來完成調用。
比如你想買美版Iphone6s,朋友出國,幫你海淘帶回,整個過程就是代理模式,朋友作為代理,代你完成你想進行的操作。
代理模式能將代理對象與真正被調用的對象分離,在一定程度上降低了系統的耦合度;
在用戶端和目標對象之間起到一個中介作用,這樣可以起到保護目標對象的作用,代理對象也可以對目標對象調用之前進行其他動作。
代理模式在用戶端和目標對象增加一個代理對象,會造成請求處理速度變慢;同時也增加了系統的複雜度。
(2)代理模式在RMI這種的體現
遠程代理的內部機制是這樣的:
下面的RMI執行個體,其實就是一個代理模式的應用。
4.RMI的簡單一實例
RMI的開發步驟如下:
先建立遠程介面及聲明遠程方法,注意這是實現雙方通訊的介面,需要繼承Remote
開發一個類來實現遠程介面及遠程方法,值得注意的是實作類別需要繼承UnicastRemoteObject
通過javac命令編譯檔案,通過java -server 命令註冊服務,啟動遠程對象
最後用戶端尋找遠程對象,並調用遠程方法
(1)服務端遠程介面
建立遠程介面SearchService,介面必須繼承Remote類,每一個定義方法都要拋出RemoteException異常
import java.rmi.Remote;import java.rmi.RemoteException; public interface SearchService extends Remote { public User findUser(String id) throws RemoteException;;}
(2)建立SearchServiceImpl實現遠程介面,注意此為遠程對象實作類別,需要繼承UnicastRemoteObject
import java.rmi.RemoteException;public class SearchServiceImpl implements SearchService{ /** * 拋出RemoteException * @throws RemoteException */ public SearchServiceImpl() throws RemoteException { super(); } @Override public User findUser(String id) throws RemoteException { /** * 類比尋找返回資料 */ User user=new User(id,"Tom","18歲"); return user; } }
(3)為服務建立一個Model層,此對象需要遠程傳輸,所以必須實現implements Serializable序列化介面,也就是可以在client和server端進行傳輸的可序列化對象
建立User類,用於資料轉送:
public class User implements Serializable{ private static final long serialVersionUID = 1L; private String id; private String name; private String age; public User(String id,String name,String age){ this.id=id; this.name=name; this.age=age; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String toString(){ StringBuffer sb=new StringBuffer(); sb.append("~使用者id-"+id); sb.append("~使用者姓名-"+id); sb.append("~使用者年齡-"+id); return sb.toString(); } }
(4)建立伺服器端,在伺服器端註冊RMI通訊連接埠與通訊路徑
import java.net.MalformedURLException;import java.rmi.Naming;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry; /** * @author BingYue */public class Process { public static void main(String[] args){ try { SearchService searchService=new SearchServiceImpl(); //註冊通訊連接埠 Registry registry=LocateRegistry.createRegistry(5678); //註冊通訊路徑 Naming.rebind("rmi://127.0.0.1:5678/searchService", searchService); System.out.println("Service Start!"); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } }
(5)建立用戶端,匯入上面的實體類和介面,通過Naming.lookup()的方式調用
如果使用IDE,就可以建立一個項目,Client代碼如下:
import java.rmi.Naming; /** * @author BingYue */public class Client { public static void main(String[] args){ try { //調用遠程對象,注意RMI路徑與介面必須與伺服器配置一致 SearchService searchService=(SearchService)Naming.lookup("rmi://127.0.0.1:5678/searchService"); User user=searchService.findUser("100"); System.out.println(user.toString()); } catch (Exception e) { e.printStackTrace(); } }}
(6)分別運行服務端和用戶端,獲得遠程調用結果
(1)Remote介面:是一個不定義方法的標記介面
Public interface Remote{
}
Java的RMI遠程方法調用實現和應用