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服务启动原理

    RegistryImpl服务端启动原理 还记得上篇我们手动启动RMI的注册中心的时候编写的代码吗? 没错就是这一句...

  • RMI注意事项

    RMI服务接口 提供服务的RMI服务接口必须实现Remote接口 RMI服务启动 RMI服务端口 RMI需要两个端...

  • Dubbo系列1:Java RMI初见

    在正式开始Dubbo系列之前,先来介绍一下Java RMI服务; 一、Java RMI的原理 Java RMI是T...

  • 远程jConsole

    服务端设置: 1、服务启动设置参数: -Djava.rmi.server.hostname=192.168.1.9...

  • zabbix之jmx监控

    jmx监控,添加启动参数 -Djava.rmi.server.hostname参数为java服务所在服务器的ip(...

  • 分布式通信框架 - rmi

    1)什么是rmi 2)简单的实现rmi 3)rmi原理 4)手写rmi框架 进群:697699179可以获取Jav...

  • 【程序员笔记】RMI使用笔记

    本文章共分为三部分 RMI简介 RMI原理 RMI使用 RMI实战 下面详细介绍 一 RMI简介 远程方法调用(R...

  • Java RMI服务搭建

    什么是RMI? RMI:远程方法调用(Remote Method Invocation)。 如何建立RMI服务? ...

  • 在Spring中配置RMI

    使用RMI服务 创建RMI服务的基本步骤 编写一个服务实现类,类中的方法必须抛出java.rmi.RemoteEx...

  • fastjson漏洞利用备忘

    预备知识: 起rmi服务 用marshalsec-0.0.3-SNAPSHOT-all.jar起一个rmi服务。 ...

网友评论

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

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