开始学习Java RMI,远程方法调用-基础篇

转自:http://blog.csdn.net/qb2049_xg/article/details/2842429

摘要:本译文翻译Sun的在线文档,为了说明RMI的基础知识,如果你刚开始接触RMI,那么本文对你 很有帮助。本文属于基础教程,学习时请你实际操作该例子。

译注:翻译这篇文件原因,是由于我在学习RMI的过程中疑惑的解决。我不知道为什么要用stub,为什么要把它译成存根 ,它到底存在于客户端还是服务端,它由那方(在JVM高于5.0的版本中)产生。在看了这边文档以后,RMI我可以算是有了一定的了解,尤其是codebase tutorial(代码库址教程)学习让我有了更 好的认识。基于此我想把其翻译出来,供大家学习。你可以点击Getting Started Using Java RMI参考原文。这是我得第一篇文章的翻译,希望大家给我意见。如果想对RMI有进一步的讨论,请你在回复 中留下E_Mail。本文用到的源文件可以由CSDN下载频道搜索下载(含使用说明),文件名称为: Getting Started Using Java RMI 文章使用源代码。

本教程将向你分步展示使用RMI的经典分布式例子“Hello World”。当你学习完这个例子,你可能有许多的疑问。我们 鼓励你到Java RMI FAQarchives of the RMI-USERS mailing list去寻找答案。如果你乐意加 入RMI-USERS邮件组,请你点击这里

这个分布式的Hello World例子含有一个可能运行于远程服务的服务器,一个简单的调用远程方法的客户端,客户端调用 远程对象的一个方法,接受到一条“Hello world”消息。

教程按如下部分进行:

教程中将使用的文件为:

  • Hello.java -远程接口(方法)定义
  • Server.java-实现远程接口的远程对象
  • Client.java-调用远程方法的简单客户端

注意:在以下教程中,“远程对象实现”和“实现类”都指向同一个类:example.hello.Server,它 实现了远程接口。

定义远程接口

一个远程对象是指实现了远程接口类的实例。远程接口继承于java.rmi.Remote接口,同时定义了一系列用于远程调用的 方法,每一个方法除了其他应用程序异常以外,还必须显式声明抛出异常java.rmi.RemoteException(或是RemoteException的父类)在throws句子中。

下面是例子远程接口的定义,example.hello.Hello。它定义了一个方法,sayHello,实现向调用者返回一个字符串:

  1. package example.hello;
  2. import java.rmi.Remote;
  3. import java.rmi.RemoteException;
  4. public interface Hello extends Remote
  5. {
  6.    String sayHello() throws RemoteException;
  7. }

调用远程方法除了和调用本地方法产生一样的问题外,还有其他许多的干扰问题(如网络通信故障和服务器出现问题) ,所以远程方法通过抛出java.rmi.RemoteException异常报告这种失败。想知道关于通信失败和恢复的进一步的内容在分布式系统中,请参考:A Note on Distributed Computing.

远程服务的实现

一个“服务类”,在本文中,是指创建一个实现远程接口对象,导出该对象,并把其用一字符串绑定到RMI注册表中含有 main主方法的类。该含有main方法类实例化了自身,或是实例化其他所有远程对象类。

在本例子中,服务器类中main方法实现远程接口Hello。main方法将完成以下任务:

  • 创建和导出远程对象
  • 使用Java RMI Registry注册和绑定该远程对象

这是Server类的程序清单,具体的关于源码的描述将在代码后:

  1. package example.hello;
  2. import java.rmi.registry.Resistry;
  3. import java.rmi.registry.LocateRegistry;
  4. import java.rmi.RemoteException;
  5. import java.rmi.server.UnicastRemoteObject;
  6. public class Server implements Hello
  7. {
  8.   public Server(){}
  9.   public String sayHello()
  10.    {
  11.     return  “Hello,World!”;
  12.    }
  13.   public static void main(String args[])
  14.    {
  15.     Try{
  16.         Server obj=new Server ();
  17.         Hello stub=(Hello)UnicastRemoteObject.explortObject(obj,0);
  18.         //Bind the remote object’ s stub in the registry
  19.         Registry registry=LocateRegistry.getRegistry();
  20.         Registry.bind(“Hello”,stub);
  21.         System.err.println(“Server ready”);
  22.        }catch(Exception e)
  23.         {
  24.          System.err.println(“Server exception:”+e.toString());
  25.          e.printStackTrace();
  26.         }
  27.     }
  28. }
 

