RMI
是Java编程语言里一种用于实现远程过程调用的应用程序编程接口。它使客户机上的运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能地简化远程接口对象的使用,简而言之就是为了调用远程方法。https://blog.youkuaiyun.com/u012291108/article/details/52863915
这个过程中涉及到对象的传输,所以会用到序列化和反序列化,以及JNDI(远程方法调用协议)
参考文章:
rmi详解:https://blog.youkuaiyun.com/weixin_44627989/article/details/93055791
stub(存根)和skeleton(框架):https://www.cnblogs.com/yin-jingyu/archive/2012/06/14/2549361.html
现在我们的需求是这样的:client想执行一个在远程机器上server的一个方法,如果我们手动去编写这些过程,socket网络编程或许是我们必须要面对的,由此便引入了stub和skeleton模型。
所有和网络相关的代码全部都由这两个部分来实现,这样一来,客户端和服务端就都不需要去处理网络相关的代码,,这样的逻辑结构

简单来说就是这样的一个逻辑
Client<->Stub<->socket<->skeleton<->server
在远程服务开启的时候,stub也没有办法知道server的域名和端口,这个时候就要用到RMIRegistry,server上会创建一个stub对象,然后把他注册到RMIRegistry,这样Client就能从RMIRegistry获取到对象,RMIRegistry会在1099端口导出自己,不设置的话默认是1099端口
调试代码
server端代码
server端接口
package test;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface HelloService extends Remote {
public String sayHello() throws RemoteException;
//要发布的服务类的方法必须都throws RemoteException
//在Util中,创建代理对象时会checkMethod,存在没有throws RemoteException的则抛出IllegalArgumentException
}
server端实现代码
package test;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
//需要继承UnicastRemoteObject,因为Remote只是接口,UnicastRemoteObject是Remote的实现类
//还要实现HelloService这个接口
protected HelloServiceImpl() throws RemoteException {
super();
}
@Override
public String sayHello() throws RemoteException{
return "hello";
}
}
server端将端口开放出去
package test;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
//服务器端
public class Server {
final static String host = "127.0.0.1";
final static int port = 8080;
public static void main(String[] args) throws RemoteException, MalformedURLException {
HelloService helloService = new HelloServiceImpl();
Registry reg=LocateRegistry.createRegistry(port);
reg.rebind("rmi://"+host+":"+port+"/hello", helloService);//不写port默认是1099
System.out.println("服务启动...");
}
}
客户端代码
package test;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class Client {
public static void main(String[] args) throws MalformedURLException, RemoteException, NotBoundException {
HelloService helloService = (HelloService) Naming.lookup("rmi://127.0.0.1:8080/hello");
//默认端口1099
System.out.println(helloService.sayHello());
}
}
跟踪服务端启动执行流程
在HelloServoceImpl里面实现父类无参构造
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRo5wyTc-1626770660728)(D:\typora\picture\image-20210630141440888.png)]](https://i-blog.csdnimg.cn/blog_migrate/79cbf10902a38dcca4c9fb54593c80dd.png)
进入UnicastRemoteObject方法
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ty91YKS3-1626770660729)(D:\typora\picture\image-20210630141428214.png)]](https://i-blog.csdnimg.cn/blog_migrate/19bb847b7e7002ee4830d53bd4eea1fe.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BRnsPcPH-1626770660730)(D:\typora\picture\image-20210630141549404.png)]](https://i-blog.csdnimg.cn/blog_migrate/b0eb7f966e05f143c371cdcab6d468c7.png)
步入
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pPClwYaF-1626770660732)(D:\typora\picture\image-20210630145656142.png)]](https://i-blog.csdnimg.cn/blog_migrate/a553057a119954571852f23fc5f92585.png)
步入UnicastServerRef()函数,其中会new一个UnicastServerRef对象,进入UnicastServerRef类,会发现其父类中包含一个LiveRef类型的属性
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sLbYqtjF-1626770660733)(D:\typora\picture\image-20210630145845070.png)]](https://i-blog.csdnimg.cn/blog_migrate/540239ae344e0810d77e72731a84c2bd.png)
步入这个liveRef函数
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vYMGW447-1626770660733)(D:\typora\picture\image-20210630150044758.png)]](https://i-blog.csdnimg.cn/blog_migrate/cff8a16b831dc744275f8b89fca076c0.png)
TCPEndpoint对象就获得了Ip以及端口的属性
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J5z7dtKk-1626770660733)(D:\typora\picture\image-20210630150346546.png)]](https://i-blog.csdnimg.cn/blog_migrate/3732f937610c750977cecd22f55d6eaf.png)
紧接着步入
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-REfjAOrD-1626770660734)(D:\typora\picture\image-20210630150558956.png)]](https://i-blog.csdnimg.cn/blog_migrate/fcc23d522991b51ecf8525e25e0b0df7.png)
这时会判断obj是否为UnicastRemoteObject类型,由于obj为自定义的HelloServiceImpl,继承了UnicastRemoteObject,因此if条件成立,接着步入sref对象的exportObject函数

可以看到会先获得对象的类属性,然后创建一个代理对象,继续查看源码,可以知道该代理对象类型为HelloServiceImpl,handler为RemoteObjectInvocationHandler,其中handler会包括上面创建的LiveRef对象(前面也知道了该对象中包含Endpoint等通信所需信息),因此可以判断在远程调用该对象时,客户端获取到的其实是该代理对象,再往下看,会生成一个Target对象,可以看到该Target对象包含了许多数据(导出输入的原始对象,创建的代理对象等)
后面这个target对象又被this.ref的exportObject方法导出
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DwcFcvNE-1626770660735)(D:\typora\picture\image-20210630150848233.png)]](https://i-blog.csdnimg.cn/blog_migrate/815e6f7c6fd8986a746787b57ad1a60f.png)
步入ref.exportObject方法,此时就是在TCPEndpoint这个类里面,在transport这个里面是有这些属性的,
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hy3RkaHH-1626770660736)(D:\typora\picture\image-20210630151131232.png)]](https://i-blog.csdnimg.cn/blog_migrate/dd8ac18208ed88d6d4bde50062299169.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5slPTHVO-1626770660736)(D:\typora\picture\image-20210630151451197.png)]](https://i-blog.csdnimg.cn/blog_migrate/03e39203cf2d8345f7edd1d0390d4a34.png)
跟进这个exportObject()函数,可以看到这个Listen函数,就是建立监听
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2OvEDqYG-1626770660737)(D:\typora\picture\image-20210630164524158.png)]](https://i-blog.csdnimg.cn/blog_migrate/6972dd590e63fe270527cbfaac85b98f.png)
接着跟进,在listen函数里面首先获得TCPEndpoint对象
然后获取到端口
后面就会开启一个socket,而且这个端口是随机的
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DE41uc6V-1626770660737)(D:\typora\picture\image-20210630165314334.png)]](https://i-blog.csdnimg.cn/blog_migrate/7cf7f77237ad1486d15f31f366e6e7e8.png)
继承这个父类构造函数的作用
UnicastRemoteObject基类的构造方法将远程对象发布到一个随机端口上,当然端口也可以指定
每一个对象都会有一个单独的socket,端口值随机
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1AFucT7t-1626770660737)(D:\typora\picture\image-20210630173018594.png)]](https://i-blog.csdnimg.cn/blog_migrate/d40a5533fc00458c77037e24de19ad68.png)
可以看到在执行完构造HelloServiceImpl的构造的时候,这个对象属性里面就有了ip和端口的信息
执行下一行代码
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NLs27r0t-1626770660738)(D:\typora\picture\image-20210630172252918.png)]](https://i-blog.csdnimg.cn/blog_migrate/90e9467f3d190a19257b940b76336341.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jb0qcXK4-1626770660738)(D:\typora\picture\image-20210630165729405.png)]](https://i-blog.csdnimg.cn/blog_migrate/531957403ca7fec7e6a174adf9f2f921.png)
步入
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-txfIVUtI-1626770660739)(D:\typora\picture\image-20210630170043972.png)]](https://i-blog.csdnimg.cn/blog_migrate/5b39f437caf0d99a4ab5cf43d201bd14.png)
步入setup函数,这里会执行exportObject()方法
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YBLvmShq-1626770660740)(D:\typora\picture\image-20210630170242690.png)]](https://i-blog.csdnimg.cn/blog_migrate/37d0229f2ba95b76c9433a83fd81f846.png)
跟进这个方法,这个方法就会返回stub存根,生成skeleton对象
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YHxjAx8G-1626770660740)(D:\typora\picture\image-20210630170350594.png)]](https://i-blog.csdnimg.cn/blog_migrate/60ae868bb7f7f3094807987e0b533e23.png)
总结
Registry reg=LocateRegistry.createRegistry(port);
reg.rebind("rmi://"+host+":"+port+"/hello", helloService);
上面这两行代码就是建立Registry对象,然后将对象和请求放进这个hashTable中去,当我们请求
rmi://127.0.0.1:8080/hello
就会对应的访问到这个对象
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xumCZHuX-1626770660740)(D:\typora\picture\image-20210630173217457.png)]](https://i-blog.csdnimg.cn/blog_migrate/c8803dbbd383345c0518d898dc46d55c.png)
总结:
1、首先接口要继承Remote这个类,然后实现类要继承UnicastRemoteObject这个类,通过这个类的构造方法将远程对象发布到一个随机端口上
2、然后新建Registry对象,这个对象的作用有两个
①在指定端口设置外部访问
②相当于建立了一个注册表,通过键和值来进行访问指定对象的方法
这就是整体建立客户端的一个逻辑
客户端执行流程跟踪
客户端的这行代码返回的是RegistryImpl_Stub,也就是一个存根
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DnINQFmX-1626770660741)(D:\typora\picture\image-20210701110611242.png)]](https://i-blog.csdnimg.cn/blog_migrate/2bdd1e6c9845a7fd5ac6a12dd2f27ab7.png)
传入域名和端口获取到服务端的RegistryImpl的代理
下周仔细研究一下Java RMI Registry各个版本安全问题

本文详细介绍了Java RMI(远程方法调用)的工作原理和使用步骤,包括如何创建远程接口、实现远程服务、启动服务器、设置Registry以及客户端如何查找并调用远程方法。通过示例代码展示了从服务端的接口定义、实现到客户端的调用过程,揭示了RMI在隐藏网络细节、序列化和反序列化等方面的角色。同时,分析了 stub 和 skeleton 的作用,以及RMIRegistry在服务注册和查找中的功能。最后,简要概述了客户端执行流程,强调了每个步骤中的关键点,如端口分配、对象导出和代理对象的生成。
343

被折叠的 条评论
为什么被折叠?



