Writing an RMI Server 写一个RMI服务端

这个计算引擎接受客户端的任务,运行任务并返回一些结果。这个服务器代码由一个接口和一个类组成。接口定义了可以从客户端调用的方法。基本上,接口定义了远程对象的客户端的视图。类提供了实现。


设计一个远程接口


这部分演示了Compter接口,提供了客户端和服务器端的连接。你也将了解RMI API,用来支持通信。


实现远程接口


这部分是实现了上述接口的类,因此实现了远程对象。这个类也实现了服务器部分,包括一个main方法,用来创建一个远程对象的实例,用RMI注册器注册,并设置安全管理。



设计一个远程接口


一个协议在这个计算引擎的代码中使任务可以提交到计算引擎,运行那些任务的计算引擎,并返回那些任务的结果给客户端。这些协议在由计算引擎支持的接口中。这个协议的远程通信如下图所示:



每个接口包含一个单一方法。计算引擎的远程接口,Compute,使任务提交到引擎中。客户端接口,Task,定义了电脑引擎如何提交任务。compute.Compute接口定义了远程访问的部分,即计算引擎自身。这是Compute接口的代码。


ackage compute;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Compute extends Remote {
    <T> T executeTask(Task<T> t) throws RemoteException;
}


通过扩展ava.rmi.Remote接口,Compute接口定义自己为一个这样的接口,它的方法可以从另一个Java虚拟机上调用。任何实现这个接口的对象时一个远程对象。


作为远程接口的成员,executeTask方法是一个远程方法。因此,这个接口必须抛出一个java.rmi.RemoteException。这个异常由RMI系统抛出,从一个远程方法调用中来指明通信失败或者发生了协议错误。RemoteException是一个检查性异常,所以了任何调用远程方法的需要处理这个异常,不管是捕获它还是抛出。


计算引擎的第二接口是Task接口,其为Compute接口中executeTask的参数类型。compute.Task接口定义了计算引擎和工作之间需要它做的接口,提供了一种工作的方式。下面是Task接口的源代码。



package compute;

public interface Task<T> {
    T execute();
}

Task接口定义了一个单一方法,execute,没有参数也不抛出异常。因为没有继承Remote,这个接口的方法不需要在它的抛出块中列出java.rmi.RemoteException


Task接口有一个类型参数,T,代表了任务计算的返回值。接口execute方法返回了计算结果并且返回类型是T。


Compute接口的executeTask方法,反过来,返回传给它的Task实例的执行结果。executeTask方法有自己的类型参数T,将自己的返回类型与传递给它的Task实例的返回类型关联在一起。


RMI使用Java对象序列化原理传递在不同的Java虚拟机之间通过值传输对象。对于一个序列化对象,它的类必须实现java.io.Serializable标志性接口。因此,实现了Task接口类也必须实现Serializable,还有被用作结果返回的对象的类。


不桶种类的任务的能被一个Compute对象运行,只要它们实现了Task类型的接口。实现这个接口的类能持有任何任务计算需要的数据和任何计算所需的其他的方法。


这里是RMI如何使简单的计算引擎运行的。因为RMI能假设Task对象是用Java编程语言写的,Task对象的实现类,先前对于计算引擎未知,按需被RMI下载到计算引擎的Java虚拟机中。这种能力使得计算引擎定义新的任务来运行在服务机器上而不需要代码明确的安装在那台机器上。


计算引擎,由ComputeEngine类实现,实现了Compte接口,通过调用executeTask方法将不同任务提交给它。这些方法是用Task实现类的execute方法运行并返回远程客户端的结果。



实现远程接口


这部分讲述一个计算引擎实现类的任务。一般而言,实现远程接口的类至少应该做一下事情:


声明要被实现的远程接口

为每个远程对象定义构造器

为远程接口的每个远程方法提供实现


一个远程服务程序需要创建初始化的远程对象和输出它们到RMI运行环境,使它们能够接受到来的远程调用。这一设置步骤能被封装在那个远程对象实现类自身的一个方法中或者完全包含在另一个类中。这步应该做以下事情:


创建和配置一个安全管理者

创建和输出一个或者更多的远程对象

用RMI注册器(或者使用其他命名服务,比如能够访问JNDI的服务)至少注册一个远程对象来开启。


计算引擎的完整代码实现如下。engine.ComputeEngine类实现了远程接口Compute并也包含了main方法来设置计算引擎。下面是ComputeEngine的源码:



package engine;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import compute.Compute;
import compute.Task;

public class ComputeEngine implements Compute {

    public ComputeEngine() {
        super();
    }

    public <T> T executeTask(Task<T> t) {
        return t.execute();
    }

