Java学习笔记之RMI远程方法调用_小明TI的博客-程序员宝宝

技术标签: java  Java基础  分布式  

RMI 应用通常有两个分开的程序组成,一个服务端程序和一个客户端程序。一个典型的服务端程序创建一些远程对象,使得对这些远程对象的引用可以被访问,等待客户端调用这些远程对象提供的方法。一个典型的客户端程序获取远程引用,指向一个或者多个服务端上的远程对象,然后调用这些远程对象所提供的方法。通常我们称这为分布式对象应用程序。

3.1 RMI的工作方式

分布式对象应用程序需要做的事情:

l 查找(定位)远程对象。 应用程序可以使用各种不同的机制取得远程对象的引用。比如应用程序可以通过 RMI 提供的简单的命名工具, RMI 注册。或者应用程序可以传递和返回远程对象作为远程方法调用的一部分。

l 和远程对象通信。 远程对象之间通信的细节由 RMI 处理,对程序员来说远程对象的通信和通常的 Java 方法调用没有区别。

l 加载传递过来的远程对象的类定义。 因为 RMI 允许对象双向传递,因此它提供了加载对象类定义和传递对象数据的机制。

下图(图3.1)描述了一个使用 RMI 注册机制取得远程对象引用的RMI分布式应用。 Server调用注册机制将一个名字和远程对象关联(或者叫绑定)。Client 根据名字在Server的注册机制里面查找远程对象,获得远程对象的引用,并调用远程对象的方法。下图还描述了这样的情况,RMI 系统使用Web Server,从Server 到Client或者从Client到Server,加载所需对象的类定义。
这里写图片描述

3.1.1 动态类加载
RMI的一个独特的核心优势就是能够加载一个未在接收端的Java虚拟机( JVM )中定义的类。在一个Java虚拟机中定义的一个对象所有的类型和行为,能够从这个Java虚拟机传递到另外一个Java虚拟机,甚至可以是远程的Java虚拟机。RMI根据对象的真实类型传递对象,所以当对象被传递到另一个Java虚拟机时对象的行为不会改变。这个特性使得新的对象类型和行为能够被引入到一个远程的Java虚拟机当中,也就是说动态扩展了应用程序的行为。下面的compute engine例子就是使用这种能力往分布式程序中引入新的行为。

3.1.2 远程对象
正如其他很多Java应用程序一样,一个构建在RMI之上的分布式应用也是由接口和类组成的。接口声明方法,类实现在接口中定义的方法,也许还会声明额外的方法。在分布式应用中,一些方法可能存在于某些Java虚拟机中但是却不在另一个Java虚拟机中。如果一个对象的方法能够在不同的Java虚拟机之间被调用,那么此对象被称作远程对象(remote objects)。

一个普通对象可通过实现远程接口( java.rmi.Remote )变成远程对象,这个远程接口有如下特征。

l 一个远程接口扩展 java.rmi.Remote 接口

l 每个远程接口里声明的方法除了声明抛出本身应用特定的异常之外,都要声明抛出 java.rmi.RemoteException 异常

当对象从一个Java虚拟机传递到另一个Java虚拟机时,RMI区别对待远程对象和非远程对象。当RMI传递一个远程对象到另一个JVM时,它实际上传递的是此远程对象对应的存根(stub)对象,而不是传递这个对象的拷贝。这个存根对象担当远程对象的代表或者代理的角色,为client提供到远程对象的引用。Client调用所获得的stub的方法,而这个stub则负责执行远程对象里这个方法的调用。

一个远程对象的stub(存根)实现了与这个远程对象所实现的远程接口的相同方法集合。这个特性使得stub能够被转型为远程对象实现的远程接口。然而,也只有那些在远程接口里声明的方法才能被接收端的JVM调用。

在客户端进行远程方法调用时,RMI框架会把遇到的网络通信失败转换为RemoteException,客户端可以捕获这种异常,并进行相应的处理。

3.1.3 远程方法中的参数与返回值传递
RMI规范对参数及返回值的传递作出了以下规定:

l 只有基本数据类型、远程对象及可序列化的对象才能作为参数或者返回值进行传递。

