經過本文,能夠加深對Java RMI的理解,知道它的工做原理,怎麼使用等. 也爲了加深我本身的理解,故整理成文.不足之處,還望指出.java
RMI(RemoteMethodInvocation):遠程方法調用,顧名思義,經過遠程的方式調用非本地對象的方法並返回結果。使用遠程調用一般解決本地計算瓶頸問題,例如分佈式記算,最近很火的阿爾法狗人機大戰,聽說運算使用上千個CPU。socket
JRMP(java remote method protocol):java遠程方法協議,這是完成java到java遠程調用的協議,基於TCP協議。分佈式
stub與skeleton:這兩個概念下面會用到,這裏解釋下,skeleton是放在服務端的代理,它知道真正的對象在哪。stub是放在客戶端的代理,它記錄了查找和調用skeleton信息。理解成遠程對象引用也成.工具
容易混淆的概念:測試
遠程方法調用與遠程過程調用的區別:遠程方法調用是java獨有的,基於JRMP對象流協議實現,支持傳輸java序列化對象。遠程過程調用是基於socket技術實現的,不能傳輸java對象,socket套接字協議支持多種語言。它們都是基於TCP協議傳輸。遠程方法調用傳輸的是java序列化對象和基本數據類型,而遠程過程調用不支持傳輸對象。this
RMI調用模型:spa
從宏觀看,想要遠程調用須要作兩件事情,1,服務端向本地對象註冊表中註冊能被調用的遠程對象. 2,客戶端向遠程對象註冊表請求遠程對象的引用..net
先經過一個例子瞭解Java中RMI是怎麼用的,而後再根據代碼分析源碼是如何實現的.代理
1,先建立遠程對象接口,繼承自Remote(稍後源碼中有分析爲何要有這個接口)code
package remote.test; import java.rmi.Remote; import java.rmi.RemoteException; /** * 遠程接口,實現Remote * @author lxz * */ public interface IRemote extends Remote{ public String show()throws RemoteException;//聲明方法 }
2,接口實現,須要繼承UnicastRemoteObject類,等會分析
package remote.test; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; /** * 遠程接口實現,繼承UnicastRemoteObject * @author lxz * */ public class RemoteImpl extends UnicastRemoteObject implements IRemote{ public RemoteImpl()throws RemoteException{}//構造方法 public String show()throws RemoteException{//調用方法實現 System.out.println("進入"); System.out.println(this.toString()); return "遠程調用成功"; } }
3,服務端向本地端口1234對象註冊表註冊對象和它的名字
package remote.test; import java.net.MalformedURLException; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; /** * 服務端啓動,建立端口上的對象註冊列表,向對象註冊表中註冊遠程調用對象 * @author lxz * */ public class TestServer { public static void main(String[] args) throws MalformedURLException, RemoteException, AlreadyBoundException, InterruptedException { RemoteImpl r = new RemoteImpl();//建立遠程對象 Registry rr = LocateRegistry.createRegistry(1234); //建立1234端口上的對象註冊表,若是已經建立了就用getRegistry方法獲取 rr.bind("testrmi", r);//向註冊表中註冊對象 System.out.println(r.toString()); } }
4,根據JDK API,以上遠程服務就算搭建完畢了,下面經過客戶端調用測試
package remote.test; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; /** * 客戶端啓動,得到遠程的對象註冊表中的對象引用 * @author lxz * */ public class TestClient { public static void main(String[] args) throws MalformedURLException, RemoteException, NotBoundException { IRemote r = (IRemote) Naming.lookup("rmi://localhost:1234/testrmi");//獲取遠程1234端口對象註冊表中testrmi的stub String a = r.show();//調用引用的方法,實際上調用的是stub,由stub與服務端交互並返回結果 System.out.println(a); } }
執行結果以下:
------------------------------------------
服務端:
RemoteImpl[UnicastServerRef [liveRef: [endpoint:[192.168.1.253:58169](local),objID:[-60651394:1539d5944e6:-7fff, -6910034932968554489]]]]
客戶端:
遠程調用成功
------------------------------------------
這樣就完成了一個遠程對象註冊與遠程對象方法調用的完整例子. 如今根據這個例子來分析它爲何要繼承UnicastRemoteObject,實現Remote,向註冊表註冊等等.
首先遠程對象實現類中須要繼承UnicastRemoteObject類,UnicastRemoteObject具備註冊爲遠程對象,生成遠程引用的功能等,全部都已經被JDK封裝好了,不須要編寫,其中的實現有些是sun包開頭的,不公開.
UnicastRemoteObject繼承關係:
有了遠程對象實現類,看服務端的啓動邏輯,其中:
Registry rr = LocateRegistry.createRegistry(1234);
LocateRegistry類:用於建立或獲取某端口的對象註冊表
LocateRegistry.createRegistry:這個方法表示得到遠程對象註冊表引用,返回Registry對象
Registry:真正操做遠程對象註冊表的接口
接着,
rr.bind("testrmi", r);
利用Registry的對象,把剛剛建立的遠程對象註冊爲名稱testrmi. 這裏還有一種寫法,效果是同樣的.
LocateRegistry.createRegistry(1234); //建立,若是已經建立了就可省略這一句 Naming.bind("rmi://localhost:1234/testrmi", r);//須要帶上端口
Naming:與對象註冊表交互的工具類
上面是服務端從遠程對象建立到對象註冊的整個邏輯.客戶端調用的邏輯比較簡單,先經過Naming工具類獲取到遠程對象的引用之後,就能夠正常使用了
(IRemote) Naming.lookup("rmi://localhost:1234/testrmi");
這裏返回的"引用"和一般講的對象引用不一樣,是遠程對象的引用信息.拿到這個"引用"之後就能夠像使用真正的對象同樣調用其中的方法.
結束.