一、什么RMI
RMI:远程方法调佣(Remort Method Invocation),它支持存储于不同地址空间的程序级对象之间彼此通信,实现远程对象之间的无缝远程调用。
JAVA RMI:用于不同虚拟机之间的通信,这些虚拟机可以在不同的主机上,也可以在同一个主机上;一个虚拟机中的对象调用另一个虚拟机上中的对象的方法,只不过是允许被远程调用的对象要通过一些标志加以标识。
远程过程调用(Remote Procedure Call,RPC):可以用于一个进程调用另一个进程(很可能在另一个远程主机上)中的过程,从而提供了过程的分布能力。Java的RMI则在RPC的基础上向前又迈进了一步,既提供分布式对象间通讯。
二、RMI原理
2.1 工作原理图
2.2 工作原理
方法调用从客户对象经占位程序(Stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向下,传递给主机,然后再次经传输层,向上穿过远程调用层和骨干网(Skeleton),到达服务器对象。 占位程序扮演着远程服务器对象的代理的角色,使该对象可被客户激活。 远程引用层处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。传输层管理实际的连接,并且追踪可以接受方法调用的远程对象。服务器端的骨干网完成对服务器对象实际的方法调用,并获取返回值。返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,占位程序获得返回值。
实际上,客户端只与代表远程主机中对象的Stub对象进行通信,丝毫不知道Server的存在。客户端只是调用Stub对象中的本地方法,Stub对象是一个本地对象,它实现了远程对象向外暴露的接口,也就是说它的方法和远程对象暴露的方法的签名是相同的。客户端认为它是调用远程对象的方法,实际上是调用Stub对象中方法,可以理解为Stub对象是远程对象在本地的一个代理,当客户端调用方法的时候,Stub对象会将调用通过网络传输给远程对象。
三、RMI远程调用步骤
3.1 RMI远程调用运行流程图
3.2 RMI 远程调用步骤
- 客户端对象调用客户端辅助对象(Stub)上的方法。
- 客户端辅助对象打包调用信息(变量、方法名),通过网络发送给服务端辅助对象。
- 服务端辅助对象将客户端辅助对象发送来的信息解包,找出真正被调用的方法以及该方法所在对象。
- 调用真正服务对象上的真正方法,并将结果返回给服务端辅助对象。
- 服务端辅助对象将结果打包,发送给客户端辅助对象。
- 客户端辅助对象将返回值解包,返回给客户对象
- 客户对象获得返回值
四、JAVA RMI简单实现
4.1 如何实现一个RMI程序
1). 创建远程接口, 并且继承java.rmi.Remote接口。
2). 实现远程接口,并且继承:UnicastRemoteObject。
3). 创建服务器程序: createRegistry方法注册远程对象,暴露一个监听。
4). 创建客户端程序,通过ip和端口连接到指定的服务器,并且将数据做封装(序列化)
5). 服务器端收到请求,先反序列化。再进行业务逻辑处理。把返回结果序列化返回
4.2 JAVA实现RMI程序
1). 定义一个远程接口
/** * 必须继承Remote接口。 * 所有参数和返回类型必须序列化(因为要网络传输)。 * 任意远程对象都必须实现此接口。 * 只有远程接口中指定的方法可以被调用。 */ public interface IRemoteMath extends Remote { // 所有方法必须抛出RemoteException public double add(double a, double b) throws RemoteException; public double subtract(double a, double b) throws RemoteException; }
2). 远程接口实现类
/** * 服务器端实现远程接口。 * 必须继承UnicastRemoteObject,以允许JVM创建远程的存根/代理。 */ public class RemoteMath extends UnicastRemoteObject implements IRemoteMath { private int numberOfComputations; protected RemoteMath() throws RemoteException { numberOfComputations = 0; } @Override public double add(double a, double b) throws RemoteException { numberOfComputations++; System.out.println("Number of computations performed so far = " + numberOfComputations); return (a+b); } @Override public double subtract(double a, double b) throws RemoteException { numberOfComputations++; System.out.println("Number of computations performed so far = " + numberOfComputations); return (a-b); } }
3). 服务器端
/* 注册远程对象,向客户端提供远程对象服务 * 远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称 * 但是,将远程对象注册到RMI Service之后,客户端就可以通过RMI Service请求 * 到该远程服务对象的stub了,利用stub代理就可以访问远程服务对象了 */ public class RMIServer { public static void main(String[] args) { try { IRemoteMath remoteMath = new RemoteMath(); LocateRegistry.createRegistry(1088); Registry registry = LocateRegistry.getRegistry(); registry.bind("Compute", remoteMath); System.out.println("Math server ready"); } catch (Exception e) { e.printStackTrace(); } } }
4). 客户端
public class MathClient { public static void main(String[] args) { try { // 如果RMI Registry就在本地机器上,URL就是:rmi://localhost:1088/Compute // 否则,URL就是:rmi://RMIService_IP:1088/Compute Registry registry = LocateRegistry.getRegistry("localhost"); // 从Registry中检索远程对象的存根/代理 IRemoteMath remoteMath = (IRemoteMath)registry.lookup("Compute"); // 调用远程对象的方法 double addResult = remoteMath.add(5.0, 3.0); System.out.println("5.0 + 3.0 = " + addResult); double subResult = remoteMath.subtract(5.0, 3.0); System.out.println("5.0 - 3.0 = " + subResult); }catch(Exception e) { e.printStackTrace(); } } }