您的位置:首页 > 编程语言 > Java开发

java分布式对象(RMI+部署使用RMI的程序)

2016-02-20 16:31 363 查看

【0】README

1)本文文字转自 core java volume 2, 旨在学习 java 分布式对象的相关知识;

2) RMI 的实例程序为原创;

3) RMI部署步骤的测试用例,参见 http://blog.csdn.net/pacosonswjtu/article/details/50705258

【1】知识背景

1)每过一段时间, 程序员社区就开始考虑“无所不在的对象”作为所有问题的解决之道;

2) 当一台计算机上的某个对象需要调用另一台计算机上的某个对象时, 他就会发送一个包含这个请求的详细信息的网络消息;

3)一旦该远程对象得到了客户端请求的东西, 就将他发送给 客户端;

4)在本章,我们将聚焦在Java的分布式编程技术上,特别是用于两个Java虚拟机(可以运行在不同的计算机上)之间通信的远程方法调用(RMI)协议。

5)RMI: Remote Method Invocation == 远程方法调用协议;

【2】客户与服务器的角色

1)所有分布式编程技术的基本思想都很简单:客户计算机产生一个请求,然后将这个请求经由网络发送到服务器。服务器处理这个请求,并发送回一个针对该客户端的响应,供客户端进行分析。图11-1展示了这个过程。



2)problem+solution:

2.1)problem:我们真正想要的是这样一种机制,客户端程序员以常规的方式进行方法调用,而无需操心数据在网络上传输或者解析响应之类的问题。

2.2)solution:解决的办法是,在客户端为远程对象安装一个代理(proxy)。(干货——引入代理的概念)

3)代理: 是位于客户端虚拟机中的一个对象,它对于客户端程序来说,看起来就像是要访问的远程对象一样。客户调用此代理,进行常规的方法调用。而客户端代理负责与服务器进行联系。

4)problem+solution:

4.1)problem:实现服务的程序员也不希望因与客户端之间的通信而被绊住。

4.2)solution:解决方法是在服务器端安装第二个代理对象。该服务器代理与客户端代理进行通信,并且它将以常规方式调用服务器对象上的方法(参见图11-2)。



5)代理之间是如何通信的呢?这要看以什么技术来实现它们。通常有三种选择(options): (干货——代理间的通信技术)

o1) CORBA,通用对象请求代理架构: 支持任何编程语言编写的对象之间的方法调用。CORBA使用Internet Inter-ORB协议(IIOP)支持对象间的通信。

o2) Web服务架构是一个协议集,有时统一描述为WS-*。它也独立于编程语言的,不过它使用基于XML的通信格式。用于传输对象的格式则是简单对象访问协议(SOAP)。

o3) RMI,Java的远程方法调用技术: 支持Java的分布式对象之间的方法调用。 (干货——引入RMI)

5.1)与RMI不同,CORBA与SOAP都是完全独立于语言的。客户端与服务器程序可以由C、C++、Java或者其他语言编写。

5.2) Sun 开发了一个更简单的机制, 称为远程方法调用-RMI: 专门针对java 应用之间的通信;

【3】远程方法调用(RMI)

1) 分布式计算的关键是远程方法调用。 在一台机器(称为客户端)上的某些代码希望调用在另一台机器(远程对象)上的某个对象的一个方法。要实现这一点,方法的参数必须以某种方式传递到另一台机器上,而服务器必须得到通知,去定位远程对象并执行要调用的方法,并且必须将返回值传递回去。(干货——分布式计算的关键是远程方法调用)

【3.1】存根与参数编组

1)存根: 当客户代码要在远程对象上调用一个远程方法时,实际上调用的是代理对象上的一个普通的方法,我们称此代理对象为存根(stub)。 (干货—— 存根是client 上的 agent object)

1.1)看个荔枝:

Warehouse centralWarehouse = get stub object;

double price = centralWarehouse.getPrice(“Blackwell Toaster”);

