Bootstrap

分布式专题-分布式通信框架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手写
;