分佈式專題-03分佈式通信框架RMI原理分析

前言

本節我們主要講分佈式通信框架RMI,並對其原理進行分析

什麼是 RPC

RPC(Remote Procedure Call,遠程過程調用),一般用來實現部署在不同機器上的系統之間的方法調用,使得程序能夠像訪問本地系統資源一樣,通過網絡傳輸去訪問遠端系統資源;對於客戶端來說, 傳輸層使用什麼協議,序列化、反序列化都是透明的

實現JAVA RMI

RMI 全稱是 remote method invocation – 遠程方法調用,一種用於遠程過程調用的應用程序編程接口,是純 java 的網絡分佈式應用系統的核心解決方案之一。

RMI 目前使用 Java 遠程消息交換協議 JRMP(Java Remote Messageing Protocol)進行通信,由於 JRMP 是專爲 Java 對象制定的,是分佈式應用系統的百分之百純 java 解決方案,用 Java RMI 開發的應用系統可以部署在任何支持 JRE 的平臺上,缺點是,由於 JRMP 是專門爲 java 對象指定的,因此 RMI 對於非 JAVA 語言開發的應用系統的支持不足,不能與非 JAVA 語言書寫的對象進行通信

在這裏插入圖片描述
我們簡單的試用一下RMI遠程通信:
首先我們在server端建立服務,使其繼承Remote:
IHelloService:

public interface IHelloService extends Remote {

    String sayHello(String msg) throws RemoteException;
}

然後來一個實現類,使其繼承UnicastRemoteObject:

public class HelloServiceImpl extends UnicastRemoteObject implements IHelloService{

    protected HelloServiceImpl() throws RemoteException {
       // super();
    }

    @Override
    public String sayHello(String msg) throws RemoteException{
        return "Hello,"+msg;
    }
}

然後服務端開放服務:

public class Server {