l 如果参数或返回值是一个远程对象,那么把它的存根对象传递到接收方。也就是接收方得到的是远程对象的存根对象。

l 如果参数或返回值是可序列化的对象,那么直接传递该对象的序列化数据。也就是说,接收方得到的是发送方的可序列化的对象的复制品。

l 如果参数或返回值是基本数据类型,那么直接传递该数据的序列化数据。也就是说,接收方得到的是发送方的基本数据类型的复制品。

3.1.4 远程对象的equals()、hashCode()和clone()方法
在Object对象中定义了equals()、hashCode()和clone()方法,这些方法没有声明抛出RemoteException。Java语言规定了当子类覆盖父类方法时,子类方法不能声明抛出比父类方法更多的异常。而RMI规范要求远程接口中的方法必须声明抛出RemoteException异常,因此无法在远程接口中定义equals()、hashCode()和clone()方法。这意味着一个远程对象的这些方法永远只能作为本地方法,被本地Java虚拟机内的其它对象调用,而不能作为远程方法,被客户端远程调用。

3.1.5 分布式垃圾收集
在Java虚拟机中,对于一个本地对象,只要不被本地Java虚拟机中的任何变量引用,它就会结束生命周期,可以被垃圾回收器回收。而对与一个远程对象,不仅会被本地Java虚拟机中的变量引用还会被远程引用。如将远程对象注册到rmiregistry注册表时,rmiregistry注册表则持有它的远程引用。

RMI框架采用分布式垃圾收集机制(DGC,Distributed Garbage Collection)来管理远程对象的生命周期。DGC的主要规则是,只有当一个远程对象不受任何本地引用和远程引用,这个远程对象才会结束生命周期。

当客户端获得了一个服务器端的远程对象存根时,就会向服务器发送一条租约通知,告诉服务器自己持有这个远程对象的引用了。此租约有一个租约期限,租约期限可通过系统属性java.rmi.dgc.leaseValue来设置,以毫秒为单位,其默认值为600 000毫秒。当到达了租约期限的一半时间,客户端如果还持有远程引用,就会再次向服务器发送租约通知。如果租约到期后服务器端没有继续收到客户端的新的租约通知,服务器端就会认为这个客户已经不再持有远程对象的引用。

有时,远程对象希望在不再受到任何远程引用时执行一些操作,如释放所占用的资源,以便安全的结束生命周期。这样的远程对象需要实现java.rmi.server.Unreferenced接口,该接口有一个unreferenced()方法,远程对象可以在这个方法中执行释放占用的相关资源的操作。当RMI框架监测到一个远程对象不再受到任何远程引用时,就会调用的这个对象的unreferenced()方法。

3.2 通过RMI创建分布式应用
通过使用RMI开发一个分布式应用遵循下面几个步骤:[3]

  1. 设计实现分布式应用的组件

  2. 编译源代码

  3. 使得你的类在网络上可访问

  4. 启动应用程序

3.2.1 设计实现应用程序组件
首先决定应用程序的体系结构,包括哪个组件是本地对象,哪个组件远程可访问。这一步骤包括:

l 定义远程接口. 一个远程接口指定了哪些方法能够被client远程调用。Client程序针对远程接口编程,而不是针对实现了这些远程接口的类。这些接口的设计包括了如何声明远程方法所需参数的对象类型,以及远程方法返回值类型。

l 实现远程对象. 远程对象需要实现至少一个远程接口。远程对象也可以实现其它的接口,但这个接口里声明的方法只在本地JVM可用。如果任何一个本地类要被用作为这些(远程)方法的参数或者是返回值,这些类也需要被实现。

l 实现客户端. 在远程接口定义好之后,使用远程对象的客户端可以在任何时候被实现,任何时候的意思包括在远程对象部署之后。

3.2.1.1 实现服务器端
ComputeEngine是一个服务器上的远程对象,从客户端接受任务,执行任务之后返回结果。这些任务是在服务端运行的机器上执行的。这种类型的分布式应用程序使得许多客户端使用性能强劲的机器或者是拥有特殊硬件资源的机器。