Server类实现了接口Hello,同时也完成了接口中的方法sayHello。方法sayHello不需要再次的声明抛出任何异常,原因 在于其本身就是一个接口方法的实现,既不会抛出RemoteException也不会抛出其他的检查异常。

注意:该类中还可以定义在接口中没有指定的方法,但是这些方法只能够被运行于远程服务的JVM调 用,不能够远程调用。

创建和导出远程对象

在服务器类的main方法中,实现了一个远程的对象以提供服务,除此之外,必须导出远程对象到Java RMI运行中,以便 接受即将到来的远程呼叫,一切完成如下:

Server obj=new Server();
Hello stub=(Hello)UnicastRemoteObject.exportObject(obj,0);

静态方法UnicastRemoteObject.exportObject(obj,0)导出了用于提供给远程的对象,等待着即将到来的在任意端口远程 方法呼叫,同时返回一个为了传给远程客户端的对象桩。导出的结果,就是让运行中的系统可能开始了一个新的服务套接字或是共享一个服务套接字,监控远程方 法调用的呼叫。该返回的桩同样实现了远程接口像远程对象类,它含有主机名称,远程对象用于通信的端口。

注意:从J2SE5.0以后的版本,桩类不再需要rmic桩编译器事先编译产生,除非远程对象要支持JVM低 于5.0的客户端。如果你的应用程序要支持这样的客户端,那么在应用和部署时,就需要为这些远程对象产生桩类提供给远程客户端下载。如何的产生桩类,请查阅 rmic编译器工具文档[ Solaris,Windows]。如何让产生的桩类更好的与你的应用部署结 合起来,请你查阅codebase tutorial(代码库址教程 ).

使用Java RMI registry注册远程对象

为了能让调用者(客户端,终端或是Applet)调用远程对象的方法,调用者必须事先获得远程对象的桩。为了自引导, Java RMI 为应用程序提供了一个注册表API,用于把远程对象的桩绑定到一个名字,这样远程客户端依靠查找该名字就能获得对应的桩。

Java RMI 注册表只是一个允许客户端获得远程对象桩引用的简单名字服务。通常,一个注册表用来(通常也是这样)仅 是定位第一个远程对象,客户端需要用到的。然后,典型的,第一个对象可能反过来为寻找其他的对象提供特殊的应用支持。举例来说,相对另一个远程调用,一 个引用可能用作一个参数或是作为一个返回值。讨论这是如何工作的,请你浏览Applying the Factory Pattern to Java RMI.

一旦远程对象被注册在服务器上,调用者就能就可以查找到该对象通过名字,获得这个对象的引用,然后调用该对象的 方法。

下面Server代码产生了一个本地主机默认端口的注册存根,然后使用其远程对象存根绑定一个名字“Hello”在那个注册 表中:

Registry registry=LocateRegistry.getRegistry();
Registry.bind(“Hello”,stub);

静态无参LocateRegistry.getRegistry返回一个实现远程端口java.rmi.registry.Registry的存根,该存根发送调用到 本地端口为1099的服务器注册表,那时bind()方法就被用来绑定远程对象存根和“Hello”名字在注册表中。

注意:LocateRegistry.getRegistry只是简单的返回一个合适的注册表存根,这个方法不会去检查是 否一个注册表正在运行中。如果在本地1099的端口上没有运行中的注册表,那么bind()方法的调用将会以服务端RemoteException异常而失败。

客户端的实现

客户端产生服务器端的注册表存根,在注册表中通过名字查找远程对象的存根,然后通过远程对象存根调用远程对象方 法sayHello()。

