深究Java中的RMI底層原理__Java

來源:互聯網
上載者:User

前言:隨著一個系統被使用者認可,業務量、請求量不斷上升,那麼單機系統必然就無法滿足了,於是系統就慢慢走向分布式了,隨之而來的是系統之間“溝通”的障礙。一般來說,解決系統之間的通訊可以有兩種方式:即遠程調用和訊息。RMI(Remote Method Invocation)就是遠程調用的一種方式,也是這篇文章主要介紹的。


一、RMI的一個簡單樣本

這個樣本拆分為服務端和用戶端,放在兩個idea項目中,並且通過了單機和雙機兩種環境的測試,是真正意義上的分布式應用。

項目結構

服務端應用: Server

主程式:        com.jnu.wwt.entry.Server

服務介面:     com.jnu.wwt.service.IOperation

服務實現:     com.jnu.wwt.service.impl.OperationImpl

用戶端應用: Client

主程式:        com.jnu.wwt.entry.Client

服務介面:    com.jnu.wwt.service.IOperation


源碼:

Server.java

/** * Created by wwt on 2016/9/14. */public class Server {    public static void main(String args[]) throws Exception{        //以1099作為LocateRegistry接收用戶端請求的連接埠,並註冊服務的映射關係        Registry registry=LocateRegistry.createRegistry(1099);        IOperation iOperation=new OperationImpl();        Naming.rebind("rmi://127.0.0.1:1099/Operation",iOperation);        System.out.println("service running...");    }}

IOperation.java(服務端和用戶端各需要一份)

/** * 服務端介面必須實現java.rmi.Remote * Created by wwt on 2016/9/14. */public interface IOperation extends Remote{    /**     * 遠程介面上的方法必須拋出RemoteException,因為網路通訊是不穩定的,不能吃掉異常     * @param a     * @param b     * @return     */    int add(int a, int b) throws RemoteException;}

OperationImpl.java

/** * Created by wwt on 2016/9/14. */public class OperationImpl extends UnicastRemoteObject implements IOperation{    public OperationImpl() throws RemoteException {        super();    }    @Override    public int add(int a, int b) throws RemoteException{        return a+b;    }}

Client.java

/** * Created by wwt on 2016/9/15. */public class Client {    public static void main(String args[]) throws Exception{        IOperation iOperation= (IOperation) Naming.lookup("rmi://127.0.0.1:1099/Operation");        System.out.println(iOperation.add(1,1));    }}

運行結果

先運行Server應用,服務就起來了。然後切換到Client應用,點擊運行,Client調用Server的服務,返回結果。




二、RMI做了些什麼

現在我們先忘記Java中有RMI這種東西。假設我們需要自己實現上面例子中的效果,怎麼辦呢。可以想到的步驟是:

編寫服務端服務,並將其通過某個服務機的連接埠暴露出去供用戶端調用。 編寫用戶端程式,用戶端通過指定服務所在的主機和連接埠號碼、將請求封裝並序列化,最終通過網路通訊協定發送到服務端。 服務端解析和還原序列化請求,調用服務端上的服務,將結果序列化並返回給用戶端。 用戶端接收並還原序列化服務端返回的結果,反饋給使用者。

這是大致的流程,我們不難想到,RMI其實也是幫我們封裝了一些細節而通用的部分,比如序列化和還原序列化,串連的建立和釋放等,下面是RMI的具體流程:


這裡涉及到幾個新概念:

Stub和Skeleton:這兩個的身份是一致的,都是作為代理的存在。用戶端的稱作Stub,服務端的稱作Skeleton。要做到對程式員屏蔽遠程方法調用的細節,這兩個代理是必不可少的,包括網路連接等細節。

Registry:顧名思義,可以認為Registry是一個“註冊所”,提供了服務名到服務的映射。如果沒有它,意味著用戶端需要記住每個服務所在的連接埠號碼,這種設計顯然是不優雅的。


三、走進RMI原理之前,先來看看用到的類及其階層和主要的方法。





哪裡看不懂隨時回來看看結構。。。開始了



四、一步步解剖RMI的底層原理
服務端啟動Registry服務

Registry registry=LocateRegistry.createRegistry(1099);
從上面這句代碼入手,追溯下去,可以探索服務端建立了一個RegistryImpl對象,這裡做了一個判斷。如果服務端指定的連接埠號碼是1099並且系統開啟了安全管理器,那麼可以在限定的許可權集內(listen和accept)繞過系統的安全校正。反之則必須進行安全校正。這裡純粹是為了效率起見。真正做的事情在setUp()方法中,繼續看下去。
public RegistryImpl(final int var1) throws RemoteException {    if(var1 == 1099 && System.getSecurityManager() != null) {        try {            AccessController.doPrivileged(new PrivilegedExceptionAction() {                public Void run() throws RemoteException {                    LiveRef var1x = new LiveRef(RegistryImpl.id, var1);                    RegistryImpl.this.setup(new UnicastServerRef(var1x));                    return null;                }            }, (AccessControlContext)null, new Permission[]{new SocketPermission("localhost:" + var1, "listen,accept")});        } catch (PrivilegedActionException var3) {            throw (RemoteException)var3.getException();        }    } else {        LiveRef var2 = new LiveRef(id, var1);        this.setup(new UnicastServerRef(var2));    }}
setUp()方法將指向正在初始化的RegistryImpl對象的遠端參照ref(RemoteRef)賦值為傳入的UnicastServerRef對象,這裡涉及了向上轉型。然後繼續移交UnicastServerRef的exportObject()方法。
private void setup(UnicastServerRef var1) throws RemoteException {    this.ref = var1;    var1.exportObject(this, (Object)null, true);}
進入UnicastServerRef的exportObject()方法。可以看到,這裡首先為傳入的RegistryImpl建立一個代理,這個代理我們可以推斷出就是後面服務於用戶端的RegistryImpl的Stub對象。然後將UnicastServerRef的skel(skeleton)對象設定為當前RegistryImpl對象。最後用skeleton、stub、UnicastServerRef對象、id和一個boolean值構造了一個Target對象,也就是這個Target對象基本上包含了全部的資訊。調用UnicastServerRef的ref(LiveRef)變數的exportObject()方法。
public Remote exportObject(Remote var1, Object var2, boolean var3) throws RemoteException {    Class var4 = var1.getClass();    Remote var5;    try {        var5 = Util.createProxy(var4, this.getClientRef(), this.forceStubUse);    } catch (IllegalArgumentException var7) {        throw new ExportException("remote object implements illegal remote interface", var7);    }    if(var5 instanceof RemoteStub) {        this.setSkeleton(var1);    }    Target var6 = new Target(var1, this, var5, this.ref.getObjID(), var3);    this.ref.exportObject(var6);    this.hashToMethod_Map = (Map)hashToMethod_Maps.get(var4);    return var5;}
到上面為止,我們看到的都是一些變數的賦值和建立工作,還沒有到串連層,這些引用對象將會被Stub和Skeleton對象使用。接下來就是串連層上的了。追溯LiveRef的exportObject()方法,很容易找到了TCPTransport的exportObject()方法。這個方法做的事情就是將上面構造的Target對象暴露出去。調用TCPTransport的listen()方法,listen()方法建立了一個ServerSocket,並且啟動了一條線程等待用戶端的請求。接著調用父類Transport的exportObject()將Target對象存放進ObjectTable中。
public void exportObject(Target var1) throws RemoteException {    synchronized(this) {        this.listen();        ++this.exportCount;    }    boolean var2 = false;    boolean var12 = false;    try {        var12 = true;        super.exportObject(var1);        var2 = true;        var12 = false;    } finally {        if(var12) {            if(!var2) {                synchronized(this) {                    this.decrementExportCount();                }            }        }    }    if(!var2) {        synchronized(this) {            this.decrementExportCount();        }    }}
到這裡,我們已經將RegistryImpl對象建立並且起了服務等待用戶端的請求。 用戶端擷取服務端Rgistry代理

IOperation iOperation= (IOperation) Naming.lookup("rmi://127.0.0.1:1099/Operation");
從上面的代碼看起,容易追溯到LocateRegistry的getRegistry()方法。這個方法做的事情是通過傳入的host和port構造RemoteRef對象,並建立了一個本地代理。可以通過Debug功能發現,這個代理對象其實是RegistryImpl_Stub對象。這樣用戶端便有了服務端的RegistryImpl的代理(取決於ignoreStubClasses變數)。但注意此時這個代理其實還沒有和服務端的RegistryImpl對象關聯,畢竟是兩個VM上面的對象,這裡我們也可以猜測,代理和遠端Registry對象之間是通過socket訊息來完成的。

public static Registry getRegistry(String host, int port,                                   RMIClientSocketFactory csf)    throws RemoteException{    Registry registry = null;    if (port <= 0)        port = Registry.REGISTRY_PORT;    if (host == null || host.length() == 0) {        // If host is blank (as returned by "file:" URL in 1.0.2 used in        // java.rmi.Naming), try to convert to real local host name so        // that the RegistryImpl's checkAccess will not fail.        try {            host = java.net.InetAddress.getLocalHost().getHostAddress();        } catch (Exception e) {            // If that failed, at least try "" (localhost) anyway...            host = "";        }    }    LiveRef liveRef =        new LiveRef(new ObjID(ObjID.REGISTRY_ID),                    new TCPEndpoint(host, port, csf, null),                    false);    RemoteRef ref =        (csf == null) ? new UnicastRef(liveRef) : new UnicastRef2(liveRef);    return (Registry) Util.createProxy(RegistryImpl.class, ref, false);}


服務端建立服務物件 從OperationImpl的建構函式看起。調用了父類UnicastRemoteObject的構造方法,追溯到UnicastRemoteObject的私人方法exportObject()。這裡做了一個判斷,判斷服務的實現是不是UnicastRemoteObject的子類,如果是,則直接賦值其ref(RemoteRef)對象為傳入的UnicastServerRef對象。反之則調用UnicastServerRef的exportObject()方法。這裡我們是第一種情況。

private static Remote exportObject(Remote obj, UnicastServerRef sref)    throws RemoteException{    // if obj extends UnicastRemoteObject, set its ref.    if (obj 
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.