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

【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 日志记录器:
    这里写图片描述
    这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值