java RMI原理详解
来源:互联网 发布:sql语句创建的表在哪 编辑:程序博客网 时间:2024/05/23 18:32
定义
RMI(Remote Method Invocation)为远程方法调用,是允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。 这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。
Java RMI:Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。
我们知道远程过程调用(Remote Procedure Call, RPC)可以用于一个进程调用另一个进程(很可能在另一个远程主机上)中的过程,从而提供了过程的分布能力。Java 的 RMI 则在 RPC 的基础上向前又迈进了一步,即提供分布式对象间的通讯。
本地对象调用
我们先看看本地对象方法的调用:
ObjectClass objectA = new ObjectClass(); String retn = objectA.Method();
但是想想,如果objectA对象在JVM a上;而我们的程序在JVM b上,而且想访问JVM a上的objectA对象方法,如何做呢?对于JVM b上的应用程序来说,是不知道JVM a上创建的ObjectClass实例对象名称是什么,因为这次我创建的实例对象可能是objectA,下次我程序一改,我创建的实例对象又叫objectB了,另外,我创没创建ObjectClass实例对象,JVM b上应用程序又怎么知道呢?
RMI就解决了这个问题。
工作原理
方法调用从客户对象经占位程序(Stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向下,传递给主机,然后再次经传 输层,向上穿过远程调用层和骨干网(Skeleton),到达服务器对象。 占位程序扮演着远程服务器对象的代理的角色,使该对象可被客户激活。 远程引用层处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。传输层管理实际的连接,并且追踪可以接受方法调用的远程对象。服务器端的骨干网完成对服务器对象实际的方法调用,并获取返回值。返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,占位程序获得返回值。
要完成以上步骤需要有以下几个步骤:
1、 生成一个远程接口
2、 实现远程对象(服务器端程序)
3、 生成占位程序和骨干网(服务器端程序)
4、 编写服务器程序
5、 编写客户程序
6、 注册远程对象
7、 启动远程对象
由于有RMI系统的支持,我们写RMI应用程序时只需要继承相关类,实现相关接口就可以了。也就是说,我们只需要定义接口、接口实现、客户端程序和服务端程序就可以了。
上图中的stub和skeleton代理都是在服务端程序中由RMI系统动态生成,服务端程序只需要继承java.rmi.server.UnicastRemoteObject类。
那么上图中的RMI Service(RMI registry)是怎么回事呢?
先卖个关子:
可以说,RMI由3个部分构成,第一个是RMIService即JDK提供的一个可以独立运行的程序(bin目录下的rmiregistry),第二个是RMIServer即我们自己编写的一个java项目,这个项目对外提供服务。第三个是RMIClient即我们自己编写的另外一个java项目,这个项目远程使用RMIServer提供的服务。
首先,RMIService必须先启动并开始监听对应的端口。
其次,RMIServer将自己提供的服务的实现类注册到RMIService上,并指定一个访问的路径(或者说名称)供RMIClient使用。
最后,RMIClient使用事先知道(或和RMIServer约定好)的路径(或名称)到RMIService上去寻找这个服务,并使用这个服务在本地的接口调用服务的具体方法。
通俗的讲完了再稍微技术的讲下:
首先,在一个JVM中启动rmiregistry服务,启动时可以指定服务监听的端口,也可以使用默认的端口。
其次,RMIServer在本地先实例化一个提供服务的实现类,然后通过RMI提供的Naming,Context,Registry等类的bind或rebind方法将刚才实例化好的实现类注册到RMIService上并对外暴露一个名称。
最后,RMIClient通过本地的接口和一个已知的名称(即RMIServer暴露出的名称)再使用RMI提供的Naming,Context,Registry等类的lookup方法从RMIService那拿到实现类。这样虽然本地没有这个类的实现类,但所有的方法都在接口里了,想怎么调就怎么调吧。
值得注意的是理论上讲RMIService,RMIServer,RMIClient可以部署到3个不同的JVM中,这个时候的执行的顺序是RMIService---RMIServer—RMIClient。另外也可以由RMIServer来启动RMIService这时候执行的顺序是RMIServer—RMIService—RMIClient。
实际应用中很少有单独提供一个RMIService服务器,开发的时候可以使用Registry类在RMIServer中启动RMIService。
Java RMI 简单示例
1. 定义一个远程接口
/* IHello.java */package mytest;/* * 在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对象, * 供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上 * 调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口” * (扩展 java.rmi.Remote 的接口)中指定的这些方法才可被远程调用。 */import java.rmi.Remote;public interface IHello extends Remote { /* extends了Remote接口的类或者其他接口中的方法若是声明抛出了RemoteException异常, * 则表明该方法可被客户端远程访问调用。 */public String sayHello(String name) throws java.rmi.RemoteException;}
2. 远程接口实现类
/* HelloImpl.java */package mytest;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;/* * 远程对象必须实现java.rmi.server.UniCastRemoteObject类,这样才能保证客户端访问获得远程对象时, * 该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所获得的这个拷贝称为“存根”, * 而服务器端本身已存在的远程对象则称之为“骨架”。其实此时的存根是客户端的一个代理,用于与服务器端的通信, * 而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。 */ /* java.rmi.server.UnicastRemoteObject构造函数中将生成stub和skeleton */public class HelloImpl extends UnicastRemoteObject implements IHello { // 这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常 protected HelloImpl() throws RemoteException { super(); } private static final long serialVersionUID = 4077329331699640331L; public String sayHello(String name) throws RemoteException { return "Hello " + name + " ^_^ "; }}
3. 服务端
/* HelloServer.java */package mytest;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;/* 注册远程对象,向客户端提供远程对象服务 * 远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称 * 但是,将远程对象注册到RMI Service之后,客户端就可以通过RMI Service请求 * 到该远程服务对象的stub了,利用stub代理就可以访问远程服务对象了 */ public class HelloServer { public static void main(String[] args) { try { IHello hello = new HelloImpl(); /* 生成stub和skeleton,并返回stub代理引用 */ /* 本地创建并启动RMI Service,被创建的Registry服务将在指定的端口上侦听到来的请求 * 实际上,RMI Service本身也是一个RMI应用,我们也可以从远端获取Registry: * public interface Registry extends Remote; * public static Registry getRegistry(String host, int port) throws RemoteException; */ LocateRegistry.createRegistry(1099); /* 将stub代理绑定到Registry服务的URL上 */ java.rmi.Naming.rebind("rmi://localhost:1099/hello", hello); System.out.print("Ready"); } catch (Exception e) { e.printStackTrace(); } }}
4. 客户端
/* Hello_RMI_Client.java */package mytest;import java.rmi.Naming;/* 客户端向服务端请求远程对象服务 */public class Hello_RMI_Client { public static void main(String[] args) { try { /* 从RMI Registry中请求stub * 如果RMI Service就在本地机器上,URL就是:rmi://localhost:1099/hello * 否则,URL就是:rmi://RMIService_IP:1099/hello */ IHello hello = (IHello) Naming.lookup("rmi://localhost:1099/hello"); /* 通过stub调用远程接口实现 */ System.out.println(hello.sayHello("zhangxianxin")); } catch (Exception e) { e.printStackTrace(); } }}
RMI 应用各个类的交互时序图
RMI应用需要用到的类
实现一个stub和skeleton程序
public interface Person { public int getAge() throws Throwable; public String getName() throws Throwable; }
Person_Stub代码:
import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.net.Socket; public class Person_Stub implements Person { private Socket socket; public Person_Stub() throws Throwable { // connect to skeleton socket = new Socket("computer_name", 9000); } public int getAge() throws Throwable { // pass method name to skeleton ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject("age"); outStream.flush(); ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); return inStream.readInt(); } public String getName() throws Throwable { // pass method name to skeleton ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject("name"); outStream.flush(); ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); return (String)inStream.readObject(); }}
import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.net.Socket; import java.net.ServerSocket; public class Person_Skeleton extends Thread { private PersonServer myServer; public Person_Skeleton(PersonServer server) { // get reference of object server this.myServer = server; } public void run() { try { // new socket at port 9000 ServerSocket serverSocket = new ServerSocket(9000); // accept stub's request Socket socket = serverSocket.accept(); while (socket != null) { // get stub's request ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); String method = (String)inStream.readObject(); // check method name if (method.equals("age")) { // execute object server's business method int age = myServer.getAge(); ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); // return result to stub outStream.writeInt(age); outStream.flush(); } if(method.equals("name")) { // execute object server's business method String name = myServer.getName(); ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); // return result to stub outStream.writeObject(name); outStream.flush(); } } } catch(Throwable t) { t.printStackTrace(); System.exit(0); } } }
Skeleton类 extends from Thread,它长驻在后台运行,随时接收client发过来的request。并根据发送过来的key去调用相应的business method。
public class PersonServer implements Person { private int age; private String name; public PersonServer(String name, int age) { this.age = age; this.name = name; } public int getAge() { return age; } public String getName() { return name; } public static void main(String args []) { // new object server PersonServer person = new PersonServer("Richard", 34); Person_Skeleton skel = new Person_Skeleton(person); skel.start(); } }
public class PersonClient { public static void main(String [] args) { try { Person person = new Person_Stub(); int age = person.getAge(); String name = person.getName(); System.out.println(name + " is " + age + " years old"); } catch(Throwable t) { t.printStackTrace(); } } }
Client(PersonClient)的本质是,它要知道Person接口的定义,并实例一个Person_Stub,通过Stub来调用business method,至于Stub怎么去和Server沟通,Client就不用管了。
Person person = new Person_Stub();而不是Person_Stub person = new Person_Stub();为什么?因为要面向接口编程嘛,呵呵。
参考
1. http://www.blogjava.net/zhenyu33154/articles/320245.html
2. http://guojuanjun.blog.51cto.com/277646/1423392/
3. http://blog.sina.com.cn/s/blog_4918a7d90100oftg.html
4. http://haolloyin.blog.51cto.com/1177454/332426/
5. http://www.it165.net/pro/html/201404/11411.html
6. http://spidermanzy.iteye.com/blog/1741045
7. http://www.infoq.com/cn/articles/cf-java-object-serialization-rmi/
8. http://www.cnblogs.com/yin-jingyu/archive/2012/06/14/2549361.html
- java RMI原理详解
- java RMI原理详解
- java RMI原理详解
- java RMI原理详解
- java RMI原理详解
- Java RMI原理
- java RMI原理解析
- java RMI原理解析
- Java RMI详解
- Java RMI详解
- Java RMI详解
- Java RMI详解
- Java RMI详解(入门)
- Java RMI详解
- Java RMI详解
- java rmi 详解
- Java RMI详解
- Java RMI详解
- 凌阳16位单片机之定时器中断
- 第十一周项目二 存储班长信息的学生类(派生类)
- 浅谈linux性能调优之十四:调节socket缓冲区
- myeclipse tomcat7.0 内存配置
- poj 1325 最小顶点覆盖
- java RMI原理详解
- 第11周 自选阅读3
- 测试用例
- 微软近年来很给力啊!
- 抑郁症的表现及危害
- 单链表应用--一元多项式求和
- Android多线程下安全访问数据库
- 中断处理流程分析
- android 三种定时器的写法