Java Rmi协议学习

0x00 Rmi介绍

RMI(Remote Method Invocation)即 Java 远程方法调用,RMI 用于构建分布式应用程序,RMI 实现了 Java 程序之间跨 JVM 的远程通信。

而JAVA本身提供了一种RPC框架 RMI及Java 远程方法调用(Java Remote Method Invocation),可以在不同的Java 虚拟机之间进行对象间的通讯,RMI是基于JRMP协议(Java Remote Message Protocol Java远程消息交换协议)去实现的。

0x01 实现原理

客户端只与代表远程主机中对象的Stub对象进行通信,丝毫不知道Server的存在。客户端只是调用Stub对象中的本地方法,Stub对象是一个本地对象,它实现了远程对象向外暴露的接口。客户端认为它是调用远程对象的方法,实际上是调用Stub对象中的方法。可以理解为Stub对象是远程对象在本地的一个代理,当客户端调用方法的时候,Stub对象会将调用通过网络传递给远程对象。

完整的通讯过程:

  1. RMI客户端在调用远程方法时会先创建Stub(sun.rmi.registry.RegistryImpl_Stub)
  2. Stub会将Remote对象传递给远程引用层(java.rmi.server.RemoteRef)并创建java.rmi.server.RemoteCall(远程调用)对象。
  3. RemoteCall序列化RMI服务名称Remote对象。
  4. RMI客户端远程引用层传输RemoteCall序列化后的请求信息通过Socket连接的方式传输到RMI服务端远程引用层
  5. RMI服务端远程引用层(sun.rmi.server.UnicastServerRef)收到请求会请求传递给Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)
  6. Skeleton调用RemoteCall反序列化RMI客户端传过来的序列化。
  7. Skeleton处理客户端请求:bindlistlookuprebindunbind,如果是lookup则查找RMI服务名绑定的接口对象,序列化该对象并通过RemoteCall传输到客户端。
  8. RMI客户端反序列化服务端结果,获取远程对象的引用。
  9. RMI客户端调用远程方法,RMI服务端反射调用RMI服务实现类的对应方法并序列化执行结果返回给客户端。
  10. RMI客户端反序列化RMI远程方法调用结果。

0x02 远程方法调用案例

上代码!!!

文件名:UserData.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package top.sh1yan.rmi3;

import java.io.Serializable;

public class UserData implements Serializable {
private String name;
private int age;
public UserData() {
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

文件名:IShowNews.java

1
2
3
4
5
6
7
8
9
package top.sh1yan.rmi3;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface IShowNews extends Remote {
public String getName() throws RemoteException;
public int getAge() throws RemoteException;
}

文件名:ShowNewsImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package top.sh1yan.rmi3;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class ShowNewsImpl extends UnicastRemoteObject implements IShowNews {

UserData Ar1 = new UserData();

protected ShowNewsImpl() throws RemoteException {
super();
}

@Override
public String getName() throws RuntimeException {
Ar1.setName("shiyan");
return Ar1.getName();
}

@Override
public int getAge() throws RuntimeException {
Ar1.setAge(1);
return Ar1.getAge();
}
}

文件名:RmiServerSy.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package top.sh1yan.rmi3;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class RmiServerSy {
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
LocateRegistry.createRegistry(9999);
Naming.bind("rmi://127.0.0.1:9999/shownews",new ShowNewsImpl());
System.out.println("Rmi服务端启动成功....");
}
}

文件名:ClientSy.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package top.sh1yan.rmi3;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class ClientSy {
public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
IShowNews Ar1 = (IShowNews) Naming.lookup("rmi://127.0.0.1:9999/shownews");
System.out.println("我的网名叫:"+Ar1.getName());
System.out.println("我的年龄:"+Ar1.getAge());
}
}

到这里需要需要启动下服务端:Run RmiServerSy.main()

