RMI 框架
RMI 框架
应用在分布式开发。
在RMI中,首先服务器端的某个类要 extends java.rmi.Romote
,然后在 RMI 注册器中注册远程对象,客户端通过代理访问 RMI 注册器,查询远程对象,最后才能调用服务器上的远程对象。
详细过程
-
HelloService 接口要 extends Remote 接口,那么这类(或接口)就可以被远程访问
-
然后在将 HelloService 通过
HelloServiceImpl
类实现,然后实例化对象ser1
,将对象在 RMI上注册并重命名为HelloService1
//实例化对象 HelloService ser1 = new HelloServiceImpl("ser1"); //注册对象 registry.rebind("HelloService1",ser1);
-
在客户端实现
HelloService
接口,然后就可以调用远程对象HelloService1
HelloService service1 = (HelloService) registry.lookup("HelloService1"); System.out.println(service1.echo("远程调用成功"));
1.RMI框架出现的背景
RMI 协议(Remote Method Protocol):远程方法调用。在JAVA 中,只要一个类(或接口) extends了 java.rmi.Remote接口,这个类就可以被客户端远程访问,并提供服务。
RMI 框架封装了所有底层通信细节,并且解决了编组,分布式垃圾收集。安全检查和并发性等通用问题。有了现成的框架。
分布式对象模型
通信过程
远程对象:如果一个对象不仅能够被本地进程访问,还能被其他主机进程访问,那么这个对象就是远程对象。一般远程对象都在服务器端,能提供各种服务,客户端则访问服务器端的远程对象,请求特定的服务
对象模型的实现系统都应该具备以下功能:
- 把分布在不同节点上的对象之间发送的消息转为字节序列,这一过程称为编组(marshalling)
- 通过套接字建立连接并发送编组后的消息,即字节序列
- 处理网络连接或传输消息是出现的各种障碍
- 为分布在不同节点上的对象提供分布式垃圾收集机制
- 为远程方法调用提供安全检查机制
- 服务端运用多线程或非阻塞通信机制,确保远程对象具有很好的并发性能,能同时被多个客户访问
- 创建与特定问题领域相关的各种本地对象和远程对象
总结一句话就是:分布式系统要处理套接字,编组,分布式垃圾收集,安全检查和并发性等问题,还要开发各种本地对象和远程对象
常见分布式框架
-
RMI
-
CORBA (公共对象请求代理体系结构):分布式对象模型的通用框架,允许用不用语言的对象进行通信,但是这一技术不再流行。
-
SOAP (Simple Object Access Protocol,简单对象访问协议):允许异构的系统之间能彼此通信,以XML作为通信语言。一个系统可以访问另一个系统对外公布的web服务
2.RMI 基本原理
RMI 采用客户/服务器通信方式。在服务器上部署了提供各种服务器的远程对象,客户端请求访问服务器上远程对象的方法。
客户端请求调用远程对象的方法
如图 HelloServiceImpl
是一个远程对象,它运行在服务器上,客户端请求调用 HelloServiceImpl
对象的 echo()
方法
RMI 框架采用代理来封装通信细节
客户端和服务器端都会产生代理
- 客户端的代理被称为远程对象的存根,通过存根调用相关方法,因为存根也会实现HelloService 接口
- 服务器端的代理被称为远程对象的骨架。骨架类没有实现 HelloService 远程接口,
HelloServiceImpl
实现了HelloService
接口。严格上讲,骨架类不是代理类。代理类与被代理类实现的接口要一致 - 存根和骨架就会通过套接字进行通信,发送被编组的参数
存根发送的内容
- 被访问的远程对象的名字
- 被调用的方法的描述
- 编组后的参数的字节序列
服务器端接收到客户端的请求,骨架进行以下操作
- 反编组参数
- 定位要访问的远程对象
- 调用远程对象的相应方法
- 获取调用产生的值或异常,对其进行编组
- 把编组后的返回值或者异常发给客户
3.创建RMI 应用流程(文章最后有源码详解)
- 创建远程接口:继承
Java.rmi.Remote
接口 - 创建远程类:实现远程接口
- 创建服务器程序:负责在 RMI 注册器中注册远程对象
- 创建客户程序:负责定位远程对象,并且调用远程对象的方法
问题思考
-
为什么要实现 HelloService 接口
客户端也要实现
HelloService
,作为存根类,与服务器端进行通信。没有这个接口,客户端与服务端无法通信
参考文献
bilibili视频:https://www.bilibili.com/video/BV18N411o7cA
源代码
HelloService.java
package com;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Date;
/*
创建远程接口
接口HelloService 是可以随便命名的,只是习惯用HelloService
*/
/*
注意这是一个接口,创建方法时 和类的写法不太一样
*/
public interface HelloService extends Remote{
public String echo(String msg) throws RemoteException;
public Date getTime() throws RemoteException;
}
HelloServiceImpl.java
package com;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Date;
/*
创建远程类。运行在服务器上
远程类就是远程对象所属的类。RMI规范要求远程类必须实现一个远程接口。
此外,为了使远程类的实例 变成能为远程客户提供服务 的远程对象,可以通过继承 java.rmi.server.UnicastRemoteObject
*/
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
private String name;
protected HelloServiceImpl(String name) throws RemoteException {
this.name=name;
}
public String echo(String msg) throws RuntimeException{
System.out.println(name+"调用echo()");
return "echo:"+msg+" from " +name;
}
public Date getTime() throws RuntimeException{
System.out.println(name+"调用getTime()");
Date d = new Date();
return d;
}
}
ServicePro.java
package com;
import java.rmi.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
/*
RMI 采用一种 命名服务机制 来使得客户可以找到服务器上的一个远程对象。
RMI 注册器会提供这种命名服务
注册器就像日常生活中的114电话查询系统,如果某单位想把自己的电话公开,在114上面注册认证就可以了
启动RMI注册器的两种方式
1.直接运行 JDK 的bin目录下载的 rmiregistry.exe 程序
2.调用 java.rmi.registry.LocateRegistry 类的静态方法 createRegistry()
*/
/*
服务器的对象要想能被客户端远程调用,必须注册远程对象。
1.调用 java.rmi.registry.Registry 接口的 bind() 和 rebind()
2.除了 java.rmi.registry.Registry; 还有java.rmi.registry.Naming;也是bind() 和 rebind()
3.调用 JNDI API(JAVA Naming and Directory Interface,java名字与目录接口)
*/
public class ServicePro {
/*
Registry registry = new Registry() {
@Override //lookup 是查找对象,返回与参数name指定的名字所绑定的对象
public Remote lookup(String name) throws RemoteException, NotBoundException, AccessException {
return null;
}
@Override //取消对象与名字的绑定
public void bind(String name, Remote obj) throws RemoteException, AlreadyBoundException, AccessException {
}
@Override
public void unbind(String name) throws RemoteException, NotBoundException, AccessException {
}
@Override
public void rebind(String name, Remote obj) throws RemoteException, AccessException {
}
@Override
public String[] list() throws RemoteException, AccessException {
return new String[0];
}
};
*/
public static void main(String args[]){
try {
//实例化两个远程对象,由于接口不能实例化,因此只能通过类实现接口,在通过类进行实例化
HelloService ser1 = new HelloServiceImpl("ser1");
HelloService ser2 = new HelloServiceImpl("ser2");
//创建并启动注册器,监听1099端口
Registry registry = LocateRegistry.createRegistry(1099);
//注册远程对象,将ser1 命名为 HelloService1
registry.rebind("HelloService1",ser1);
registry.rebind("HelloService2",ser2);
System.out.println("Success Registry Two Objects");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
Client.java
package com;
/*
客户端程序
要远程访问服务器上的对象(这些对象在rmi注册器上已经注册)
*/
import sun.rmi.registry.RegistryImpl;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Client {
//列出所有对象,肯定要存在一个数组中
public static void showRemoteObject(Registry registry) throws RemoteException {
String[] names = registry.list(); //创建字符串数组
for(String name:names){
System.out.println(name);
}
}
public static void main(String agrs[]) throws RemoteException, NotBoundException {
//客户端请求连接服务器端的1099端口
Registry registry = LocateRegistry.getRegistry(1099);
//实现接口,也就是实现存根类(相当于客户端的代理)
HelloService service1 = (HelloService) registry.lookup("HelloService1");
HelloService service2 = (HelloService) registry.lookup("HelloService2");
//测试存根对象所属的类
Class stuClass = service1.getClass();
System.out.println("service1 是"+stuClass.getName()+"的实例");
// 继承了 Remote接口的 HelloService接口,被远程调用成功,证明HelloService提供了远程服务
System.out.println(service1.echo("远程调用成功"));
}
}