    public static void main(String[] args) {
        try {

            //已經發布了一個遠程對象
            IHelloService helloService=new HelloServiceImpl();

            LocateRegistry.createRegistry(1099);

            //註冊中心 key - value
            Naming.rebind("rmi://127.0.0.1/Hello",helloService);
            System.out.println("服務啓動成功");
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

現在啓動一下試試:
在這裏插入圖片描述
啓動成功,我們實現client:

public class ClientDemo {

    public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
        IHelloService helloService=
                (IHelloService)Naming.lookup("rmi://127.0.0.1/Hello");
        // HelloServiceImpl實例(HelloServiceImpl_stub)
        // RegistryImpl_stub
        System.out.println(helloService.sayHello("Mic"));
    }
}

運行一下:
在這裏插入圖片描述
通信成功!

Tips:
遠程對象必須實現 UnicastRemoteObject,這樣才能保證客戶端訪問獲得遠程對象時,該遠程對象會把自身的一個拷貝以 Socket 形式傳輸給客戶端,客戶端獲得的拷貝稱爲 “stub” ,而服務器端本身已經存在的遠程對象成爲“skeleton”,此時客戶端的 stub 是客戶端的一個代理,用於與服務器端進行通信,而 skeleton 是服務端的一個代理,用於接收客戶端的請求之後調用遠程方法來響應客戶端的請求

RPC 框架原理

遠程對象發佈

我們根據剛纔的RMI實現的DEMO注意到了兩個關鍵的類:

  • Remote
  • UnicastRemoteObject

在RMI裏源碼是如何關聯的呢?
在這裏插入圖片描述

遠程引用層

服務端類圖:
在這裏插入圖片描述

RMI通信原理分析

發佈遠程對象

發佈遠程對象,看到上面的類圖可以知道,這個地方會發布兩個遠程對象,一個是 RegistryImpl、另外一個是我們自己寫的 RMI 實現類對象;
從 HelloServiceImpl 的構造函數看起。調用了父類 UnicastRemoteObject 的 構 造 方 法 , 追 溯 到 UnicastRemoteObject 的私有方法 exportObject()。這裏做 了 一 個 判 斷 , 判 斷 服 務 的 實 現 是 不 是 UnicastRemoteObject 的子類,如果是,則直接賦值其 ref (RemoteRef)對象爲傳入的 UnicastServerRef 對象。反之則調用 UnicastServerRef 的 exportObject()方法。

 IHelloService helloService=new HelloServiceImpl();

因爲 HelloServiceImpl 繼承了 UnicastRemoteObject,所以在服務啓動的時候,會通過 UnicastRemoteObject 的構造方法把該對象進行發佈

public class HelloServiceImpl extends UnicastRemoteObject {

    protected HelloServiceImpl() throws RemoteException {
       super();
    }

進入父類:

  private static Remote exportObject(Remote obj, UnicastServerRef sref)
        throws RemoteException
    {
        // if obj extends UnicastRemoteObject, set its ref.
        if (obj instanceof UnicastRemoteObject) {
            ((UnicastRemoteObject) obj).ref = sref;
        }
        //創建UnicastServerRef對象,對象內又引用了LiveRef(Tcp通信)
        return sref.exportObject(obj, null, false);
    }

看exportObject:

public Remote exportObject(Remote var1, Object var2, boolean var3) throws RemoteException {
        Class var4 = var1.getClass();

        Remote var5;
        try {
        //創建遠程代理類,該代理是對OperationImpl對象的代理,getClientRef提供的InvocationHandler中提供了Tcp連接
            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);
        }
//包裝實際對象,並將其暴露在TCP端口上,等待客戶端調用
        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;
    }

服務端啓動 Registry 服務

  LocateRegistry.createRegistry(1099);

從上面這段代碼入手,開始往下看。可以發現服務端創建了一個 RegistryImpl 對象,這裏做了一個判斷,如果服務端指定的端口是 1099 並且系統開啓了安全管理器,那麼就可以在限定的權限集內繞過系統的安全校驗。這裏純粹是爲 了 提 高 效 率 , 真 正 的 邏 輯 在 this.setup(new UnicastServerRef())這個方法裏面

    public RegistryImpl(final int var1) throws RemoteException {
        this.bindings = new Hashtable(101);
        if (var1 == 1099 && System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                    public Void run() throws RemoteException {
                        LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
                        RegistryImpl.this.setup(new UnicastServerRef(var1x, (var0) -> {
                            return RegistryImpl.registryFilter(var0);
                        }));
                        return null;
                    }
                }, (AccessControlContext)null, 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, RegistryImpl::registryFilter));
        }

    }

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(RegistryImpl_Stub)對象。然後將 UnicastServerRef 的 skel(skeleton)對象設置爲當前 RegistryImpl 對象。最後用 skeleton、stub、UnicastServerRef 對象、id 和一個
boolean 值構造了一個 Target 對象,也就是這個 Target 對象基本上包含了全部的信息,等待 TCP 調用。調用 UnicastServerRef 的 ref(LiveRef)變量的 exportObject() 方法。
【var1=RegistryImpl ; var 2 = null ; var3=true】 LiveRef 與 TCP 通信的類

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 對象創建並且起了服務等待客戶端的請求。

客戶端獲取服務端 Registry 代理

IHelloService helloService=
             (IHelloService)Naming.lookup("rmi://127.0.0.1/Hello");

從上面的代碼看起,容易追溯到 LocateRegistry 的getRegistry()方法。這個方法做的事情是通過傳入的 host 和 port 構造 RemoteRef 對象,並創建了一個本地代理。這個代理對象其實是 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) {
 try {
                host = java.net.InetAddress.getLocalHost().getHostAddress();
            } catch (Exception e) {
                // If that failed, at least try "" (localhost) anyway...
                host = "";
            }
        }
        
        //與TCP通信的類
        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);

//創建遠程代理類,引用lieveref,好讓動態代理時,能進行tcp通信
        return (Registry) Util.createProxy(RegistryImpl.class, ref, false);
    }

回到服務端:

  //註冊中心 key - value
            Naming.rebind("rmi://127.0.0.1/Hello",helloService);