1
2
3
D:\Java\jdk1.8.0_191\bin\java.exe "-javaagent:D:\My-software\IntelliJ IDEA 2020.1\lib\idea_rt.jar=59487:D:\My-software\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath D:\Java\jdk1.8.0_191\jre\lib\charsets.jar;D:\Java\jdk1.8.0_191\jre\lib\deploy.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;D:\Java\jdk1.8.0_191\jre\lib\javaws.jar;D:\Java\jdk1.8.0_191\jre\lib\jce.jar;D:\Java\jdk1.8.0_191\jre\lib\jfr.jar;D:\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;D:\Java\jdk1.8.0_191\jre\lib\jsse.jar;D:\Java\jdk1.8.0_191\jre\lib\management-agent.jar;D:\Java\jdk1.8.0_191\jre\lib\plugin.jar;D:\Java\jdk1.8.0_191\jre\lib\resources.jar;D:\Java\jdk1.8.0_191\jre\lib\rt.jar;E:\Programming_related\javacode\IDEA_Code\out\production\test001 top.sh1yan.rmi3.RmiServerSy

Rmi服务端启动成功....

其次,我们再运行下客户端代码:Run ClientSy.main()

1
2
3
4
5
6
D:\Java\jdk1.8.0_191\bin\java.exe "-javaagent:D:\My-software\IntelliJ IDEA 2020.1\lib\idea_rt.jar=59514:D:\My-software\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath D:\Java\jdk1.8.0_191\jre\lib\charsets.jar;D:\Java\jdk1.8.0_191\jre\lib\deploy.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;D:\Java\jdk1.8.0_191\jre\lib\javaws.jar;D:\Java\jdk1.8.0_191\jre\lib\jce.jar;D:\Java\jdk1.8.0_191\jre\lib\jfr.jar;D:\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;D:\Java\jdk1.8.0_191\jre\lib\jsse.jar;D:\Java\jdk1.8.0_191\jre\lib\management-agent.jar;D:\Java\jdk1.8.0_191\jre\lib\plugin.jar;D:\Java\jdk1.8.0_191\jre\lib\resources.jar;D:\Java\jdk1.8.0_191\jre\lib\rt.jar;E:\Programming_related\javacode\IDEA_Code\out\production\test001 top.sh1yan.rmi3.ClientSy

我的网名叫:shiyan
我的年龄:1

进程已结束,退出代码 0

0x03 远程更新数据

继续上代码!!!

文件名:User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package top.sh1yan.rmi2;

import java.io.Serializable;

public class User implements Serializable {
private static final long serialVersionUID = 1L;

private String name;
private int age;

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

文件名:IUpData.java

1
2
3
4
5
6
7
8
9
10
package top.sh1yan.rmi2;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface IUpData extends Remote {
public User updata(User user) throws RemoteException;
public User getShow() throws RemoteException;

}

文件名:UpDataImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package top.sh1yan.rmi2;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class UpDataImpl extends UnicastRemoteObject implements IUpData {

User Ar1 = new User();

private static final long serialVersionUID = 1L;

protected UpDataImpl() throws RemoteException {
Ar1.setName("初始姓名");
Ar1.setAge(0);
}

@Override
public User updata(User user) throws RemoteException {
Ar1.setName(user.getName());
Ar1.setAge(user.getAge());
return Ar1;
}

@Override
public User getShow() throws RemoteException{
return Ar1;
}

}

文件名:RmiServerTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package top.sh1yan.rmi2;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class RmiServerTest {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(8888);
Naming.bind("rmi://localhost:8888/rdata",new UpDataImpl());
System.out.println("远程服务端启动成功,等待客户端调用...");
}catch (RemoteException e){
System.out.println("创建远程对象发生异常!");
e.printStackTrace();
}catch (AlreadyBoundException e) {
System.out.println("发生重复绑定对象异常!");
e.printStackTrace();
}catch (MalformedURLException e) {
System.out.println("发生URL畸形异常!");
e.printStackTrace();
}
}
}

文件名:ClientTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package top.sh1yan.rmi2;

import java.rmi.Naming;

public class ClientTest {
public static void main(String[] args) throws Exception {
IUpData lookup = (IUpData)Naming.lookup("rmi://localhost:8888/rdata");
User Br1 = new User();
Br1.setName("shiyan");
Br1.setAge(1);
System.out.println("远程服务器原始数据为:" + lookup.getShow().toString());
lookup.updata(Br1);
System.out.println("远程服务器现有数据为:" + lookup.getShow().toString());
}
}

到这里需要需要启动下服务端:Run RmiServerTest.main()

