0x01 前言
上一篇把基础名词还有流程大概讲了一下
这一篇我们就从一个例子来演示RMI的运行流程
0x02 环境
编辑器为: IntelliJ IDEA
java版本:
java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
0x03 例子
0x03.1 案例介绍
假设我们现在有二台服务器,一台服务器放注册中心与RMI服务端,一台服务器放RMI客户端
然后现在要进行通信,让客户端调用服务端的对象
0x03.2 目录结构
// 目录结构
├── RMITest
│ ├── RMITestInterface.java
│ ├── RMIServer
│ │ ├── RMIServerTest.java
│ │ └── RMITestImpl.java
│ └── RMIClient
│ └── RMIClientTest.java
0x03.3 公共接口
先定义一个远程接口,这个接口,服务端客户端都要使用到
也就是两台服务器都要保存一份的接口
package RMITest;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* RMI测试接口
* 定义一个远程接口, 继承java.rmi.Remote接口
*/
public interface RMITestInterface extends Remote {
String test() throws RemoteException;
}
这里我们创建了一个RMITestInterface
接口,定义了一个test
方法,同时抛出RemoteException
异常
想使用RMI远程方法调用,必须继承java.rmi.Remote
接口,继承以后表示这是一个RMI标识接口
表示该类可以的方法可以从非本地虚拟机被调用执行
同时由于RMI通信本质也是基于“网络传输”,所以也需要要抛出RemoteException
异常
0x03.4 创建注册中心与服务端
接着我们创建个RMITestImpl
类,继承UnicastRemoteObject
类,实现RMITestInterface
接口
该类是一个远程接口实现类,用于被客户端调用与执行的
注意: 远程接口实现类必须继承UnicastRemoteObject
类,用于生成存根/桩(Stub)
和骨架(Skeleton)
package RMITest.RMIServer;
import RMITest.RMITestInterface;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class RMITestImpl extends UnicastRemoteObject implements RMITestInterface {
/**
* 调用父类的构造函数
*
* @throws RemoteException
*/
protected RMITestImpl() throws RemoteException {
super();
}
/**
* RMI测试方法
*
* @return 返回测试的字符串
*/
@Override
public String test() throws RemoteException {
return "test~";
}
}
创建注册中心与服务端
客户端可以通过这个输出的URL直接访问访问远程对象,不需要知道远程实例对象的名称
因为服务端将RMITestImpl对象,注册到了RMI注册中心上,并且公开了一个固定的路径 ,供客户端访问
package RMITest.RMIServer;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class RMIServerTest {
// 注册中心的服务器ip
public static final String RMI_HOST = "127.0.0.1";
// 注册中心设置的开放端口
public static final int RMI_PORT = 9998;
// RMI服务名称
public static final String RMI_NAME = "rmi://" + RMI_HOST + ":" + RMI_PORT + "/test";
/**
* 注册中心创建 与 服务端创建
*
* @param args
*/
public static void main(String[] args) {
try {
// 创建注册中心
LocateRegistry.createRegistry(RMI_PORT);
// 服务端
// 功能: 注册远程对象RMITestImpl到RMI注册中心
Naming.bind(RMI_NAME, new RMITestImpl());
// 输出该对象的访问地址
System.out.println("RMI服务启动成功,服务地址:" + RMI_NAME);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 运行该文件
0x03.5 创建客户端并执行
最后面客户端只需要调用java.rmi.Naming.lookup方法
然后通过服务端公开的路径,从服务端上拿到对应接口的实现类,之后通过本地接口即可调用远程对象的方法
注意: 最终执行的地方是在服务端,不在客户端,因为是客户端在远程调用服务端对象
package RMITest.RMIClient;
import RMITest.RMITestInterface;
import java.rmi.Naming;
public class RMIClientTest {
public static void main(String[] args) {
try {
String rmiName = "rmi://127.0.0.1:9998/test";
// 查找远程RMI服务
RMITestInterface rt = (RMITestInterface) Naming.lookup(rmiName);
// 调用远程接口RMITestInterface类的test方法
String result = rt.test();
// 输出服务端执行结果
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 运行结果
test~
0x04 RMI客户端调用远程对象大致流程
通过例子结合上一篇的文章,可以基于例子尝试理出一个运行流程
// 前置操作服务端
1. 服务端,创建RMITestImpl(远程对象)
2. 服务端,注册RMITestImpl(远程对象)到RMI注册中心
// 大致流程
1. 客户端发起请求(调用远程RMITestImpl类的test方法),请求转发至客户端存根/桩(Stub)
2. 客户端存根/桩(Stub)将请求的接口、方法、参数等信息进行序列化
3. 经过客户端传输层(Transport)将序列化后的数据传输至服务端传输层(Transport)
4. 服务端传输层(Transport)将序列化后的数据转发到对应的服务端骨架(Skeleton)
5. 服务端骨架(Skeleton)将客户端序列化后的数据,进行反序列化后调用实际处理类(例子的RMITestImpl类)
6. 实际处理类(例子的RMITestImpl类)根据请求调用test方法,将结果返回给服务端骨架(Skeleton)
7. 服务端骨架(Skeleton)将结果序列化
8. 通过服务端传输层(Transport)传输至客户端传输层(Transport)
9. 客户端传输层(Transport)将序列化后的数据转发至客户端存根/桩(Stub)
10. 客户端存根/桩(Stub)将序列化后的数据,进行反序列化,最后将反序列化后的Java Object返回给客户端
0x05 结尾
经过本文,我们体会到了RMI的正常运行流程
也清楚的知道了,如何搭建一个简单的RMI服务并进行访问
后面就可以开始学习如何利用了
未完待续….