RMI服务启动原理

作者: 逐梦々少年 | 来源:发表于2019-11-14 00:14 被阅读0次

    上一篇我们简单了解了RPC及其概念,并且实现了RMI框架进行服务发布,并且用系统自带的注册中心和代
    码启动注册中心的方式将服务注册到了注册中心中,并且实现了两种方式的服务消费,那么我们不禁好奇,
    这个由JDK官方早期就开发简易实现的RPC框架,如何实现的服务注册?注册中心启动过程是如何做的?如
    何实现的服务消费?那么带着这些疑问,开启本篇文章的学习之旅
    

    RegistryImpl服务端启动原理

    还记得上篇我们手动启动RMI的注册中心的时候编写的代码吗?

    Registry registry = LocateRegistry.createRegistry(9999);
    

    没错就是这一句,经过上篇文章学习,我们知道createRegistry调用以后会启动一个RMI的注册中心,那么我们就从这个方法开始剖析吧:

    public static Registry createRegistry(int port) throws RemoteException {
            return new RegistryImpl(port);
    }
    

    从这里可以看出来createRegistry方法其实内部创建了一个RegistryImpl

    public RegistryImpl(final int var1) throws RemoteException {
            //如果端口为默认的1099并且java安全管理器不为空
            if (var1 == 1099 && System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                        public Void run() throws RemoteException {
                            LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
                            //向上转型将ref引用对象构建并且传递给setup方法
                            RegistryImpl.this.setup(new UnicastServerRef(var1x));
                            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));
            }
    
        }
    

    我们可以看到根据端口号创建了ref引用对象,并且传递进了setup方法,我们来看卡setup方法:

    private void setup(UnicastServerRef var1) throws RemoteException {
            this.ref = var1;//传来的ref引用
            var1.exportObject(this, (Object)null, true);//ref对象调用了exportObject方法
        }
    

    setup方法里面调用了ref引用的exportObject方法,并且传递了三个参数,这里的三个参数分别为Remote类型实例、Object类型参数、以及一个布尔值参数,这里的Remote类型的实例在上篇我们曾介绍过,此类接口的实例都是RMI的远程对象,那么我们来看看exportObject方法做了什么操作:

    public Remote exportObject(Remote var1, Object var2, boolean var3) throws RemoteException {
            //获取远程对象的class
            Class var4 = var1.getClass();
    
            Remote var5;
            try {
                //通过代理的方式创建ref引用
                var5 = Util.createProxy(var4, this.getClientRef(), this.forceStubUse);
            } catch (IllegalArgumentException var7) {
                throw new ExportException("remote object implements illegal remote interface", var7);
            }
            //如果创建出来的引用属于RemoteStub类及其子类,调用setSkeleton方法
            if (var5 instanceof RemoteStub) {
                this.setSkeleton(var1);
            }
            //根据传递进来的远程对象、当前类实例、当前ref引用的id以及传递的boolean参数来创建出Target实例,并再次传递到exportObject方法中
            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;
        }
    

    这里我们可以注意到两个重要的点,其中一个就是当代理引用类型属于RemoteStub及其子类的时候,会将代理传递给setSkeleton方法,而setSkeleton方法仅仅是校验当前代理的class是否存在,不存在将会创建一个Skeleton,即

    this.setSkeleton(var1);
    

    而另外一个点即为通过代理等参数合集创建出Target实例后,再次将Target实例传入ref引用实例的exportObject方法,即

    this.ref.exportObject(var6);//这里的ref就是我们上一步setUp方法传递来的LiveRef实例
    

    到目前为止依然都是RMI的赋值和创建工作,接下来开始我们就会进入到连接层相关的内容了,接着我们开始 LiveRef类的exportObject方法,代码如下:

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

    这里可以看到内部调用了ep的exportObject方法,这个ep对象则是LiveRef对象构造的时候传递来的Endpoint实例对象,那么初始化是什么时候调用的呢?还记得RegistryImpl类的构造方法吗?内部实现了LiveRef类的创建并且传递给UnicastServerRef类进行包装将包装后的UnicastServerRef类实例传递到setUp方法中,即:

     LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
     RegistryImpl.this.setup(new UnicastServerRef(var1x));
    

    默认的Endpoint接口的实现类为TCPEndpoint,而TCPEndpoint类的exportObject方法内部又调用了自身的transport对象实例的exportObject方法,我们跟进transport类(TCPTransport)的exportObject方法,代码如下:

    public void exportObject(Target var1) throws RemoteException {
            synchronized(this) {
                this.listen();//开启监听,根据传递来的port,创建sokect连接,创建TCPTransport实例
                ++this.exportCount;
            }
    
            boolean var2 = false;
            boolean var12 = false;
    
            try {
                var12 = true;
                super.exportObject(var1);//调用父类的exportObject方法
                var2 = true;
                var12 = false;
            } finally {
                if (var12) {
                    if (!var2) {
                        synchronized(this) {
                            this.decrementExportCount();//释放一个exportCount,并且关闭对应的socket连接
                        }
                    }
    
                }
            }
    
            if (!var2) {
                synchronized(this) {
                    this.decrementExportCount();
                }
            }
    
        }
    

    可以看到这里就是真正的创建Target对象暴露出去,调用TCPTransport的listen()方法,listen()方法创建了一个ServerSocket,并且启动了一条线程等待客户端的请求。接着调用父类Transport的exportObject()将Target对象存放进ObjectTable中 ,至此我们已经成功的将RegistryImpl 对象创建并且监听等待来自客户端的请求

    RMI服务端绑定原理

    现在我们已经创建好了RMI的注册中心,是该创建服务端绑定到注册中心了

    Naming.rebind("user", userHandler);
    

    我们通过Naming类的方式进行绑定,这样我们可以顺便看看如何调用第二种启动方式的,即:

    registry.rebind("user", userHandler);
    

    我们进入Naming类的rebind方法,代码如下:

    public static void rebind(String name, Remote obj)
            throws RemoteException, java.net.MalformedURLException
        {
            ParsedNamingURL parsed = parseURL(name);//根据name解析url
            Registry registry = getRegistry(parsed);//获取registry实例
    
            if (obj == null)
                throw new NullPointerException("cannot bind to null");
    
            registry.rebind(parsed.name, obj);//这里使用持有的registry实例的rebind方法进行服务绑定操作
        }
    

    可以看到这里通过getRegistry(parsed)方法获取了registry实例对象,然后调用了registry.rebind方法进行服务绑定操作,没错,这里就是第二种服务启动方式的代码啦!而getRegistry(parsed)方法做了什么呢?其实就是调用了LocateRegistry.getRegistry(parsed.host, parsed.port);方法,这也是我们第二种创建服务的时候获取registry实例的方式,那么一切都知道了,我们来看看服务如何绑定的吧,进入registry实例的rebind方法(RegistryImpl类):

    public void rebind(String var1, Remote var2) throws RemoteException, AccessException {
            checkAccess("Registry.rebind");
            this.bindings.put(var1, var2);//将url和服务引用存入hashTable
        }
    

    可以看出来这里就是将解析出来的url和远程对象存入定义好的hashTable中,bindings则是提前定义好的HashTable,如下:

    private Hashtable<String, Remote> bindings = new Hashtable(101);
    

    如此,服务便存入注册中心中进行绑定了

    RMI客户端请求原理

    我们知道客户端请求调用服务端是通过registry.lookup方式获取服务端的远程对象引用,代码如下:

    UserHandler handler = (UserHandler) registry.lookup("user");
    

    首先我们来到RegistryImpl_Stub 类的lookup方法,代码如下:

    public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
            try {
                RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);//从ref引用的newCall方法获取RemoteCall实例
    
                try {
                    ObjectOutput var3 = var2.getOutputStream();//从RemoteCall实例中获取输出流,将数据写入
                    var3.writeObject(var1);
                } catch (IOException var18) {
                    throw new MarshalException("error marshalling arguments", var18);
                }
    
                super.ref.invoke(var2);//将RemoteCall实例传递给ref引用的invoke方法
    
                Remote var23;
                try {
                    ObjectInput var6 = var2.getInputStream();//获取输入流,读取传来的数据
                    var23 = (Remote)var6.readObject();
                } catch (IOException var15) {
                    throw new UnmarshalException("error unmarshalling return", var15);
                } catch (ClassNotFoundException var16) {
                    throw new UnmarshalException("error unmarshalling return", var16);
                } finally {
                    super.ref.done(var2);
                }
    
                return var23;
            } catch (RuntimeException var19) {
                throw var19;
            } catch (RemoteException var20) {
                throw var20;
            } catch (NotBoundException var21) {
                throw var21;
            } catch (Exception var22) {
                throw new UnexpectedException("undeclared checked exception", var22);
            }
        }
    

    这里涉及到了ref对象实例(RemoteRef )的newCall方法,获取RemoteCall实例,此实例将用来进行与客户端的数据传输交互,并且将当前的实例传递给ref的invoke方法,所以我们接着看看RemoteRefnewCall方法(UnicastRef 类),代码如下:

     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();//从ref引用对象中开启连接
    
            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);//将我们的服务信息(端口等)传递给StreamRemoteCall实例进行构建
    
                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;
            }
        }
    

    可以看出来这里有很多ref引用的操作,而这个ref对象是什么呢?没错就是之前创建的LiveRef实例,通过该实例内部的Channel管道创建连接,进行数据交互,至此RMI的服务注册与调用流程我们已经学习完毕

    相关文章

      网友评论

        本文标题:RMI服务启动原理

        本文链接:https://www.haomeiwen.com/subject/rfrlictx.html