調用 RegistryImpl_Stub 的 ref ( RemoteRef )對象的newCall()方法,將 RegistryImpl_Stub 對象傳了進去,不要忘了構造它的時候我們將服務器的主機端口等信息傳了進去,也就是我們把服務器相關的信息也傳進了 newCall()方法。newCall()方法做的事情簡單來看就是建立了跟遠程 RegistryImpl 的 Skeleton 對象的連接。(不要忘了上面我們說到過服務端通過 TCPTransport 的 exportObject()方法等待着客戶端的請求)

 public RemoteCall newCall(RemoteObject var1, Operation[] var2, int var3, long var4) throws RemoteException {
        clientRefLog.log(Log.BRIEF, "get connection");
        Connection var6 = this.ref.getChannel().newConnection();

        try {
            clientRefLog.log(Log.VERBOSE, "create call context");
            if (clientCallLog.isLoggable(Log.VERBOSE)) {
                this.logClientCall(var1, var2[var3]);
            }

            StreamRemoteCall var7 = new StreamRemoteCall(var6, this.ref.getObjID(), var3, var4);

            try {
                this.marshalCustomCallData(var7.getOutputStream());
            } catch (IOException var9) {
                throw new MarshalException("error marshaling custom call data");
            }

            return var7;
        } catch (RemoteException var10) {
            this.ref.getChannel().free(var6, false);
            throw var10;
        }
    }

連接建立之後自然就是發送請求了。我們知道客戶端終究只是擁有 Registry 對象的代理,而不是真正地位於服務端的 Registry 對象本身,他們位於不同的虛擬機實例之中,無法直接調用。必然是通過消息進行交互的。看看 super.ref.invoke() 這 裏 做 了 什 麼 ? 容 易 追 溯 到 StreamRemoteCall 的 executeCall()方法。看似本地調用,但其實很容易從代碼中看出來是通過 tcp 連接發送消息到服務端。由服務端解析並且處理調用。

回到客戶端:

 IHelloService helloService= (IHelloService)Naming.lookup("rmi://127.0.0.1/Hello");

看一下源碼是怎麼實現這個流程的:

   public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
        try {
            RemoteCall var2 = this.ref.newCall(this, operations, 2, 4905912898345647071L);

            try {
                ObjectOutput var3 = var2.getOutputStream();
                var3.writeObject(var1);
            } catch (IOException var17) {
                throw new MarshalException("error marshalling arguments", var17);
            }

            this.ref.invoke(var2);

            Remote var22;
            try {
                ObjectInput var4 = var2.getInputStream();
                var22 = (Remote)var4.readObject();
            } catch (IOException var14) {
                throw new UnmarshalException("error unmarshalling return", var14);
            } catch (ClassNotFoundException var15) {
                throw new UnmarshalException("error unmarshalling return", var15);
            } finally {
                this.ref.done(var2);
            }

            return var22;
        } catch (RuntimeException var18) {
            throw var18;
        } catch (RemoteException var19) {
            throw var19;
        } catch (NotBoundException var20) {
            throw var20;
        } catch (Exception var21) {
            throw new UnexpectedException("undeclared checked exception", var21);
        }
    }

invoke反射:

public void invoke(RemoteCall var1) throws Exception {
        try {
            clientRefLog.log(Log.VERBOSE, "execute call");
            var1.executeCall();
        } catch (RemoteException var3) {
            clientRefLog.log(Log.BRIEF, "exception: ", var3);
            this.free(var1, false);
            throw var3;
        } catch (Error var4) {
            clientRefLog.log(Log.BRIEF, "error: ", var4);
            this.free(var1, false);
            throw var4;
        } catch (RuntimeException var5) {
            clientRefLog.log(Log.BRIEF, "exception: ", var5);
            this.free(var1, false);
            throw var5;
        } catch (Exception var6) {
            clientRefLog.log(Log.BRIEF, "exception: ", var6);
            this.free(var1, true);
            throw var6;
        }
    }

至此,我們已經將客戶端的服務查詢請求發出了。

瞭解Java RMI

服務端接收客戶端的服務查詢請求並返回給客戶端結果
這裏我們繼續跟蹤server端代碼的服務發佈代碼,一步步往上面翻。按照下圖順序
在這裏插入圖片描述
入口:

       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;
    }

LiveRef.class:

      public void exportObject(Target var1) throws RemoteException {
        this.ep.exportObject(var1);
    }

