天天看点

Java远程方法调用RMI的实现

                                   RMI的实现

远程方法调用基本流程

     远程方法,可以让本地访问远程的对象。当需要访问远程对象的时候,一般需要在客户端或者客户堆中建立一些辅助对象,这些辅助对象使得客户感觉如同调用本地对象的方法一样。客户辅助对象乔装为服务对象,当客户调用客户辅助对象上的方法,客户辅助对象会联系服务器,传送方法调用信息(方法名称,变量等),然后就等待服务器的返回。

    在服务器端,服务辅助对象从客户辅助对象中接受请求(Socket连接),将调用的信息解包,然后调用服务器端上的真正服务对象的真正方法。对于服务对象来说,调用是本地的。

    服务辅助对象从服务中得到返回值,将它打包,然后就运回到客户辅助对象,客户辅助对象对信息解包,最后将返回值交给客户对象。

RMI概况

    RMI提供了客户辅助对象和服务辅助对象,为客户辅助对象和服务对象创建相同的方法。由于调用远程方法的过程中使用的是I/O和网络,而这些都有可能发生异常。

   RMI将客户辅助对象成为stub(桩),服务辅助对象称为skeleton(骨架)。

制作远程服务

   步骤一 制作远程接口:定义了可以供客户远程调用的方法。stub和实际服务都实现该接口

   步骤二 制作远程接口的实现:真正的实际工作的服务

   步骤三 利用rmic产生stub

   步骤四 启动RMI:registry,rmiregistry如同一个清单,客户可以从中查到代理的位置

   步骤五 开始启动远程服务:开始启动远程服务,一般服务实现类会去实例化一个服务实例,然后将这个服务注册到RMI registry。

   步骤一 制作远程接口

   一般首先扩展自Remote,Remote仅仅是一个记号接口,没有方法,在该接口中声明所有的方法都会抛出RemoteException,必须确定变量和返回值是原语性或者可序列化的,如果是复杂对象则需要对于负责对象先序列化。即让该复杂对象继承Serializable。

   步骤二 制作远程接口的实现

   具体的服务必须实现远程接口,同时一般直接扩展UnicastRemoteObject,设计一个不带变量的构造器,同时声明RemoteException。(当类被实例化的时候,超类的构造器总是先被调用,如果超类的构造器抛出了异常,那么在子类的构造器中必须抛出异常,而UnicastRemoteObject的构造器抛出了异常。)可以在实现类中注册该服务,但是必须要先启动rmiregistry。 Naming.rebind("name",myremoteImpl)注册到rmiregistry中。

   步骤三 产生stub

   rmic 远程实现类名

   步骤四 执行rmiregistry

   步骤五 启动服务

   启动服务,一般可以在实现类的内部启动或者单独类中启动,主要就是注册到rmiregistry

在远程虚拟机上启动服务器一般要包括两个服务器,一个是远程对象本身,还有一个是允许本地客户端下载远程对象引用的注册表。必须要先执行rmiregistry目的开启注册表,然后再启动远程对象注册到该注册表中

  以上是服务器端的配置,在客户端,只需要该远程对象接口的类或者.class文件,以及利用rmic生成的stud文件。

代码实例

远程接口

import java.rmi.Remote;

import java.rmi.RemoteException;

public interface MyRemote extends Remote {

        public String sayHelloBaby() throws RemoteException;

}

远程接口实现

import java.rmi.server.UnicastRemoteObject;

public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {

  protected MyRemoteImpl() throws RemoteException {

  }

  @Override

  public String sayHelloBaby() throws RemoteException {

    // TODO Auto-generated method stub

    return "Congratulations to you!";

启动远程服务,必须先执行rmiregistry,

在windows下执行start  rmiregistry

import java.io.IOException;

import java.rmi.Naming;

public class RMIServer {

  public static void main(String[] args) {

                try{

                  MyRemote f=new MyRemoteImpl();

                  Naming.rebind("myfirst", f);

                  System.out.println("OK");

                }catch(IOException e)

                {

                  System.err.println(e);

                }

客户端服务

public class MyRemoteClient {

    public static void main(String[] args) {

        // TODO Auto-generated method stub

         try

          {

               MyRemote service=(MyRemote)Naming.lookup("rmi://127.0.0.1/FirstRemote");

               String res=service.sayHelloBaby();

               System.out.println("return="+res);

            }catch(Exception e)

             {

               e.printStackTrace();

           }

    }

整个目录截图如下

客户端所需要类:F:\JavaRMI\client

服务器所需要类:F:\JavaRMI\server

执行顺序是:

第一步:编译所有的类,在此要将远程对象接口复制到客户端下

第二步:执行rmic MyRemoteImpl,这个目的生成桩字节码

第三步:将该桩字节码复制一份到客户端目录下

第四步:启动rmiregistry服务,必须要保证桩字节码在classpath路径下

本次桩字节码在F:\JavaRMI\server,所以启动rmiregistry之前先设置路径

第五步:启动远程对象服务即RMIServer

第六步:执行客户端程序

在使用RMI的过程中,必须要注意:

1) 在启动远程服务之前先必须要启动rmiregistry,必须要保证桩在类路径中,

   可以在启动rmiregistry之前设置

2)必须要让变量的返回值的类型成为可序列化的类型

3)客户端必须要有Stub类和远程对象接口类。

问题解决:

在执行服务器远程对象运行的中,可能会出现java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:

这是因为在执行start rmiregistry 命令前必须确保 MyRemoteImpl_Stub.class(留意包

名)包含在了CLASSPATH配置的路径中,所以必须在在执行start rmiregistry命令前执行  

set CLASSPATH=%CLASSPATH%;F:\JavaRMI\server

本文转自 zhao_xiao_long 51CTO博客,原文链接:http://blog.51cto.com/computerdragon/1178053