1.2)存根位于客户端机器上,而非服务器上。它知道如何通过网络与服务器联系。

1.3)存根将远程方法所需的参数打包成一组字节。

1.4)参数编组:对参数编码的过程称作参数编组(parameter marshalling),参数编组的目的是将参数转换成适合在虚拟机之间进行传递的格式。在RMI协议中,对象是使用序列化机制进行编码的,第1章描述了这种机制。在SOAP协议中,对象被编码为XML。 (干货——参数编组定义)

2)总的来说,客户端的存根方法构造了一个信息块,它由以下几部分组成(parts):

p1)被使用的远程对象的标识符;

p2)被调用的方法的描述;

p3)编组后的参数;

3)然后,存根将此信息发送给服务器。在服务器一端,接收对象执行以下动作(actions):

a1)定位要调用的远程对象;

a2)调用所需的方法,并传递客户端提供的参数;

a3)捕获返回值或该调用产生的异常;

a4)将返回值编组,打包送回给客户端存根。

4) 反编组:客户端存根对来自服务器端的返回值或异常进行反编组,就成为了调用存根的返回值。如果远程方法抛出了一个异常,那么存根就在客户端发起调用的处理空间中重新抛出该异常。图11-3展示了一次远程方法调用的信息流。



Attention) 这个过程显然很复杂,不过好消息是,这一切都是完全自动的,而且在很大程度上,它对程序员是透明性。

【4】RMI 编程模型

【4.1 接口实现】

1) 如何实现和启动服务器与客户端程序。 (干货——如何实现和启动服务器与客户端程序)

step1)远程对象的能力是由在客户端和服务器之间共享的接口所表示的。例如,以下程序的接口描述了远程仓库对象所提供的服务:

public interface Warehouse extends Remote
{
double getPrice(String description) throws RemoteException;
}


对以上代码的分析(Analysis):

A1)远程对象的接口必须扩展Remote接口,它位于java.rmi包中。

A2)接口中的所有方法还必须声明抛出RemoteException异常,这是因为远程方法调用与生俱来就缺乏本地调用的可靠性,远程调用总是存在失败的可能。

step2) 接下来,在服务器端,必须提供这样的类,它真正实现了在远程接口中声明的工作。

public class WarehouseImpl extends UnicastRemoteObject implements Warehouse
{
public WarehouseImpl() throws RemoteException
{
prices = new HashMap<String, Double>();
prices.put("Blackwell Toaster", 24.95);
prices.put("ZapXpress Microwave Oven", 49.95);
}
public double getPrice(String description) throws RemoteException
{
Double price = prices.get(description);
return price == null ? 0 : price;
}
private Map<String, Double> prices;
}


Attention) UnicastRemoteObject类 :你可以看出这个类是远程方法调用的目标,因为它继承自UnicastRemoteObject,这个类的构造器使得它的对象可供远程访问。

2)problem+solution:

2.1)problem:有时候可能不希望服务器类继承UnicastRemoteObject,也许是因为实现类已经继承了其他的类。

2.2)solution:在这种情况下,读者需要亲自初始化远程对象,并将它们传给静态的exportObject方法。如果不继承UnicastRemoteObject,可以在远程对象的构造器中像下面这样调用exportObject方法。

UnicastRemoteObject.exportObject(this, 0); // 第二个参数是0,表明任意合适的端口都可用来监听客户端连接。

Attention) Unicast这个术语是指我们是通过产生对单一的IP地址和端口的调用来定位远程对象的这一事实。这是Java SE中唯一支持的机制。更复杂的分布式对象系统(诸如JINI)会考虑到对在多个不同的服务器上的远程对象的”Multicast”查找。 (干货——这里提到了JINI)

