博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
RMI原理揭秘之远程对象
阅读量:6153 次
发布时间:2019-06-21

本文共 9407 字,大约阅读时间需要 31 分钟。

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

  1. 定义远程接口:

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

 3. 定义实现类:

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.服务端:

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.客户端:

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基类

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出来的对象类型:

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类的继承是如何影响这些的呢? 我们来探索一下。

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基类的构造方法将远程对象发布到一个随机端口上,当然端口也可以指定。

 protected UnicastRemoteObject(int port) throws RemoteException    {        this.port = port;        exportObject((Remote) this, port);    }
  public static Remote exportObject(Remote obj, int port)        throws RemoteException    {        return exportObject(obj, new UnicastServerRef(port));    }
 /**     * 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, null, false);    }

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

 /**     * 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方法:

  /**     * 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发布了多少个对象。

super.exportObject(target);
/**     * 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:

 /**     * 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生成所有没有源代码,我们只能从反编译的代码中查看一点信息:

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

/**     * 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对象中,序列化到客户端。

转载地址:http://pdffa.baihongyu.com/

你可能感兴趣的文章
批量删除oracle中以相同类型字母开头的表
查看>>
Java基础学习总结(4)——对象转型
查看>>
BZOJ3239Discrete Logging——BSGS
查看>>
SpringMVC权限管理
查看>>
spring 整合 redis 配置
查看>>
cacti分组发飞信模块开发
查看>>
浅析LUA中游戏脚本语言之魔兽世界
查看>>
飞翔的秘密
查看>>
Red Hat 安装源包出错 Package xxx.rpm is not signed
查看>>
编译安装mysql-5.6.16.tar.gz
查看>>
活在当下
查看>>
每天进步一点----- MediaPlayer
查看>>
PowerDesigner中CDM和PDM如何定义外键关系
查看>>
跨域-学习笔记
查看>>
the assignment of reading paper
查看>>
android apk 逆向中常用工具一览
查看>>
MyEclipse 报错 Errors running builder 'JavaScript Validator' on project......
查看>>
Skip List——跳表,一个高效的索引技术
查看>>
Yii2单元测试初探
查看>>
五、字典
查看>>