    public static void main(String[] args) {
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new SecurityManager());
        }
        try {
            String name = "Compute";
            Compute engine = new ComputeEngine();
            Compute stub =
                (Compute) UnicastRemoteObject.exportObject(engine, 0);
            Registry registry = LocateRegistry.getRegistry();
            registry.rebind(name, stub);
            System.out.println("ComputeEngine bound");
        } catch (Exception e) {
            System.err.println("ComputeEngine exception:");
            e.printStackTrace();
        }
    }
}


下面部分介绍了计算引擎实现的每个组件。


计算引擎实现类如下:


public class ComputeEngine implements Compute

这个申明表明这个类实现了Compute远程接口,因此可以被用作一个远程对象。


ComputeEngine类定义了一个远程对象实现类,实现了一个单一接口,没有其他类。ComputeEngine类也包含了两个可执行程序元素,只可在本地调用。第一个是ComputeEngine实例的构造器。第二个是main方法是创建ComputeEngine实例并使它能被客户端访问。


为远程对象定义构造器

ComputeEngine类有一个单一的默认构造器。代码如下:


public ComputeEngine() {
    super();
}

这个构造器仅仅调用了父类构造器,是Object类的默认构造器。虽然父类构造器的调用可以被省略,但是却清晰的包含在里面。


提供每个远程方法的实现


这个类为远程对象提供了每个远程接口指定的远程方法的实现。executeTask方法代码实现如下:


public <T> T executeTask(Task<T> t) {
    return t.execute();
}

这个方法实现了ComputeEngine远程对象和它的客户端的协议。每个客户端用一个Task对象提供给ComputeEngine,这个对象有一个特别的Task接口的execute方法的实现。ComputeEngine执行每个客户端的任务并直接返回Task的execute方法的结果给客户端。


在RMI中传递对象


来自远程方法的传递的参数或者返回值可以是几乎所有类型,包括本地对象,远程对象,和基本数据类型。更准确地说,任何类型的任何实体能被传递或者从一个远程方法中返回只要类型的实例是一个基本数据类型,一个远程对象,或者一个序列化对象,意味着实现了java.io.Serializable接口。


一些对象类型不符合这些标准类型中的一个,就不能被传递或者从一个远程方法中返回。这些对象的大多数,比如线程或者文件描述符,封装了只能在单一地址空间中才有意义的信息。核心类中的许多,包括java.lang包下的和java.util包下的,实现了序列化接口。


管理参数和返回值如何传递的规则如下:

      远程对象基本上传递依赖。一个远程对象的依赖是一个存根,它是一个客户端代理,完全实现了一组远程远程对象实现的饿远程接口。

      通过复制传递本地对象,使用对象序列化。默认的,复制所有的字段,除了标示static或者transient的字段。根据逐类原则,默认的序列化行为可以被重写。


通过依赖传递远程对象意味着远程方法调用的对象的任何状态的改变都反射到原始的远程对象。当一个远程对象传递的时候,只有那些远程接口对于接受者是可访问的。任何定义在实现类的方法或者定义在非远程接口的实现类不能被接受者访问。


例如,如果你传递一个ComputeEngine类的实例的依赖,接受者仅仅可以访问计算引擎的executeTask方法。接受者看不见ComputeEngine的构造器,或者它的任何java.lang.Object的任何方法的实现。


在参数和从远程方法调用的返回值中,不是远程的对象通过值传递。之后,对象的副本在接受的Java虚拟机中创建。通过接受者的任何对象状态的变化仅仅反射到接受者的复制中,而不是反射到发送者的原始实例中。任何通过发送者发送的对象的状态的改变也仅仅反射到发送者的原始实例,而不是接受者的复制中。


实现服务器的main方法


main是ComputeEngine类中最复杂的方法。main方法用于启动ComputeEngine并之后需要做必要的初始化和准备服务器接受来自客户端调用的内部工作。这个方法不是一个远程方法,意味着不能从一个不同的Java虚拟机上调用。因为main方法申明为static,这个方法也不能与一个对象关联除了ComputeEngine


创建和设置一个安全管理者


main方法的第一个任务是创建和安装一个安全管理者,保护系统资源的访问,这种访问来自运行在Java虚拟机上的未取得信任代码。一个安全管理者确定一个下载的代码是否可以访问本地文件系统或者执行其他的特殊操作。


如果一个RMI程序不设置一个安全管理着,RMI将不下载类(从本地以外的路径),接受的对象作为一个参数或者返回远程方法调用的值。这个限制确保了下载的代码属于一个安全的策略。


下面的代码创建和设置了一个安全管理者。


if (System.getSecurityManager() == null) {
    System.setSecurityManager(new SecurityManager());
}