intro to jini ( from http://baike.baidu.com/link?url=2uT-UgPQerl5Xze13Xqyg0e9eVTH5SOI6ucWnFw_nMKHw8754Lc1H_eM94gxiM7U0U9FUttdQd1yZCiBeYvVlK)

Jini(Java Intelligent Network Infrastructure)是Sun公司的研究与开发项目,它能极大扩展Java技术的能力。Jini技术可使范围广泛的多种硬件和软件—即可与网络相连的任何实体—能够自主联网。

Jini可以使人们极其简单地使用网络设备和网络服务,就象今天我们使用电话一样—通过网络拨号即插即用。Jini的目标是最大限度地简化与网络的交互性。

【4.2】RMI注册表

1)problem+solution:

1.1)problem:要访问服务器上的一个远程对象时,客户端首先需要一个本地的存根对象。可是客户端如何对该存根发出请求呢?最普通的方法是调用另一个服务对象上的一个远程方法,以返回值的方式取得存根对象。然而,这就成了先有鸡还是先有蛋的问题。

1.2)solution:第一个远程对象总要通过某种方式进行定位。为此,JDK提供了自举注册服务(bootstrap registry service)。

2)如何注册一个远程对象? (干货——如何注册一个远程对象)

服务器程序使用自举注册服务来注册至少一个远程对象。要注册一个远程对象,需要一个RMI URL和一个对实现对象的引用。RMI的URL以rmi:开头,后接服务器以及一个可选的端口号,接着是远程对象的名字。例如: rmi://localhost:99/central_warehouse

3)默认情况下,主机名是localhost,端口为1099。 服务器告诉注册表在给定位置将该对象关联或”绑定”到这个名字。

3.1)下面的代码将一个WarehouseImpl对象注册到了同一个服务器上的RMI注册表中: (干货——绑定远程对象到server上的RMI注册表中)

WarehouseImpl centralWarehouse = new WarehouseImpl();

Context namingContext = new InitialContext();

namingContext.bind(“rmi:central_warehouse”, centralWarehouse);

3.2) 看个荔枝:下面的程序只是构造并注册了一个WarehouseImpl对象



Attention) 基于安全原因,一个应用只有当它与注册表运行在同一个服务器时,该应用才可以绑定、取消绑定,或重绑定注册对象的引用。

4)客户端可以通过下面的调用枚举所有注册过的RMI对象: (干货——注意是client 端枚举 出 server 端 注册过的RMI对象)

Enumeration<NameClassPair> e = namingContext.list("rmi://regserver.mycompany.com")


5)NameClassPair是一个助手类: 它包含绑定对象的名字和该对象所属类的名字。

5.1)看个荔枝: 例如,下面的代码可以显示所有注册对象的名字:

while (e.hasMoreElements())

System.out.println(e.nextElement().getName());

6)客户端可以通过下面的方式,来指定服务器和远程对象的名字,以此获得访问远程对象所需的存根: (干货——client端获得存根)

String url = "rmi://regserver.mycompany.com/central_warehouse";
Warehouse centralWarehouse = (Warehouse) namingContext.lookup(url);


Attention) 在一个全局注册表中,想保持一个名字的惟一性非常困难,因此不应该将此技术作为定位服务器端对象的一般方法。相反,自举服务只应该用来注册非常少的远程对象。然后使用这些对象来定位其他的对象。 (干货——自举服务的局限性)

7) 代码11-4展示了客户端如何获得远程仓库对象的存根,并调用远程的getPrice方法。





【4.3】部署程序(you can also refer to http://www.cnblogs.com/leslies2/archive/2011/05/20/2051844.html)

0)部署前的准备

0.1)创建两个目录, 分别存放用于启动server 和 client 的类:

server/

WarehouseServer.class

Warehouse.class

WarehouseImpl.class

client/

WarehouseClient.class

Warehouse.class

0.2)当部署RMI 应用时, 通常需要动态地将类交付给运行程序,其中一个例子就是RMI注册表。请记住注册表的一个实例要服务许多不同的RMI应用。RMI注册表需要有权限访问注册的服务接口的类文件,但是,当注册表启动时,无法预测将来会产生的所有注册请求。因此,RMI 注册表会动态地加载之前从未遇到过的所有远程接口的类文件;

