zoukankan      html  css  js  c++  java
  • RMI原理揭秘之远程对象

    讨论开始之前,我们先看看网上的一个例子,这个例子我腾抄了一分,没有用链接的方式,只是为了让大家看得方便,如有侵权,我立马***。

    1. 定义远程接口:

    2. 1
      2
      3
      4
      5
      6
      package com.guojje;
      import java.rmi.Remote;
      import java.rmi.RemoteException;
      public interface IHello extends Remote {
          public int helloWorld()throws RemoteException;
      }

     3. 定义实现类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.guojje;
    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
     
    public class Hello extends UnicastRemoteObject implements IHello {
        private static final long serialVersionUID = 1L;
        private int index = 0;
        protected Hello() throws RemoteException {
        }
        @Override
        public int helloWorld(){
            System.out.println("Hello!");
            return ++index;
        }
    }

     4.服务端:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.guojje;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
     
    public class HelloServer {
        public static void main(String args[]) {
            try {
                IHello rhello = new Hello();
                Registry registry = LocateRegistry.createRegistry(8888);
                registry.bind("test", rhello);
                System.out.println("Remote Hello Object is bound sucessfully!");
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    5.客户端:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.guojje;
    import java.rmi.Naming;
    public class HelloClient {
        public static void main(String args[]) {
            try {
                for (int i = 0; i < 5; i++) {
                    IHello rhello = (IHello) Naming
                            .lookup("rmi://localhost:8888/test");
                    System.out.println(rhello.helloWorld());
                }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    6.输出结果:  

    1)服务端输出:

     Remote Hello Object is bound sucessfully!
    Hello!
    Hello!
    Hello!
    Hello!
    Hello!

    2)客户端输出:

    0
    1
    2
    3
    4

    7.把实现类更改为不继承UnicastRemoteObject基类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.guojje;
    import java.io.Serializable;
    import java.rmi.RemoteException;
     
    public class Hello implements IHello,Serializable {
        private static final long serialVersionUID = 1L;
        private int index = 0;
        protected Hello() throws RemoteException {
        }
        @Override
        public int helloWorld(){
            System.out.println("Hello!");
            return ++index;
        }
    }

    8.输出结果:  

    1)服务端输出:

     Remote Hello Object is bound sucessfully!

    2)客户端输出:

    Hello!
    1
    Hello!
    1
    Hello!
    1
    Hello!
    1
    Hello!
    1

    这两个用例的输出结果来看,前一个用例index计数器一直在增加,且Hello!输出在服务端。这说明

    helloWorld方法执行是在服务端,客户端的每一次对象方法调用,都是对服务端对象的调用。

    而后一个用例就不同了。helloWorld方法执行是在客户端,且每次lookup出来的都是新的对象。

    我们看一下lookup出来的对象类型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.guojje;
    import java.rmi.Naming;
    public class HelloClient {
        public static void main(String args[]) {
            try {
                for (int i = 0; i < 5; i++) {
                    IHello rhello = (IHello) Naming
                            .lookup("rmi://localhost:8888/test");
                    System.out.println(rhello.getClass());
                    System.out.println(rhello.helloWorld());
                }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    实现类继承UnicastRemoteObject时,lookup出来的对象类型是$Proxy0,而不继承UnicastRemoteObject的类,对象类型是com.guojje.Hello。

    我们把继承UnicastRemoteObject类的对象叫做远程对象,我们lookup出来的对象,只是该远程对象的存根(Stub)对象,而远程对象永远在远方。客户端每一次的方法调用,最后都是仅有的那一个远程对象的方法调用。

    没有继承UnicastRemoteObject类的对象,同样可以bind到Registry,但lookup出来了对象却是远程对象

    经过序列化,然后到客户端反序列化出来的新的对象,后续的方法调用与远程对象再无关系。

    那UnicastRemoteObject类的继承是如何影响这些的呢? 我们来探索一下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.guojje;
    public class HelloServer {
        public static void main(String args[]) {
            try {
                new Hello();
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    仅仅new一个远程对象,运行这个程序,我们就发现进程不会退出。它hang在哪儿呢?

    jstack查看线程栈,发现有SocketAccept在监听:wKioL1OS0jnDzmo-AAIVRoxVLQ4285.jpg

    wKiom1OS0sqxo5iXAABQrHaFVmM834.jpg

    的确启动了一个监听端口。ServerSocket类上添加debug.得到调用栈如下:

    wKiom1OS0--zrDznAAG61A8Jrp4407.jpg

    UnicastRemoteObject基类的构造方法将远程对象发布到一个随机端口上,当然端口也可以指定。

    1
    2
    3
    4
    5
     protected UnicastRemoteObject(int port) throws RemoteException
        {
            this.port = port;
            exportObject((Remote) this, port);
        }
    1
    2
    3
    4
    5
      public static Remote exportObject(Remote obj, int port)
            throws RemoteException
        {
            return exportObject(obj, new UnicastServerRef(port));
        }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     /**
         * Exports the specified object using the specified server ref.
         */
        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;
            }
            return sref.exportObject(obj, nullfalse);
        }

    迎来一个重要的方法(UnicastServerRef.java):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
     /**
         * Export this object, create the skeleton and stubs for this
         * dispatcher.  Create a stub based on the type of the impl,
         * initialize it with the appropriate remote reference. Create the
         * target defined by the impl, dispatcher (this) and stub.
         * Export that target via the Ref.
         */
        public Remote exportObject(Remote impl, Object data,
                                   boolean permanent)
            throws RemoteException
        {
            Class implClass = impl.getClass();
            Remote stub;
     
            try {
                stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
            catch (IllegalArgumentException e) {
                throw new ExportException(
                    "remote object implements illegal remote interface", e);
            }
            if (stub instanceof RemoteStub) {
                setSkeleton(impl);
            }
     
            Target target =
                new Target(impl, this, stub, ref.getObjID(), permanent);
            ref.exportObject(target);
            hashToMethod_Map = hashToMethod_Maps.get(implClass);
            return stub;
        }

    这里的stub变量就是我们lookup出来的远程对象的存根对象。而target保留了远程对象的信息集合:

    对象,存根,objId等。

    接着看TCPTransport.java的exportObject方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
      /**
         * Export the object so that it can accept incoming calls.
         */
        public void exportObject(Target target) throws RemoteException {
            /*
             * Ensure that a server socket is listening, and count this
             * export while synchronized to prevent the server socket from
             * being closed due to concurrent unexports.
             */
            synchronized (this) {
                listen();
                exportCount++;
            }
     
            /*
             * Try to add the Target to the exported object table; keep
             * counting this export (to keep server socket open) only if
             * that succeeds.
             */
            boolean ok = false;
            try {
                super.exportObject(target);
                ok = true;
            finally {
                if (!ok) {
                    synchronized (this) {
                        decrementExportCount();
                    }
                }
            }
        }

    listen()启动监听。这里对TcpTransport加了同步,防止多个线程同时执行,同时也防止同一端口启动多次。

    一次TcpTransport会有一个监听端口,而一个端口上可能会发部多个远程对象。exportCount计录该TcpTransport发布了多少个对象。

    1
    super.exportObject(target);
    1
    2
    3
    4
    5
    6
    7
    /**
         * Export the object so that it can accept incoming calls.
         */
        public void exportObject(Target target) throws RemoteException {
            target.setExportedTransport(this);
            ObjectTable.putTarget(target);
        }

    ObjectTable为静态方法调用,那么所有的远程对象信息(target)都会放到这个对象表中。我们这个target包含了远程对象几乎所有的信息,找到他,就找到远程对象的存根对象。

    远程对象的发布,先说这么多。我们再看一下lookup是如何找到远程对象的存根的.

    当然先从远程对象的bind说起:

    wKioL1OS446jQG1iAAB55G2yZU8637.jpg

    RegistryImpl.java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     /**
         * Binds the name to the specified remote object.
         * @exception RemoteException If remote operation failed.
         * @exception AlreadyBoundException If name is already bound.
         */
        public void bind(String name, Remote obj)
            throws RemoteException, AlreadyBoundException, AccessException
        {
            checkAccess("Registry.bind");
            synchronized (bindings) {
                Remote curr = bindings.get(name);
                if (curr != null)
                    throw new AlreadyBoundException(name);
                bindings.put(name, obj);
            }
        }

    bindings里放得确实是远程对象本身,而不是他的存根。这是怎么回事?继续追究lookup方法。

    wKiom1OS3yOxvz0jAAHQmvM6xbM210.jpg

    RegistryImpl_Skel类是rmic生成所有没有源代码,我们只能从反编译的代码中查看一点信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    case 2// lookup(String)
        {
            java.lang.String $param_String_1;
            try {
            java.io.ObjectInput in = call.getInputStream();
            $param_String_1 = (java.lang.String) in.readObject();
            catch (java.io.IOException e) {
            throw new java.rmi.UnmarshalException("error unmarshalling arguments", e);
            catch (java.lang.ClassNotFoundException e) {
            throw new java.rmi.UnmarshalException("error unmarshalling arguments", e);
            finally {
            call.releaseInputStream();
            }
            java.rmi.Remote $result = server.lookup($param_String_1);
            try {
            java.io.ObjectOutput out = call.getResultStream(true);
            out.writeObject($result);
            catch (java.io.IOException e) {
            throw new java.rmi.MarshalException("error marshalling return", e);
            }
            break;
        }

    从网络流中读取第一个参数必须是lookup的字符串参数。然后调用服务端RegistryImpl对象lookup

    方法,该方法返回的的确是远程对象,而非远程对象的存根呀?

    问题的原因在于下面的序列化过程,继续看调用栈:wKiom1OS4PTwzJB8AAJXCHenxJw799.jpg

    看一个重要方法MarshalOutputStream.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
         * Checks for objects that are instances of java.rmi.Remote
         * that need to be serialized as proxy objects.
         */
        protected final Object replaceObject(Object obj) throws IOException {
            if ((obj instanceof Remote) && !(obj instanceof RemoteStub)) {
                Target target = ObjectTable.getTarget((Remote) obj);
                if (target != null) {
                    return target.getStub();
                }
            }
            return obj;
        }

    如果对象是java.rmi.Remote实例,则向对象表中找到该对象的Target,如果存在,则返回其存根对象。

    所以返回服务端的变成了远程对象的存根。先到此,后续再探索其方法调用,安全等相关问题。


    补充:

    其实Registry的实现类RegistryImpl也是个远程对象,这里registry.bind却没有进行远程调用。这是因为我是用LocateRegistry.createRegistry(8888)创建的远程对象,而不是通过LocateRegistry.getRegistry(8888)获取的远程对象,而getRegistry返回的是RegistryImpl的Stub.仅此而已。


    另外一次RMI调用,要创建两个连接,一个连接面向注册端口,即上面的8888端口。另一个面向服务端口。在这里这个服务端口是随机的。最后绑定在UnicastServerRef对象中,序列化到客户端。


    本文转自 anranran 51CTO博客,原文链接:http://blog.51cto.com/guojuanjun/1423392


  • 相关阅读:
    汇总国内开源站点镜像网站-20210813更新
    谈谈技术人的技术家园
    庖丁解码
    好的软件(软件工程)
    LeetCode 914卡盘分组
    《黑客与画家》读书笔记
    30岁的我给现在26岁的自己的想说的一些话
    毕业一年半,发现自己还是一个CURD boy
    LeetCode 120 Triangle
    疫情相关项目复盘
  • 原文地址:https://www.cnblogs.com/twodog/p/12138629.html
Copyright © 2011-2022 走看看