ComputeEngine的奇特之处在于它运行的任务不需要在它写代码或者运行的时候定义。新的任务可以随时被创建然后交由其执行。一个任务唯一的要求就是任务类必须实现一个特定的接口。需要完成的任务的代码能够被RMI系统下载到ComputeEngine。然后ComputeEngine运行这个任务,使用ComputeEngine所运行的机器上的资源。

执行任意任务的能力是由Java平台的动态特性保证的,这个动态特性又被RMI扩展到网络世界。RMI动态装载任务代码到ComputeEngine所在的JVM,然后运行这个任务,而不需要预先知道实现这个任务的类。这样一个有动态加载代码能力的应用通常被称为 behavior-based application 。这种应用程序通常要求允许代理的基础结构。有了RMI,这种应用构成了Java平台上分布式计算的基本的机制。

l 设计远程接口

compute.Compute接口定义了远程可访问的部分,下面是Compute接口的源代码。通过继承java.rmi.Remote接口,Compute接口表明自己是接口方法可以被另一个JVM调用的接口。所以实现该接口的对象就是远程对象。

package compute;

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

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

作为远程接口的一员,executeTask方法是一个远程方法。因此该方法需要定义为能抛出java.rmi.RemoteException异常。这个异常由RMI系统调用一个远程方法时抛出,表示通讯失败或者是协议错误发生。RemoteException是一个受检查的异常,所以任何调用远程方法的代码需要处理这个异常,要么捕获该异常,要么声明抛出子句。

ComputeEngine需要的第二个接口是Task接口,这个接口也是executeTask的类型参数。Compute.Task接口定义了ComputeEngine和它要执行的工作直接的接口,提供了开始这个工作的方法。下面是Task接口的源代码:

package compute;

public interface Task {
T execute();
}
Task接口就定义了一个方法,execute,无参数,也没有异常。因为这个接口没有继承Remote接口,所以方法也无需声明抛出java.rmi.RemoteException子句。

RMI使用Java对象序列化机制在JVM之间以值传递方式传输对象。对象要能被序列化就需要实现java.io.Serializable这个标识接口,因此实现Task接口的类也要实现java.io.Serializable接口,作为Task执行结果的对象的类也必须要实现这个接口。

不同种类的任务都能被一个Compute对象执行,只要这些任务都实现了Task接口类型。实现这个接口的类可以包含任何执行计算所需要的数据和其他执行计算所需要的方法。

RMI假定Task对象由Java语言程序编写,ComputeEngine先前不知道的Task对象的实现,在需要时由RMI下载到ComputeEngine所在的JVM。这个能力使得ComputeEngine的Client能够定义新的将要在Server上运行的任务,而不需要代码显式的被安装在Server机器上。

l 实现ComputeEngine类 (服务类)

概括的说,一个实现远程接口的类至少需要做以下步骤:

Ø 声明要实现的remote interface

Ø 为每个remote对象的定义构造函数

Ø 实现remote interface里的远程调用方法

一个RMI 的server端程序需要创建初始的远程对象并把他们发布到RMI的环境,使其能够接受远程调用。这一步骤可以包括在远程对象某个方法中,也可以在其他类的实体对象中。这一步骤要做如下步骤:

Ø 创建并安装一个security manager

Ø 创建并发布一个或者多个远程对象

Ø 使用RMI registry注册至少一个远程对象

实现安全管理器的目的是RMI框架利用Java安全管理器来确保远程方法调用的安全性。

下面是ComputeEngine的全部实现。这个类实现了远程调用接口(remote interface)Compute,还有main方法,用来安装compute engine。下面是这个类的源代码:

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();
            //使用匿名端口导出远程对象,以便能够接收传入的调用。
            //返回远程对象的stub
            Compute stub =
              (Compute) UnicastRemoteObject.exportObject(engine);
            Registry registry = LocateRegistry.getRegistry();
            registry.rebind(name, stub);
            System.out.println("ComputeEngine bound");
        } catch (Exception e) {
            System.err.println("ComputeEngine exception:");
            e.printStackTrace();
        }
    }
}

