`
古镇灵狐
  • 浏览: 3869 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

RMI原理揭秘之远程对象

    博客分类:
  • Java
 
阅读更多

转载:http://guojuanjun.blog.51cto.com/277646/1423392/

 

  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原理及应用详解

    1. **导出远程对象**: 开发者创建实现远程接口的类,并实例化一个远程对象,然后使用`java.rmi.Naming.rebind()`或`UnicastRemoteObject.exportObject()`将其导出到网络上。 2. **注册远程对象**: 将导出的对象注册...

    RMI远程方法调用RMI远程方法调用

    RMI远程方法调用是Java平台上的一个关键特性,它允许Java对象在不同的JVM之间进行通信,从而实现分布式计算。RMI的核心理念是让开发者能够像调用本地方法一样调用远程对象的方法,简化了分布式系统的设计和实现。 *...

    RMI实现的远程调用

    4. ** stubs 和 skeletons**:在RMI中,远程对象的引用在客户端是通过stubs(桩)表示的,而实际的远程调用则由serverside的skeleton(骨架)处理。现代的JVM已经不再需要手动生成stubs和skeletons,它们由Java...

    rmi原理与入门demo.zip

    远程方法调用(Remote Method Invocation,RMI)是Java提供的一种分布式计算技术,它允许一个Java对象调用网络另一端的Java对象的方法。RMI在Java应用中的作用是构建跨网络的、分布式的对象系统,使得开发者可以像...

    Java RMI demo多对象

    - ** stub 和 skeleton**:RMI系统使用代理(stub)对象作为远程对象的本地代表,而skeleton对象则在远程服务器上接收和分发调用。在现代Java版本中,这些组件由JDK自动处理。 2. **RMI步骤**: - **注册远程对象...

    Java RMI(远程方法调用)Demo

    4. **客户端(Client)**:客户端通过RMI机制获取远程对象的引用,然后调用远程方法。这通常涉及到反序列化过程,因为远程方法的调用结果需要通过网络传输。 5. **服务器端(Server)**:服务器端运行远程对象,...

    java rmi远程方法调用 客户端

    Java RMI(Remote Method Invocation,远程方法调用)是Java平台提供的一种分布式计算技术,它允许在不同的Java虚拟机之间透明地调用对象的方法。在RMI架构中,客户端能够像调用本地对象一样调用远程服务器上的对象...

    RMI原理及实现(JAVA)

    远程方法调用(RMI,Remote Method Invocation)是Java提供的一种用于在分布式环境中调用远程对象的方法。RMI的核心思想是使客户端可以像调用本地对象一样调用远程对象,实现这一目标的关键在于隐藏了网络通信的细节...

    Java RMI 远程方法调用

    Java RMI(Remote Method Invocation,远程方法调用)是Java平台提供的一种分布式计算技术,它允许在不同的Java虚拟机之间进行方法调用,仿佛这些方法是在本地对象上执行一样。这个技术极大地简化了构建分布式应用的...

    RMI原理及实现

    远程方法调用(Remote Method Invocation,简称RMI)是Java平台上的一个核心特性,它允许Java对象在不同的JVM之间进行交互。RMI为分布式计算提供了一个简单而强大的模型,使得开发者可以像调用本地方法一样调用远程...

    RMI远程调用

    远程方法调用(Remote Method Invocation,简称RMI)是Java平台提供的一种机制,它允许一个Java对象调用另一个在不同Java虚拟机(JVM)上的对象的方法。RMI是Java分布式计算的核心技术,主要用于构建分布式系统,...

    动态代理与RMI远程调用

    在提供的`动态代理与RMI远程调用.ppt`中,可能会详细解释这两个概念,通过PPT的讲解和实例,可以更直观地理解动态代理和RMI的工作原理。同时,`src`目录下的源码文件则提供了具体的实现示例,读者可以通过阅读代码,...

    rmi远程接口调用

    RMI(Remote Method Invocation,远程方法调用)是Java平台上的一个核心特性,它允许Java对象在不同的JVM之间进行通信,实现分布式计算。RMI系统由两部分组成:服务端(Server)和客户端(Client)。服务端提供远程...

    rmi上传文件到远程服务器

    远程接口定义了可以在远程对象上调用的方法,这些方法必须是声明为`remote`的,并可能抛出`java.rmi.RemoteException`。远程实现是实现了远程接口的具体类,它包含了实际的业务逻辑。最后,RMIServer是运行远程对象...

    RMI实现远程文件传输实例

    远程方法调用(Remote Method Invocation,RMI)是Java平台提供的一种用于在分布式环境中执行远程对象的方法调用机制。RMI允许一个Java对象在一台计算机上执行另一个Java对象的方法,即使该对象位于另一台计算机上。...

    RMI原理及实现,ppt格式

    - RMI是Java中的一个特性,允许对象在不同的网络节点之间进行交互,执行远程对象的方法。 - RMI调用对最终用户是透明的,意味着用户无需关心方法实际在哪里执行,只需像调用本地方法一样调用远程方法。 - 所有...

    ssd8 exercise4 RMI远程方法调用

    4. **导出远程对象**:使用`java.rmi.Naming`或`java.rmi.server.UnicastRemoteObject`类将实现的远程对象导出到网络,使其可被远程访问。 5. **客户端调用**:客户端通过RMIServer获取远程对象的引用,然后就可以...

    使用rmi进行远程调用

    4. **导出远程对象**:使用`java.rmi.server.UnicastRemoteObject`的`exportObject()`方法将`BankServiceImpl`实例导出为远程对象,使其可以被远程调用。 5. **启动RMID服务**:确保服务器上运行着RMID守护进程,它...

Global site tag (gtag.js) - Google Analytics