0x01 前言

上一篇把基础名词还有流程大概讲了一下
这一篇我们就从一个例子来演示RMI的运行流程

0x02 环境

  1. 编辑器为: IntelliJ IDEA
  2. java版本:
  3. java version "1.7.0_80"
  4. Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
  5. Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)

0x03 例子

0x03.1 案例介绍

假设我们现在有二台服务器,一台服务器放注册中心与RMI服务端,一台服务器放RMI客户端
然后现在要进行通信,让客户端调用服务端的对象

0x03.2 目录结构

  1. // 目录结构
  2. ├── RMITest
  3. ├── RMITestInterface.java
  4. ├── RMIServer
  5. ├── RMIServerTest.java
  6. └── RMITestImpl.java
  7. └── RMIClient
  8. └── RMIClientTest.java

0x03.3 公共接口

先定义一个远程接口,这个接口,服务端客户端都要使用到
也就是两台服务器都要保存一份的接口

  1. package RMITest;
  2. import java.rmi.Remote;
  3. import java.rmi.RemoteException;
  4. /**
  5. * RMI测试接口
  6. * 定义一个远程接口, 继承java.rmi.Remote接口
  7. */
  8. public interface RMITestInterface extends Remote {
  9. String test() throws RemoteException;
  10. }

这里我们创建了一个RMITestInterface接口,定义了一个test方法,同时抛出RemoteException异常
想使用RMI远程方法调用,必须继承java.rmi.Remote接口,继承以后表示这是一个RMI标识接口
表示该类可以的方法可以从非本地虚拟机被调用执行

同时由于RMI通信本质也是基于“网络传输”,所以也需要要抛出RemoteException异常

0x03.4 创建注册中心与服务端

接着我们创建个RMITestImpl类,继承UnicastRemoteObject类,实现RMITestInterface接口
该类是一个远程接口实现类,用于被客户端调用与执行的
注意: 远程接口实现类必须继承UnicastRemoteObject类,用于生成存根/桩(Stub)骨架(Skeleton)

  1. package RMITest.RMIServer;
  2. import RMITest.RMITestInterface;
  3. import java.rmi.RemoteException;
  4. import java.rmi.server.UnicastRemoteObject;
  5. public class RMITestImpl extends UnicastRemoteObject implements RMITestInterface {
  6. /**
  7. * 调用父类的构造函数
  8. *
  9. * @throws RemoteException
  10. */
  11. protected RMITestImpl() throws RemoteException {
  12. super();
  13. }
  14. /**
  15. * RMI测试方法
  16. *
  17. * @return 返回测试的字符串
  18. */
  19. @Override
  20. public String test() throws RemoteException {
  21. return "test~";
  22. }
  23. }

创建注册中心与服务端
客户端可以通过这个输出的URL直接访问访问远程对象,不需要知道远程实例对象的名称
因为服务端将RMITestImpl对象,注册到了RMI注册中心上,并且公开了一个固定的路径 ,供客户端访问

  1. package RMITest.RMIServer;
  2. import java.rmi.Naming;
  3. import java.rmi.registry.LocateRegistry;
  4. public class RMIServerTest {
  5. // 注册中心的服务器ip
  6. public static final String RMI_HOST = "127.0.0.1";
  7. // 注册中心设置的开放端口
  8. public static final int RMI_PORT = 9998;
  9. // RMI服务名称
  10. public static final String RMI_NAME = "rmi://" + RMI_HOST + ":" + RMI_PORT + "/test";
  11. /**
  12. * 注册中心创建 与 服务端创建
  13. *
  14. * @param args
  15. */
  16. public static void main(String[] args) {
  17. try {
  18. // 创建注册中心
  19. LocateRegistry.createRegistry(RMI_PORT);
  20. // 服务端
  21. // 功能: 注册远程对象RMITestImpl到RMI注册中心
  22. Naming.bind(RMI_NAME, new RMITestImpl());
  23. // 输出该对象的访问地址
  24. System.out.println("RMI服务启动成功,服务地址:" + RMI_NAME);
  25. } catch (Exception e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. }
  30. // 运行该文件

0x03.5 创建客户端并执行

最后面客户端只需要调用java.rmi.Naming.lookup方法
然后通过服务端公开的路径,从服务端上拿到对应接口的实现类,之后通过本地接口即可调用远程对象的方法
注意: 最终执行的地方是在服务端,不在客户端,因为是客户端在远程调用服务端对象

  1. package RMITest.RMIClient;
  2. import RMITest.RMITestInterface;
  3. import java.rmi.Naming;
  4. public class RMIClientTest {
  5. public static void main(String[] args) {
  6. try {
  7. String rmiName = "rmi://127.0.0.1:9998/test";
  8. // 查找远程RMI服务
  9. RMITestInterface rt = (RMITestInterface) Naming.lookup(rmiName);
  10. // 调用远程接口RMITestInterface类的test方法
  11. String result = rt.test();
  12. // 输出服务端执行结果
  13. System.out.println(result);
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  19. // 运行结果
  20. test~

0x04 RMI客户端调用远程对象大致流程

通过例子结合上一篇的文章,可以基于例子尝试理出一个运行流程

  1. // 前置操作服务端
  2. 1. 服务端,创建RMITestImpl(远程对象)
  3. 2. 服务端,注册RMITestImpl(远程对象)到RMI注册中心
  1. // 大致流程
  2. 1. 客户端发起请求(调用远程RMITestImpl类的test方法),请求转发至客户端存根/桩(Stub)
  3. 2. 客户端存根/桩(Stub)将请求的接口、方法、参数等信息进行序列化
  4. 3. 经过客户端传输层(Transport)将序列化后的数据传输至服务端传输层(Transport)
  5. 4. 服务端传输层(Transport)将序列化后的数据转发到对应的服务端骨架(Skeleton)
  6. 5. 服务端骨架(Skeleton)将客户端序列化后的数据,进行反序列化后调用实际处理类(例子的RMITestImpl类)
  7. 6. 实际处理类(例子的RMITestImpl类)根据请求调用test方法,将结果返回给服务端骨架(Skeleton)
  8. 7. 服务端骨架(Skeleton)将结果序列化
  9. 8. 通过服务端传输层(Transport)传输至客户端传输层(Transport)
  10. 9. 客户端传输层(Transport)将序列化后的数据转发至客户端存根/桩(Stub)
  11. 10. 客户端存根/桩(Stub)将序列化后的数据,进行反序列化,最后将反序列化后的Java Object返回给客户端

0x05 结尾

经过本文,我们体会到了RMI的正常运行流程
也清楚的知道了,如何搭建一个简单的RMI服务并进行访问

后面就可以开始学习如何利用了

未完待续….