远程方法需要的参数或者是返回值几乎可以是任何对象,包括本地对象,远程对象,元数据类型。更确切的说,任何实体对象只要是符合如下类型的实例都能作为远程对象可用的参数或者返回值,这些类型包括元数据类型,远程对象或者是一个可序列化对象(实现了java.io.Serializable的对象)。

有些对象类型不能满足上面的要求,因此不能传递给远程方法作为参数,或作为远程方法的返回值。这些对象如线程或者是文件描述符,它们只在单个地址空间内是有意义的,许多核心的类都实现了Serializable接口。

如何传递参数和获得返回值遵循如下的约定:

Ø 远程对象本质上通过引用传递。一个远程对象的引用包含在此对象所对应的存根对象中,在向注册处注册时传递就是这个存根对象,而客户端通过名字在注册出查找获得的也是这个存根对象。这个存根可作为一个实现了远程接口的客户端的代理。

Ø 本地对象通过值拷贝传递,其中使用对象序列化的技术。默认的拷贝方法是,拷贝除了被标识为static和transient的所有的域。默认的序列化行为可以被重载。

通过引用传递远程对象意味着所有对这个对象状态的修改都会影响到原来的远程对象。当传递远程对象引用的时候,只有远程接口提供的方法才能被接收者使用。任何在实现类中定义的方法或者类实现的非远程接口中定义的方法,接收端是不可用的。

在远程方法调用的参数返回值中,非远程对象的对象通过值拷贝传递。因此,在接受端的JVM中一个对象的拷贝被创建。任何对对象状态的更改只会影响到拷贝的对象,而不会影响发送端的原始对象实例。任何由发送端对对象状态的更改,只会影响发送端原始对象的实例,而不会影响接收端该对象的拷贝对象。

l 实现服务器 (由服务类中的main方法完成)

ComputeEngine中最复杂的代码就是这个main方法。Main方法用来启动ComputeEngien因此需要做必要的初始化,为server接受client的请求做准备。这个方法不是远程方法,就是说它不能被另一个JVM调用。因为main方法声明成静态的,这个方法不会和一个对象关联,只和ComputeEngine类关联。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u014349086/article/details/51636606

智能推荐

用VC++制作播放AVI视频流的动画按钮_纳木错的博客-程序员宝宝

Visual C++ 开发环境为控件提供的自绘制功能使程序员能够充分发挥自己的创造性来设计比较漂亮的程序界面。所谓AVI按钮是指每当鼠标从按钮上经过时就播放一段按钮提示的AVI,在许多的游戏程序以及三维动画软件中(如摩托英豪、Cool 3D等)都广泛的采用了这种AVI按钮。它使得程序的用户界面很具有动感,也使得我们的程序至少看上去更专业,本实例借助Visual C++强大的控件自绘制功能来实现这种

Mariadb/Mysql升级至10.4.10操作实战_HDP_CDH的博客-程序员宝宝

切记:升级操作需要使用yum源1,备份相关数据库数据 mysqldump -h127.0.0.1 -uroot -ppass --databases hive ambair &gt;/opt/tools/mariadb-10.4.10/ambari-hive.sql2,卸载原有mariadb数据库 卸载相关rpm包,示例如下 rpm -ivh m...

JZ56. 删除链表中重复的节点_dblinux的博客-程序员宝宝

题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1-&gt;2-&gt;3-&gt;3-&gt;4-&gt;4-&gt;5 处理后为 1-&gt;2-&gt;51. 分析​ 厘清思路:要删除重复节点,首先要考虑到的是,万一第一个节点就重复了?所以要提前定义一个节点vHead,让vHead的下一个节点指向链表头结点,最后返回vHead -&gt; next 即可。要是有一个节点重复多次?所以要在if判断当前节点值等于下一节点值的

自定义数据集(Pokemon)实战_daoboker的博客-程序员宝宝_pokemon数据集