TCPEndpoint.class:

  public void exportObject(Target var1) throws RemoteException {
        this.transport.exportObject(var1);
    }

exportObject:

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();
            }
        }

    }

在 TCP 協議層發起 socket 監聽,並採用多線程循環接收請求:TCPTransport.AcceptLoop(this.server)

 private void listen() throws RemoteException {
        assert Thread.holdsLock(this);

        TCPEndpoint var1 = this.getEndpoint();
        int var2 = var1.getPort();
        if (this.server == null) {
            if (tcpLog.isLoggable(Log.BRIEF)) {
                tcpLog.log(Log.BRIEF, "(port " + var2 + ") create server socket");
            }

            try {
                this.server = var1.newServerSocket();
                Thread var3 = (Thread)AccessController.doPrivileged(new NewThreadAction(new TCPTransport.AcceptLoop(this.server), "TCP Accept-" + var2, true));
                var3.start();
            } catch (BindException var4) {
                throw new ExportException("Port already in use: " + var2, var4);
            } catch (IOException var5) {
                throw new ExportException("Listen failed on port: " + var2, var5);
            }
        } else {
            SecurityManager var6 = System.getSecurityManager();
            if (var6 != null) {
                var6.checkListen(var2);
            }
        }

    }

AcceptLoop:

 private class AcceptLoop implements Runnable {
        private final ServerSocket serverSocket;
        private long lastExceptionTime = 0L;
        private int recentExceptionCount;

        AcceptLoop(ServerSocket var2) {
            this.serverSocket = var2;
        }

        public void run() {
            try {
                this.executeAcceptLoop();
            } finally {
                try {
                    this.serverSocket.close();
                } catch (IOException var7) {
                    ;
                }

            }

        }

繼續通過線程池來處理 socket 接收到的請求

    public void run() {
            Thread var1 = Thread.currentThread();
            String var2 = var1.getName();

            try {
                var1.setName("RMI TCP Connection(" + TCPTransport.connectionCount.incrementAndGet() + ")-" + this.remoteHost);
                AccessController.doPrivileged(() -> {
                    this.run0();
                    return null;
                }, TCPTransport.NOPERMS_ACC);
            } finally {
                var1.setName(var2);
            }

        }

下面這個 run0 方法裏面做了一些判斷,具體的功能是幹嘛不太清楚,我猜想是對不同的協議來做處理。我們的這個案例中,會走到如下的代碼中來。最終調用TCPTransport.this.handleMessages(var14, true);

  case 75:
                        var10.writeByte(78);
                        if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
                            TCPTransport.tcpLog.log(Log.VERBOSE, "(port " + var2 + ") suggesting " + this.remoteHost + ":" + var11);
                        }

                        var10.writeUTF(this.remoteHost);
                        var10.writeInt(var11);
                        var10.flush();
                        String var16 = var5.readUTF();
                        int var17 = var5.readInt();
                        if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
                            TCPTransport.tcpLog.log(Log.VERBOSE, "(port " + var2 + ") client using " + var16 + ":" + var17);
                        }

                        var12 = new TCPEndpoint(this.remoteHost, this.socket.getLocalPort(), var1.getClientSocketFactory(), var1.getServerSocketFactory());
                        var13 = new TCPChannel(TCPTransport.this, var12);
                        var14 = new TCPConnection(var13, this.socket, (InputStream)var4, var9);
                        TCPTransport.this.handleMessages(var14, true);
                        return;

這個地方也做了判斷,你們如果不知道怎麼走的話,直接在這裏加斷點就知道。這裏會走到 case 80 的段落,執行serviceCall()這個方法