0.3)动态交付的类文件是通过标准的web server发布的。在我们所举的case中, server程序 需要使用 Warehouse.class 文件 对于 RMI 注册表来说是可以获得的,因此将这个文件放到了第三个称为download 目录中的:

download/

Warehouse.class

0.4)下面,我们用 web server 来访问这个目录中的内容(NanoHTTPD web server)

1)当应用被部署时,服务器、RMI注册表、Web服务器和客户端可以定位到四台不同的计算机上,参见图11-5。但是,出于测试的目的,我们将只使用一台计算机。



Attention) 由于安全的原因,作为JDK一部分的rmiregistry服务只允许来自同一台主机的绑定调用。也就是说,服务器和rmiregistry进程需要定位在同一台计算机上。但是,RMI架构允许更通用的支持多台服务器的RMI注册表实现。 (干货——基于安全原因,服务器和rmiregistry进程需要定位在同一台计算机上)

2)测试远程方法调用:

step1)打开一个新的控制台窗口,转到download目录,然后将NanoHTTPD.java(NanoHTTPD web 服务器)复制到这个目录中。使用下面的命令来编译该源文件并启动这个web服务器: java NanoHTTPD 8080



step2)打开另一个控制台窗口,转到不包含任何类文件的某个目录,并启动RMI注册表:rmiregistry



Attention)

A1)在启动RMI注册表之前,请确保CLASSPATH环境变量没有进行任何设置,并仔细检查当前目录是否确实不包含任何类文件。否则,RMI注册表可能会找到一些假冒的类文件,这使得注册表需要从另一个不同的来源下载额外的类文件时,产生混淆。

A2)简单地讲,每个存根对象都有一个代码基项,指出了它是从何处加载的。这个代码基被用来加载它所依赖的类。如果RMI注册表在本地找到了这样的类,那么它的代码基就会被设置为错误的值。

A3)推荐在server 程序中通过代码注册通讯端口和注册通讯路径 (干货——不推荐使用 rmiregistry命令行)

// 注册通讯端口

LocateRegistry.createRegistry(1099);

// 注册通讯路径

Naming.rebind(“rmi://localhost:1099/warehouseService”, warehouseService);

step3)现在已经准备好启动服务器了。打开第三个控制台窗口,转到server目录,并执行下面的命令:

java -Djava.rmi.server.codebase=http://localhost:8080/ WarehouseServer // java.rmi.server.codebase属性指出了服务类文件的URL。服务器程序将这个URL传递给RMI注册表。



Warning) 确保代码基URL以斜杠”/”结尾非常重要。

step4)最后,打开第四个控制台窗口,转到client目录,运行:



你将会看到一条短消息,表示运行方法被成功调用, 参见图11-6所示;



【4.4】记录 RMI 活动的日志

1) 如果用下面的选项启动服务器:

-Djava.rmi.server.logCalls=true WarehouseServer & // 那么服务器会在其控制台上记录所有的远程方法调用到日志中。

java -Djava.rmi.server.logCalls=true -Djava.rmi.server.codebase=http://localhost:8080/ com.corejava.chapter11.server.WarehouseServer



2) 如果想查看额外的日志信息,就必须用标准的Java日志API配置日志记录器。

2.1) 可以用下面的内容创建一个logging.properties文件。

handlers=java.util.logging.ConsoleHandler

sun.rmi.loader.level=FINE

java.util.logging.ConsoleHandler.level=FINE

java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter

2.2)我们可以对设置进行调整,方法是为每一个记录器设置单独的等级而不是设置全局等级。例如,要跟踪类的加载行为,可以设置为:

sun.rmi.loader.level=FINE

2.3)用以下选项启动RMI注册表

-Djava.util.logging.config.file=directory/logging.properties

2.4)用以下选项启动客户端与服务器

-Djava.util.logging.config.file=directory/logging.properties

2.5)表11-1 列出了所有的RMI 日志记录器:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 远程方法调用