使远程对象可以访问客户端

下一步,main方法创建了ComputeEngine的一个实例并传输它到一个RMI运行环境中,代码如下:


Compute engine = new ComputeEngine();
Compute stub =
    (Compute) UnicastRemoteObject.exportObject(engine, 0);

这个静态UnicastRemoteObject.exportObject方法传输了提供的远程对象这样它能接受来自远程客户端的远程方法的调用。第二个参数,int类型,指明那个端口用来监听这个对象的到来的远程调用请求。通常使用值0,指明使用一个默认端口。实际端口将在运行时被RMI或者被其他指定的操作系统关闭。然而,非零的值也可用来指定一个特殊的端口来监听。只要exportObject对象调用成功返回,ComputeEngine远程对象就准备处理到来的远程调用。


exportObject这个方法为传入的远程对象返回一个存根。注意变量stub的类型必须是Compute,而不是ComputeEngine,因为远程对象的存根仅仅实现了远程接口,这个接口了,传入参数也实现了。



exportObject方法申明了它能抛出RemoteException,是一个检查异常类型。mian方法在try/catch块中处理这个异常。如果异常没有以这种方式提交,RemoteException将在main方法中抛出。尝试传输一个远程对象能抛出异常,在必要通信通信资源不可用的情况下,比如请求端口被绑定用于其他目的占用。


在一个客户端能在远程对象上调用一个方法前,必须首先获取远程对象的依赖。获得依赖可以以同样的方式,在程序中获得任何其他对象引用,比如获取依赖作为返回值的一部分或者作为数据结构的一部分,它包含了这样的依赖。


系统提供了远程对象的独特类型,RMI注册器,绑定依赖到其他远程对象上。RMI注册器是一个简单的远程对象,命名service,能够使客户端通过名字获取远程对象的依赖。注册器典型用于获取第一个远程对象,RMI客户端需要使用它。第一个远程对象稍后可能提供寻找其他对象的支持。


java.rmi.registry.Registry远程接口是一个API,绑定(或者注册)和查看注册器中的远程对象。java.rmi.registry.LocateRegistry类提供了静态方法,来在一个特定的网络地址(主机和端口)整合到注册器的依赖。这些方法创建了远程依赖对象,包含了制定的网络地址,不需要执行任何网络通信。LocateRegistry也提供了静态方法在当前Java虚拟机上创建一个新的注册器,虽然这个例子不使用这些方法。只要远程对象注册到本机上的RMI注册器,在任何主机上的客户端可以通过名字查看这个远程对象,获取其依赖,并调用这个对象上的远程方法。这个注册器可以被运行在一台主机上的所有服务共享,或者被一个独立的服务进程,能创建和使用自己的注册器。


ComputeEngine类创建了对象的名字使用如下代码:

String name = "Compute";


代码添加名字到在服务器上运行的RMI的注册器上。代码如下:

Registry registry = LocateRegistry.getRegistry();
registry.rebind(name, stub);
rebind调用使一个远程调用到本地主机的RMI注册器上。如何其他调用,这个调用能 抛出RemoteException异常,在main方法末尾的catch块中处理。


注意下面关于Registry.rebind调用:

     LocateRegistry.getRegistry的无参重载在本地主机和默认的注册端口上,1099,整合了一个到注册器的依赖,如果端口不是1099,你必须用一个int作为参数重载这个方   法。


     当调用了注册器上的一个远程调用,一个代表远程对象的存根被传递而不是远程对象自身的复制。当一个客户端查看服务器远程对象注册器时,那个存根的复制返回了。这种情况下的远程对象通过依赖高效传值而不是它的值。


     因为安全原因,应用程序只能绑定,解绑或者重新绑定一个远程对象的依赖,通过运行在相同主机上的注册器。这个限定防范远程客户端移除或者重写服务注册器的实体。但可以随时查看,不管是本地还是远程的主机上。



只要服务器注册了本地的本地RMI注册机,它打印消息表明它准备开始处理调用。稍后,main方法完成了。没必要为了保证服务一直活跃而开启一个线程。只要在其他Java虚拟机,本地或远程的,有一个ComputeEngine兑现的依赖,ComputeEngine对象将不会关闭或者被垃圾回收。因为程序在注册机上绑定了ComputeEngine的依赖,保证了远程客户端可访问注册机本身。RMI系统保持ComputeEngine处理一直运行。ComputeEngine可以接受请求并不再重新申明知道从注册机上移除和没有远程对象持有ComputeEngine对象的远程依赖。


这部分讲的是异常处理。在一些分布式应用中,从获取远程依赖失败重新成功是可能的。例如,应用程序会努力尝试那个操作或者选择另一个服务来运行操作。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值