1
2
3
D:\Java\jdk1.8.0_191\bin\java.exe "-javaagent:D:\My-software\IntelliJ IDEA 2020.1\lib\idea_rt.jar=59731:D:\My-software\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath D:\Java\jdk1.8.0_191\jre\lib\charsets.jar;D:\Java\jdk1.8.0_191\jre\lib\deploy.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;D:\Java\jdk1.8.0_191\jre\lib\javaws.jar;D:\Java\jdk1.8.0_191\jre\lib\jce.jar;D:\Java\jdk1.8.0_191\jre\lib\jfr.jar;D:\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;D:\Java\jdk1.8.0_191\jre\lib\jsse.jar;D:\Java\jdk1.8.0_191\jre\lib\management-agent.jar;D:\Java\jdk1.8.0_191\jre\lib\plugin.jar;D:\Java\jdk1.8.0_191\jre\lib\resources.jar;D:\Java\jdk1.8.0_191\jre\lib\rt.jar;E:\Programming_related\javacode\IDEA_Code\out\production\test001 top.sh1yan.rmi2.RmiServerTest

远程服务端启动成功,等待客户端调用...

其次,我们再运行下客户端代码:Run ClientTest.main()

1
2
3
4
5
6
D:\Java\jdk1.8.0_191\bin\java.exe "-javaagent:D:\My-software\IntelliJ IDEA 2020.1\lib\idea_rt.jar=59743:D:\My-software\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath D:\Java\jdk1.8.0_191\jre\lib\charsets.jar;D:\Java\jdk1.8.0_191\jre\lib\deploy.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;D:\Java\jdk1.8.0_191\jre\lib\javaws.jar;D:\Java\jdk1.8.0_191\jre\lib\jce.jar;D:\Java\jdk1.8.0_191\jre\lib\jfr.jar;D:\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;D:\Java\jdk1.8.0_191\jre\lib\jsse.jar;D:\Java\jdk1.8.0_191\jre\lib\management-agent.jar;D:\Java\jdk1.8.0_191\jre\lib\plugin.jar;D:\Java\jdk1.8.0_191\jre\lib\resources.jar;D:\Java\jdk1.8.0_191\jre\lib\rt.jar;E:\Programming_related\javacode\IDEA_Code\out\production\test001 top.sh1yan.rmi2.ClientTest

远程服务器原始数据为:User{name='初始姓名', age=0}
远程服务器现有数据为:User{name='shiyan', age=1}

进程已结束,退出代码 0

0x04 Rmi 用法总结

1、服务端需先创建一个引用对象,该对象必须继承 Serializable 接口,也就是该引用对象必须是可序列化的。

参考 0x02 中 UserData.java 文件;

2、创建接口规范类,该类必须 Remote 类,同时该创建的接口类中的方法必须设置 RemoteException 异常;同时客户端使用的方法必须都写在该接口规范中,否则客户端无法远程调用使用,就算在实现类中的写的方法,客户端也无法远程调用使用。

参考 0x02 中 IShowNews.java 文件;

3、当引用对象和接口规范都完成后,开始写接口的实现类;接口的实现类除了需继承创建的接口,还需继承 UnicastRemoteObject 类。

参考 0x02 中 ShowNewsImpl.java 文件;

4、剩下为开始写Rmi的服务端和客户端;

5、Rmi服务端首选需要进行设置服务端端口号:LocateRegistry.createRegistry(端口号);

6、其次考试绑定URL地址:Naming.bind(“rmi://127.0.0.1:端口号/资源名称”,new 接口的实现类());

参考 0x02 中 RmiServerSy.java 文件

7、客户端这里用过 Naming.lookup() 进行获取远程对象,获取到的远程对象需要强制转换成接口规范的类型。

参考 0x02 中 ClientSy.java 文件

8、用法到这里就结束了,总的来说使用起来还是很方便的。

0x05 心得总结

对于一些概念性的东西,还是多敲几遍实现代码能很快的理解了,纯看文字理论的话,很容易把自己给看懵的。

0x06 参考文章

[1] https://blog.csdn.net/wubinghai/article/details/82951769

[2] https://paper.seebug.org/1194/

[3] https://javasec.org/javase/RMI/