Load datainherit from torch.utils.data.Dataset__len__返回一个样本数量__getitem__去读取一个具体的样本大致的逻辑为:完成初始化工作,把所有图片信息加载进来,图片的路径和label根据mode模式是train还是valid做各种裁剪得到样本数量和具体样本,返回的img和label就是对应idx的自定义数据tensor类型class NumberDataset(Dataset): def __init__(self, training=T

关于python的一些好的书籍推荐-如果只能推荐3本关于python的书,你会推荐哪3本?..._weixin_37988176的博客-程序员宝宝

Python是一种跨平台的计算机程序设计语言。是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。Python现如今已成为真正的万金油语言,哪里需要哪里用,不管是做web,做爬虫自动化,做数据分析,甚至是做机器学习和深度学习都是信手拈来,周边的类库丰富到无法想象。今天就来...

pagespeed insights 上线了,可以通过web进行网站性能分析了_freewebsys的博客-程序员宝宝

地址: https://developers.google.cn/speed/pagespeed/insights/分析下csdn网站:功能挺多的,还不错。 可以进行参考,然后优化。提高访问速度。

随便推点

django源码简析——后台程序入口_M1ss_He的博客-程序员宝宝

Ref: http://www.cnblogs.com/Tour/p/6403833.html这一年一直在用云笔记,平时记录一些tips或者问题很方便,所以也就不再用博客进行记录,还是想把最近学习到的一些东西和大家作以分享,也能够对自己做一个总结。工作中主要基于django框架,进行项目的开发,我是主要做后台相关比较多一些,熟悉django的同学知道,django的后台进程通常通过下面这种方式运行...

数据库设计之站内信设计_xulei_19850322的博客-程序员宝宝

最近做网站,有个站内信功能,站内信和邮箱的功能类似,只不过不通过邮件服务器发送,而是直接将记录保存在数据库中,要求做到能发能收能删,能群发,想了下,设计如下,欢迎看到这篇文章的朋友给出建议:发件表,收件表,内容表分离,发件表中保存发送与草稿两种邮件,发送多个邮件时,收件表的收件人ID与删除状态为填写多个,以“,”分隔,例如收件人ID为10000,10001,10002,这时,对应的删除状态

Meat quality evaluation based on computer vision technique A review 论文解读_Hong_Youth的博客-程序员宝宝

《基于计算机视觉技术的肉质评价综述》目的:评估肉类生产过程的安全性和质量,确保人类食用更加安全健康的肉类传统方法:昂贵和耗时、破坏性、不一致、不稳定解决思路:将计算机视觉技术引入肉食品的各种质量检测评价指标:新鲜度、嫩度经典的评估方法有:感官评估和化学技术、TVB-N(总挥发盐基氮)和微生物种群图像评估可根据肉类的属性:颜色、形状、大小、表面纹理特征使用机器视觉方法的局限性:仅限于识别颜色、大小、表面结构等外部质量因素。机器视觉系统:帧捕捉器、相机、光源、计算机硬件和软件..

对多目标粒子群算法MOPSO的理解_qlzhu1991的博客-程序员宝宝

多目标粒子群(MOPSO)算法是由CarlosA. Coello Coello等在2004年提出来的,详细参考1。目的是将原来只能用在单目标上的粒子群算法(PSO)应用于多目标上。我们知道原来的单目标PSO流程很简单:à初始化粒子位置(一般都是随机生成均匀分布)à计算适应度值(一般是目标函数值-优化的对象)à初始化历史最优pbest为其本身和找出全局最优gbestà根据位置和速度公

安装 gcc-4.1.2_lukcat的博客-程序员宝宝_下载gcc4.1.2报错dpkg: error: need an action option

转自:http://www.scroll-lock.eu/index.php?option=com_content&view=article&id=244:compile-gcc-412-on-ubuntu-1004&catid=1:scroll-blogI was trying to compile gcc-4.1.2 on Ubuntu x64 10.04 because Maya 201

解决bootstrap标签页切换中ifram引入的外部链接显示出现问题(layui也差不多)最新更新_IT_CREATE的博客-程序员宝宝

先看一段代码:&lt;div style="width: 100%"&gt; &lt;ul id="myTab" class="nav nav-tabs"&gt; &lt;li class="active"&gt; &lt;a href="#information" data-toggle="tab"&gt;★销售人员信息&lt;/a&gt;...