下面是客户端的代码清单:

  1. package example.hello;
  2. import java.rmi.registry.LocateRegistry;
  3. import java.rmi.registry.Registry;
  4. public class Client
  5. {
  6.    private Client(){}
  7.    public static void main(String[] args)
  8.    {
  9.       String host=(args.length<1)?null:args[0];
  10.       try{
  11.            Registry registry=LocateRegistry.getRegistry (host);
  12.            Hello stub=(Hello)registry.lookup (“Hello”);
  13.            String response=stub.sayHello();
  14.            System.out.println(“Response:”+response);
  15.          }catch(Exception e)
  16.            {
  17.             System.err.println(“Client exception:”+e.toString ());
  18.             e.printStackTrace();
  19.            }
  20.     }
  21. }
 

通过命令行的指定主机,客户端最先获得静态方法LocateRegistry.getRegistry()返回指定主机注册表存根。如果没有 指定(服务)主机,即主机为null,那么客户端将被暗示使用本地主机。

下一步,从服务端的注册表中,客户端调用注册表存根远程方法lookup()以获得远程对象的存根。

最终,客户端使用远程对象的存根调用远程方法sayHello(),以下的动作当在此时发生:

  • 运行中的客户端,依着主机和端口,在远程对象存根中打开了一个到服务端的连接,然后序列化所有调用数据。
  • 运行中的服务端,接受呼叫,匹配相应的对象,序列化到客户端的结果(响应“Hello,word”)。
  • 运行中的客户端,接受,反序列化,返回结果到调用者。

远程方法调用的返回信息,最终被列印到控制台上。

编译所有源文件

本例中的源文件可以(参考)如下编译:

javac -d destDir Hello.java Server.java Client.java

这里的destDir是指编译class存放地

注意:如果你的服务端需要支持低于5.0的虚拟机版本,那么所有的远程对象类的存根需要使用rmic 编译产生,同时这些存根能够让客户端下载。请参看codebase tutorial(代码库址教程)获得更多信息。

启动registry,server,client

为了完成这个例子,你需要做如下的工作:

  • 启动Java RMI registry
  • 启动服务端
  • 启动客户端

启动注册表服务

要启动注册表服务,只需要在服务端执行rmiregistry命令即可。如果成功,命令没有任何输出,是典型的后台服务。请 你查考工具文档,获得更多的rmiregistry [ SolarisWindows]的信息。

例子,在Solaris平台:

rmiregistry &

或者在Windows平台:

start rmiregistry

默认的注册服务运行在TCP协议端口1099。如果想改变该端口,需要在命令行中指定端口号。例如,想在Windows平台上 启动注册服务在端口2001上运行,只需:

start rmiregistry 2001

如果注册服务运行在非1099端口,你将需要为LocateRegistry.getRegistry()方法指定设定的端口调用无论是在客户端 或是服务端。例如,注册服务运行在端口是2001的主机上,那么getRegistry()在服务端的调用就该是:

Registry registry=LocateRegistry.getRegistry(2001);

启动服务

启动远程服务,在命令行运行Server类如下:

在Soloaris平台上:

java –classpath classDir –Djava.rmi.server.codebase=file:classDir/ example.hello.Server &

在Windows平台上:

java –classpath classDir –Djava.rmi.server.codebase=file:classDir/ example.hello.Server

这里的classDir是指类文件存放着的根目录(参看destDir在编译源文件 部分)。设定java.rmi.server.codebase属性就是为了让注册表能够调用远程接口(注意”/”结尾非常的重要),关于如何使用这个系统属性,请你参考codebase tutorial(代码库址教程)

(启动)服务输出应该如下:

Server readey

这个服务继续运行,直到用户(强制)结束(典型的结束该进程)。

运行客户端

一旦服务准备好了,客户端就可以按着如下运行:

java –classpath classDir example.hello.Client

这里的classDir还是类文件存放的根目录(参看destDir在“编译源文件”部分)。

客户端的输出如下:

response: Hello, world!


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值