參考
https://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-arch2.html
http://www.cnblogs.com/wxisme/p/5296441.html
http://blog.csdn.net/qb2049_xg/article/details/3278672
http://classfoo.com/ccby/article/p1wgbVn
http://www.cnblogs.com/yin-jingyu/archive/2012/06/14/2549361.html
RMI與RPC
RMI在我看來更像Java專屬的RPC,或者說純面向對象的RPC。跟RPC一樣是分布式非常重要的內容,也是Java消息中間件的基礎。
RMI原理
本質就是在兩處同步一個Java對象(可以基於 JDK
本身的對象序列化或者基於 HTTP
協議的數據序列化)A 和 A!,但其中一個Java對象A進行方法調用時,通過RMI的代理能力轉發給另一個Java對象A!進行執行,並將A!的執行結果作為A的執行結果。這樣就實現了,運算流程的分布式計算。一般這兩個對象存在於兩台機器上。
- 將可以遠程調用的對象進行序列化,然后綁定到RMI Server(被調方,運行者)中作為存根(stub)
- RMI Client 會先去下載stub反序列化然后發起client調用,RMI 底層(RMI Interface Layer & Transport Layer)會講請求參數封裝發送到RMI Server
- RMI Server 接收到封裝的參數,傳遞給樁(skeleton),由樁解析參數並且以參數調用對應的存根()stub方法。
- 存根方法在RMI Server執行完畢之后,返回結果將被RMI底層封裝並傳輸給RMI Client(也就是主調方,調用者)
目前的Java版本已經不需要創建skeleton,也不需要rmic來編譯stub了,但是我學習的時候還是使用的rmic編譯的stub,所以示例也是這樣做的。
RMI Server編寫
編寫RMI Server Interface,這個也會被用在客戶端(RMI Client),供客戶端使用。列出了開放遠程調用的接口
1 package org.lyh.server; 2 3 import java.rmi.Remote; 4 import java.rmi.RemoteException; 5 6 /** 7 * Created by lvyahui on 2016/4/22. 8 */ 9 public interface ITimeServer extends Remote { 10 long getServerTime() throws RemoteException; 11 int add(int a,int b) throws RemoteException; 12 }
編寫時間Server 接口
1 package org.lyh.server.impl; 2 3 import org.lyh.server.ITimeServer; 4 5 import java.rmi.RemoteException; 6 import java.rmi.server.RMIClientSocketFactory; 7 import java.rmi.server.RMIServerSocketFactory; 8 import java.rmi.server.UnicastRemoteObject; 9 10 /** 11 * Created by lvyahui on 2016/4/22. 12 */ 13 public class TimeServer extends UnicastRemoteObject implements ITimeServer { 14 15 public TimeServer(int port) throws RemoteException { 16 super(port); 17 } 18 19 public TimeServer() throws RemoteException { 20 } 21 22 public TimeServer(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException { 23 super(port, csf, ssf); 24 } 25 26 @Override 27 public long getServerTime() throws RemoteException { 28 return System.currentTimeMillis(); 29 } 30 31 @Override 32 public int add(int a, int b) throws RemoteException { 33 return a + b; 34 } 35 }
將編寫好的Server實現對象綁定到Java RMI的名字服務上
1 package org.lyh; 2 3 import org.lyh.server.impl.TimeServer; 4 5 import java.net.MalformedURLException; 6 import java.rmi.Naming; 7 import java.rmi.RemoteException; 8 9 public class Main { 10 public static void main(String[] args) { 11 try { 12 TimeServer timeServer = new TimeServer(); 13 /* 綁定到JVM 的 RMI Server上*/ 14 // Naming.bind("t1",timeServer); 15 // Naming.rebind("t1",timeServer); 16 /* 當RMI注冊server是指定了端口時或者不在本機運行時,需要這樣寫*/ 17 Naming.rebind("//localhost/t1",timeServer); 18 System.out.println("Bind is Finish"); 19 } catch (RemoteException e) { 20 e.printStackTrace(); 21 } catch (MalformedURLException e) { 22 e.printStackTrace(); 23 } 24 } 25 }
這時,要用rmic進一步編譯TimeServer.class文件,得到TimeServer_stub.class 存根對象文件。如果不用rmic編譯的方式,也可以通過寫代碼的方式獲取stub。
因為我是使用IDEA開發的,所以我進入了RMI\out\production\RMI目錄編譯,執行rmic org.lyh.server.impl.TimeServer
、
這樣會在相同目錄下生成TimeServer_stub.class 文件
然后啟動RMI名字注冊服務,執行 rmiregistry 命令(jdk\bin下的一個腳本),可以執行 rmiregistry port執行端口,否則默認就是1099
下面可以執行org.lyh.Main@main方法了,將存根綁定(注冊)到RMI 名字服務上,名字為t1
RMI Client編寫
RMI client就簡單了,拉取存根,然后發起調用,調用被傳輸到Server執行,並獲取到執行結果,返回結果直接由接口方法return得到。
1 package org.lyh.client; 2 3 import org.lyh.server.ITimeServer; 4 5 import java.net.MalformedURLException; 6 import java.rmi.Naming; 7 import java.rmi.NotBoundException; 8 import java.rmi.RemoteException; 9 10 /** 11 * Created by lvyahui on 2016/4/22. 12 */ 13 public class TimeClient { 14 public static void main(String[] args) { 15 try { 16 ITimeServer iTimeServer = (ITimeServer) Naming.lookup("rmi://192.168.18.1/t1"); 17 System.out.println(iTimeServer.getServerTime()); 18 System.out.println(iTimeServer.add(3,7)); 19 } catch (NotBoundException e) { 20 e.printStackTrace(); 21 } catch (MalformedURLException e) { 22 e.printStackTrace(); 23 } catch (RemoteException e) { 24 e.printStackTrace(); 25 } 26 } 27 }
為了更加真實,我將這個Client上傳到虛擬機上編譯運行