RMI原理及流程

前言

学习过程中发现有很多“概念性”的东西,导致大段的文字说明出现。

java这门语言,我自认为很多“概念性”的东西其实源自于它的语言特性像继承多态这种,所以其实可以不用说的这么繁琐。

以后我记笔记的时候应该要尝试怎么用最简单的语言去描述,并且阅读源码才是最重要的。

学习过程中愈加感觉到,耐心,细心这两点尤其重要!

初识JAVA RMI

定义

RMI(Remote Method Invocation)为远程方法调用,是允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。 这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。

Java RMI:Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。

https://blog.csdn.net/xinghun_4/article/details/45787549

流程

整个RMI的流程分为三个部分,RMIServer , RMIClient , RMIRegister,其中RMIRegister(RMIService)担任中间人的身份

服务端( RMIServer ) 会将自己提供的服务的实现类交给这个中间人 , 并公开一个名称 . 任何客户端( RMIClient )都可以通过公开的名称找到这个实现类 , 并调用它

这样做避免了服务端和客户端资源的直接交互,也使得客户端能更好地查找要使用的对象。解决了一部分安全问题。

关于Stub存根:

在 JVM 之间通过 RMI 进行远程对象的调用时,并不是简单地直接将远程对象复制一份传递给客户端,而是传递了一个远程对象 Stub ,Stub 基本上相当于是远程对象的引用或者 代理 (java 在 RMI 中用到了代理模式)。Stub 对象对于我们是透明的,客户端可以像调用本地方法一样直通过 Stub 对象来调用远程的方法。

image-20220304173116-y7586sp.png

交互时序图:

image-20220304205514-millkuw.png

实现一个简单的rmi应用

定义一个远程接口

准备一个接口,让客户端通过这个接口来访问服务

1
2
3
4
public interface Hello extends Remote {
String sayHello(String name) throws RemoteException;
}

  • 在 Java 中 , 如果一个类继承了 java.rmi.Remote 接口 , 那么该类将成为一个服务端的远程对象 , 供客户端访问并提供一定的服务

  • JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”(扩展java.rmi.Remote 的接口)中指定的这些方法才可被远程调用。

  • extends了Remote接口的类或者其他接口中的方法若是声明抛出了RemoteException异常,则表明该方法可被客户端远程访问调用。

远程接口实现类

1
2
3
4
5
6
7
8
9
10
11
public class HelloImpl extends UnicastRemoteObject implements Hello {
protected HelloImpl() throws RemoteException {
super();
}

@Override
public String sayHello(String name) throws RemoteException {
System.out.println("Connect from "+name);
return "hello there";
}
}

远程对象必须实现java.rmi.server.UniCastRemoteObject类,这样才能保证客户端访问获得远程对象时,该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所获得的这个拷贝称为“存根Stub”,而服务器端本身已存在的远程对象则称之为“骨架Skeleton”。

其实此时的存根是客户端的一个代理,用于与服务器端的通信,而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。

其实 , 与其说是客户端和服务端进行交互 , 不如说是 客户端代理( Stub ) 和 服务端代理( Skeleton ) 在进行交互 。

在 JDK1.2 以后的 RMI 中 , 可以通过反射API 直接将请求发送给真实类 , 不再需要 Skeleton 来做中转了

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RmiServer {
public static void main(String[] args) {
try {
//生成服务
Hello hello = new HelloImpl();//生成stub和skeleton,并返回stub代理引用
//注册端口
LocateRegistry.createRegistry(1099);
java.rmi.Naming.rebind("rmi://127.0.0.1:1099/hello",hello);//将stub代理绑定到Registry服务的URL上,客户端可以通过这个URL直接访问远程对象
System.out.println("Ready");
} catch (RemoteException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}

RMIService ( RMIRegister ) 服务的默认端口为 : 1099

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RmiClient {
public static void main(String[] args) {
try {
Hello hello = (Hello) Naming.lookup("rmi://localhost:1099/hello");
System.out.println(hello.sayHello("xianbei"));
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}

}
}

image-20220305010523-3hnef8v.png

类继承图

image-20220304205543-skkud0p.png

在我中间编写demo的时候出现了一些小问题,Hello hello = (Hello) Naming.lookup("rmi://localhost:1099/hello");

这里是一个关于接口的知识点,太久所以有所遗忘。接口类型用来声明一个接口类型的引用变量

https://www.cnblogs.com/xiaoheliu1024/p/10918017.html

通讯流程分析

tcp.stream eq 41

tcp.stream eq 49

一共建立了两次tcp连接,客户端先连接Registry,寻找Hello对象,Registry返回一个序列化数据,就是Hello对象,客户端反序列化这个数据,发现是远程对象,然后根据封装的ip端口建立新的tcp连接,并且进行远程方法调用

服务端给返回给客户端的Hello类,其中为反序列化数据,以及其他的rmi中的类

image-20220305151629-x10h630.png

image-20220305152802-dzp3lm9.png

也就是rmi register只会返回一个注册了的远程对象,然后服务端执行远程命令调用它而已

参考

https://www.guildhab.top/2020/03/java-rmi-ldap-%e6%b5%81%e7%a8%8b%e5%88%86%e6%9e%90/