void handleMessages(Connection var1, boolean var2) {
        int var3 = this.getEndpoint().getPort();

        try {
            DataInputStream var4 = new DataInputStream(var1.getInputStream());

            do {
                int var5 = var4.read();
                if (var5 == -1) {
                    if (tcpLog.isLoggable(Log.BRIEF)) {
                        tcpLog.log(Log.BRIEF, "(port " + var3 + ") connection closed");
                    }

                    return;
                }

                if (tcpLog.isLoggable(Log.BRIEF)) {
                    tcpLog.log(Log.BRIEF, "(port " + var3 + ") op = " + var5);
                }

                switch(var5) {
                case 80:
                    StreamRemoteCall var6 = new StreamRemoteCall(var1);
                    if (!this.serviceCall(var6)) {
                        return;
                    }
                    break;
                case 81:
                case 83:
                default:
                    throw new IOException("unknown transport op " + var5);
                case 82:
                    DataOutputStream var7 = new DataOutputStream(var1.getOutputStream());
                    var7.writeByte(83);
                    var1.releaseOutputStream();
                    break;
                case 84:
                    DGCAckHandler.received(UID.read(var4));
                }
            } while(var2);

        } catch (IOException var17) {
            if (tcpLog.isLoggable(Log.BRIEF)) {
                tcpLog.log(Log.BRIEF, "(port " + var3 + ") exception: ", var17);
            }

        } finally {
            try {
                var1.close();
            } catch (IOException var16) {
                ;
            }

        }
    }

一步一步我們找到了 Transport 的 serviceCall()方法,這個方 法 是 關 鍵 。 瞻 仰 一 下 主 要 的 代 碼 , 到ObjectTable.getTarget()爲止做的事情是從 socket 流中獲取 ObjId,並通過 ObjId 和 Transport 對象獲取 Target 對象,這裏的 Target 對象已經是服務端的對象。再借由 Target 的派發器 Dispatcher ,傳入參數服務實現和請求對象RemoteCall,將請求派發給服務端那個真正提供服務的 RegistryImpl 的 lookUp()方法,這就是 Skeleton 移交給具體實現的過程了,Skeleton 負責底層的操作。

 public boolean serviceCall(final RemoteCall var1) {
        try {
            ObjID var39;
            try {
                var39 = ObjID.read(var1.getInputStream());
            } catch (IOException var33) {
                throw new MarshalException("unable to read objID", var33);
            }

            Transport var40 = var39.equals(dgcID) ? null : this;
            Target var5 = ObjectTable.getTarget(new ObjectEndpoint(var39, var40));
            final Remote var37;
            if (var5 != null && (var37 = var5.getImpl()) != null) {
                final Dispatcher var6 = var5.getDispatcher();
                var5.incrementCallCount();

                boolean var8;
                try {
                    transportLog.log(Log.VERBOSE, "call dispatcher");
                    final AccessControlContext var7 = var5.getAccessControlContext();
                    ClassLoader var41 = var5.getContextClassLoader();
                    ClassLoader var9 = Thread.currentThread().getContextClassLoader();

                    try {
                        setContextClassLoader(var41);
                        currentTransport.set(this);

                        try {
                            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                                public Void run() throws IOException {
                                    Transport.this.checkAcceptPermission(var7);
                                    var6.dispatch(var37, var1);
                                    return null;
                                }
                            }, var7);
                            return true;
                        } catch (PrivilegedActionException var31) {
                            throw (IOException)var31.getException();
                        }
                    } finally {
                        setContextClassLoader(var9);
                        currentTransport.set((Object)null);
                    }
                } catch (IOException var34) {
                    transportLog.log(Log.BRIEF, "exception thrown by dispatcher: ", var34);
                    var8 = false;
                } finally {
                    var5.decrementCallCount();
                }

                return var8;
            }

            throw new NoSuchObjectException("no such object in table");
        } catch (RemoteException var36) {
            RemoteException var2 = var36;
            if (UnicastServerRef.callLog.isLoggable(Log.BRIEF)) {
                String var3 = "";

                try {
                    var3 = "[" + RemoteServer.getClientHost() + "] ";
                } catch (ServerNotActiveException var30) {
                    ;
                }

                String var4 = var3 + "exception: ";
                UnicastServerRef.callLog.log(Log.BRIEF, var4, var36);
            }

            try {
                ObjectOutput var38 = var1.getResultStream(false);
                UnicastServerRef.clearStackTraces(var2);
                var38.writeObject(var2);
                var1.releaseOutputStream();
            } catch (IOException var29) {
                transportLog.log(Log.BRIEF, "exception thrown marshalling exception: ", var29);
                return false;
            }
        }

        return true;
    }

所以在上面demo的客戶端通過:

IHelloService helloService= (IHelloService)Naming.lookup("rmi://127.0.0.1/Hello");

先會創建一個 RegistryImpl_Stub 的代理類,通過這個代理類進行 socket 網絡請求,將 lookup 發送到服務端,服務端通過接收到請求以後,通過服務端的 RegistryImpl_Stub (Skeleton),執行 RegistryImpl 的 lookUp。而服務端的 RegistryImpl 返回的就是服務端的 HeloServiceImpl 的實現類

    public static Remote lookup(String name)
        throws NotBoundException,
            java.net.MalformedURLException,
            RemoteException
    {
        ParsedNamingURL parsed = parseURL(name);
        Registry registry = getRegistry(parsed);

        if (parsed.name == null)
            return registry;
        return registry.lookup(parsed.name);
    }

客 戶 端 獲 取 通 過 lookUp() 查 詢 獲 得 的 客 戶 端 HelloServiceImpl 的 Stub 對象

客 戶 端 通 過 Lookup 查 詢 獲 得 的 是 客 戶 端 HelloServiceImpl 的 Stub 對象(這一塊我們看不到,因爲這塊由 Skeleton 爲我們屏蔽了),然後後續的處理仍然是通過 HelloServiceImpl_Stub 代理對象通過 socket 網絡請求 到 服 務 端 , 通 過 服 務 端 的 HelloServiceImpl_Stub(Skeleton) 進行代理,將請求通過 Dispatcher 轉發到對應的服務端方法獲得結果以後再次通過 socket 把結果返回到客戶端;

RMI 做了什麼?

根據上面的源碼閱讀,實際上我們看到的應該是有兩個代理 類 , 一 個 是 RegistryImpl 的 代 理 類 和 我 們 HelloServiceImpl 的代理類。

一定要說明,在 RMI Client 實施正式的 RMI 調用前,它必須通過 LocateRegistry 或者 Naming 方式到 RMI 註冊表尋找要調用的 RMI 註冊信息。找到 RMI 事務註冊信息後, Client 會從 RMI 註冊表獲取這個 RMI Remote Service 的 Stub 信息。這個過程成功後,RMI Client 才能開始正式的調用過程。

另外要說明的是 RMI Client 正式調用過程,也不是由 RMI Client 直接訪問 Remote Service,而是由客戶端獲取的 Stub 作爲 RMI Client 的代理訪問 Remote Service 的代理Skeleton,如上圖所示的順序。也就是說真實的請求調用是在 Stub-Skeleton 之間進行的。

Registry 並不參與具體的 Stub-Skeleton 的調用過程,只負責記錄“哪個服務名”使用哪一個 Stub,並在 Remote Client 詢問它時將這個 Stub 拿給 Client(如果沒有就會報錯)。
在這裏插入圖片描述

實現自己的 RPC 框架

完整代碼見本節後記部分分享在github的地址

在這裏插入圖片描述

  • 服務端

先來一個接口:

public interface IGpHello {

    String sayHello(String msg);
}

接口的實現類:

public class GpHelloImpl implements IGpHello{
    @Override
    public String sayHello(String msg) {
        return "Hello , "+msg;
    }
}

發佈一個遠程服務:

public class RpcServer {
    //創建一個線程池
    private static final ExecutorService executorService=Executors.newCachedThreadPool();

    public void publisher(final Object service,int port){
        ServerSocket serverSocket=null;
        try{
            //啓動一個服務監聽
            serverSocket=new ServerSocket(port);

            //循環監聽
            while(true){
                //監聽服務
                Socket socket=serverSocket.accept();
                //通過線程池去處理請求
                executorService.execute(new ProcessorHandler(socket,service));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

處理socket請求:

public class ProcessorHandler implements Runnable{

    private Socket socket;
    //服務端發佈的服務
    private Object service;

    public ProcessorHandler(Socket socket, Object service) {
        this.socket = socket;
        this.service = service;
    }

    @Override
    public void run() {
        //處理請求
        ObjectInputStream inputStream=null;
        try {
            //獲取客戶端的輸入流
            inputStream=new ObjectInputStream(socket.getInputStream());
            //反序列化遠程傳輸的對象RpcRequest
            RpcRequest request=(RpcRequest) inputStream.readObject();
            //通過反射去調用本地的方法
            Object result=invoke(request);

            //通過輸出流講結果輸出給客戶端
            ObjectOutputStream outputStream=new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(result);
            outputStream.flush();
            inputStream.close();
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(inputStream!=null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private Object invoke(RpcRequest request) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        //一下均爲反射操作,目的是通過反射調用服務
        Object[] args=request.getParameters();
        Class<?>[] types=new Class[args.length];
        for(int i=0;i<args.length;i++){
            types[i]=args[i].getClass();
        }
        Method method=service.getClass().getMethod(request.getMethodName(),types);
        return method.invoke(service,args);
    }
}

傳輸對象:

public class RpcRequest implements Serializable {

    private static final long serialVersionUID = -9100893052391757993L;
    private String className;
    private String methodName;
    private Object[] parameters;

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }
}

  • 客戶端
    協議傳輸:
public class RemoteInvocationHandler implements InvocationHandler {
    private String host;
    private int port;

    public RemoteInvocationHandler(String host, int port) {
        this.host = host;
        this.port = port;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //組裝請求
        RpcRequest request=new RpcRequest();
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        request.setParameters(args);
        //通過tcp傳輸協議進行傳輸
        TCPTransport tcpTransport=new TCPTransport(this.host,this.port);
        //發送請求
        return tcpTransport.send(request);
    }
}

TCPTransport:

public class TCPTransport {

    private String host;

    private int port;

    public TCPTransport(String host, int port) {
        this.host = host;
        this.port = port;
    }

    //創建一個socket連接
    private Socket newSocket(){
        System.out.println("創建一個新的連接");
        Socket socket;
        try{
            socket=new Socket(host,port);
            return socket;
        }catch (Exception e){
            throw new RuntimeException("連接建立失敗");
        }
    }

    public Object send(RpcRequest request){
        Socket socket=null;
        try {
            socket = newSocket();
            //獲取輸出流,將客戶端需要調用的遠程方法參數request發送給
            ObjectOutputStream outputStream=new ObjectOutputStream
                    (socket.getOutputStream());
            outputStream.writeObject(request);
            outputStream.flush();
            //獲取輸入流,得到服務端的返回結果
            ObjectInputStream inputStream=new ObjectInputStream
                    (socket.getInputStream());
            Object result=inputStream.readObject();
            inputStream.close();
            outputStream.close();
            return result;

        }catch (Exception e ){
            throw new RuntimeException("發起遠程調用異常:",e);
        }finally {
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

代理:

public class RpcClientProxy {

    /**
     * 創建客戶端的遠程代理。通過遠程代理進行訪問
     * @param interfaceCls
     * @param host
     * @param port
     * @param <T>
     * @return
     */
    public <T> T clientProxy(final Class<T>
                                     interfaceCls,
                             final String host,final int port){
        //使用到了動態代理。
        return (T)Proxy.newProxyInstance(interfaceCls.getClassLoader(),
                new Class[]{interfaceCls},new RemoteInvocationHandler(host,port));
    }
}

傳輸對象,同服務端

  • 測試
    服務端:
public class ServerDemo {
    public static void main(String[] args) {
        IGpHello iGpHello=new GpHelloImpl();
        RpcServer rpcServer=new RpcServer();
        rpcServer.publisher(iGpHello,8888);
    }
}

客戶端:

public class ClientDemo {

    public static void main(String[] args) {
        RpcClientProxy rpcClientProxy=new RpcClientProxy();

        IGpHello hello=rpcClientProxy.clientProxy
                (IGpHello.class,"localhost",8888);
        System.out.println(hello.sayHello("mic"));
    }
}

分別啓動,控制檯查看效果
在這裏插入圖片描述
通信成功!

後記

本節代碼地址:github

節目預告:
下一小節,我們主要講分佈式協調服務

  • 初步認識Zookeeper
  • 瞭解Zookeeper的核心原理
  • 分佈式協調服務Zookeeper實踐及與原理分析
  • Zookeeper實踐之配合註冊中心完成RPC手寫
發佈了57 篇原創文章 · 獲贊 5 · 訪問量 5007
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章