RMI学习

​ 之前学习的记录,在博客上留一份~

名词介绍

1、UnicastRef:客户端对于网络请求的封装对象,实际上客户端发起的请求先是通过UnicastRef操作,而UnicastRef中操作是通过StreamRemoteCall来进行请求。
2、UnicastServerRef:服务端对于网络请求的封装对象,实际上服务端发起的请求先是通过
UnicastServerRef操作,而UnicastServerRef中操作是通过StreamRemoteCall来进行请求。
3、Registry:注册中心,实际上创建注册中心是一个RegistryImpl对象,是直接与客户端通信的部分。,其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法的引用,如果没有这个的话,那么客户端无法找到对应的stub对象,这个注册中心只是一个媒介,方便了客户端进行寻找
4、RegistryImpl_Stub 注册中心存根,客户端在第一个步骤找注册中心时(也就是LocateRegistry.getRegistry)其实是先创建一个存根(还是在客户端上,此时并没有发起网络连接),这个存根保存着注册中心的ip、端口信息。
5、RegistryImpl_Skel 注册中心骨架,RMI服务端在创建注册中心时,会设置一个骨架,客户端存根向注册中心发起网络连接(比如binds、rebinds动作),注册中心会通过RegistryImpl_Skel来处理这次连接,RegistryImpl_Skel对不同的动作有不同的处理逻辑。
image-20221222152248079
6、marshalValue与unmarshalValue,封包与解包,rmi整个过程是基于序列化与反序列化的(比如客户端传入的参数、服务端调用的端口),这两个过程便通过封包与解包

rmi调用总览

1、服务端创建部分

1、创建远程调用对象
这一个首先会以RemoteObjectInvocationHandler动态代理你想要远程调用的对象,然后将这个动态代理绑定到一个随机端口(绑定到端口的是一个Target对象,Target保留了动态代理、远程对象等等信息)。
2、创建注册中心
这一步会创建一个注册中心,将注册中心、注册中心骨架、注册中心存根放到Target对象中绑定到指定端口。
3、注册服务
这一步直接将第1步创建的远程调用对象(一个动态代理)直接put到注册中心的绑定列表bindings 当中。

2、客户端调用部分及与服务端的通信过程

这一步画个图
image-20221222162555888
1、
第一步,客户端获取注册中心,可能的代码如下

LocateRegistry.getRegistry("127.0.0.1", 9999)

这一步获取的是注册中心存根,此时未与客户端发起通信。
2、
第二步,客户端通过注册中心存根寻找指定的服务,
3、
第三步,注册中心会使用注册中心的骨架来处理,注册中心骨架会根据调用的服务名称来找到绑定在注册中心的服务
4、
第四步,注册中心骨架将远程对象返回给客户端
5、
第5步,客户端获取到返回的远程对象(动态代理)后,调用指定的方法。此时会与服务端远程对象通信(服务端创建的第二步)
6、
第6步,远程对象调用真正想要实现的服务。
7、
第7步,服务对象将执行的结果返回给UnicastRemoteObject
8、
最后UnicastRemoteObject将结果返回给客户端,整个过程结束。

rmi实现过程源代码分析

1、 服务端远程对象创建

服务端的第一个步骤是创建远程对象

// 实例化服务端远程对象
ServicesImpl obj = new ServicesImpl();
// 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理
Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);

进入java.rmi.server.UnicastRemoteObject#exportObject(java.rmi.Remote, int)
image-20221221175736374
exportObject 的作用是导出一个远程对象,使其可以使用所提供的特定端口接收传入调用。

new UnicastServerRef(port)

首先创建一个UnicastServerRef 服务端的远程请求对象
接着调用UnicastServerRef 的exportObject 方法
image-20221221180125346
这个类的作用是
导出该对象,为该分派器创建骨架和存根。根据impl的类型创建存根,使用适当的远程引用初始化它。创建由impl、dispatcher (this)和存根定义的目标。通过Ref导出目标。
在这一步骤,主要的功能是创建一个proxy
跟进
image-20221221180308131
由于我们需要代理的对象是一个自己实现的方法,所以不存在其存根对象,所以进入一个创建动态代理的过程。(UnicastServerRef 的exportObject 方法通过判断对象是否又stub来判断是注册中心的创建还是远程对象的创建)
image-20221221180358155
这里发现,通过RemoteObjectInvocationHandler动态代理创建一个远程对象
接着创建了一个 target对象(这个tatget对象将会与一个随机端口绑定)
调用了一个liveRef的exportObject方法
image-20221221181313829
这里的exportObject 创建一个随机端口,这个端口也就是调用远程对象的端口,
根据到
sun.rmi.transport.tcp.TCPTransport#exportObject
image-20221221181834154
这里先创建了一个随机端口
接着调用exportObject方法
将target put到ObjectTable 中
image-20221221181919126
在这个过程中,我们可以看到有DGCImpl 的创建
image-20221221182215241
image-20221221182400898
随着远程对象的创建,也便随着一个DGC 服务端的创建,同样也put到 ObjectTable 中
image-20221221182713768
这一步之后 objTable 中变存在两个值,一个是DGC对象,一个是我们之前创建的target对象(远程对象)。
最后将创建的动态代理返回
image-20221221183009897

注册中心创建

注册中心的创建其实和服务端远程对象创建类似
image-20221221183520037
这个创建了一个LiveRef 对象,
接着创建了一个UnicastServerRef对象,这个对象的ref指向刚创建的LiveRef ,并且设置了RegistryImpl::registryFilter(JEP290防御机制)
接着进入sun.rmi.registry.RegistryImpl#setup
调用创建的UnicastServerRef的exportObject方法
image-20221221184056667
同样是创建一个代理动作
不同的是这是的remoteClass 为 sun.rmi.registry.RegistryImpl
其存在sun.rmi.registry.RegistryImpl_Stub 方法
image-20221221184416490
进入了sun.rmi.server.Util#createStub
image-20221221184618801
返回了一个sun.rmi.registry.RegistryImpl_Stub 对象,这里就不是动态代理对象了
由于是RegistryImpl_Stub 对象继承于RemoteStub,所以会设置骨架
image-20221221184827273
image-20221222173652216
是一个RegistryImpl _Skel 对象
接着便同样是创建一个target对象,并且此对象与9999 端口绑定
image-20221221185147585
这时候objTable 对象便有了3个值,其中两个是创建远程对象时添加的,新添加的便是注册中心,其端口为设置的9999
image-20221221185353849

服务注册

下一个步骤是将 我们创建的远程对象,注册到注册中心
调用的是bind方法,这一步比较简单,将对象put到 RegistryImpl 中的bindings一个map对象中。
image-20221221193727376

远程调用

还记得前面步骤的objTable吗,这个过程与objTable中对象的调用相关联,主要是三个对象
1、注册中心registry,绑定在9999端口
2、远程对象UnicastServerRef,其端口随机
3、DGC对象,端口随机
首先是远程调用寻找registry

LocateRegistry.getRegistry("127.0.0.1", 9999)

image-20221222114126818
image-20221222114144076
同样是创建了一个代理
image-20221222114217313
这里与创建注册中心的过程很像。
image-20221222114325476
所以在LocateRegistry.getRegistry("127.0.0.1", 9999)的过程中,实际上实在客户端本地创建了一个RegistryImpl_Stub对象,并不涉及与服务端的通信。
第二个步骤,找到Services 这个对象

Services services = (Services) registry.lookup("Services");

看下lookup方法
image-20221222114647005
首先new了一个StreamRemoteCall 对象,这个对象是用来与服务端通信的,存放着服务端注册中心的连接信息。
首先将要发送给服务端的信息 $param_String_1 写入 OutputStream,接着调用
invoke 方法来进行通信
image-20221222115029316
调用getInputStream方法时触发通信image-20221222115116269
这时我们通信的对象是注册中心,是一个 UnicastServerRef对象,用其 dispatch 方法来处理客户端的请求(下面步骤是服务端的流程)
sun.rmi.server.UnicastServerRef#dispatch
image-20221222173749189
在上面我们分析时,注册中心是设置了骨架的,所以进入oldDispatch 方法,
image-20221222115520857
unmarshalCustomCallData 方法是设置了InputFilter,是防御反序列化的
image-20221222115622552
接着进入注册中心 骨架RegistryImpl_Skel 的dispatch 方法,
我们调用的方法是lookup,看一下其实现
image-20221222173837512
首先获取了服务名称,然后调用server的lookup方法(这里的server是注册中心),
image-20221222173907586
这一步就比较简单了,也就是找到在服务注册时,向注册中心bindings put的对象(也就是远程对象),
image-20221222140607694
接着将此对象序列化,发送给客户端
客户端收到数据后,反序列化出对象
image-20221222173142854
可以看到返回的result 是一个动态代理,里面的端口对应的是 注册远程对象时的随机端口,
下一步,将之前声明的call给free掉
image-20221222142447521
这一步骤会触发服务端的DGC垃圾回收
image-20221222142625255
这里便不在分析了,之后将找到的远程对象返回
图片.png
接着便调用该服务的相应方法了,还记得远程对象是以RemoteObjectInvocationHandler创建的动态代理吗,所以首先进入RemoteObjectInvocationHandler的invoke 方法
image-20221222174002998
一直进入sun.rmi.server.UnicastRef#invoke(java.rmi.Remote, java.lang.reflect.Method, java.lang.Object[], long)
image-20221222174049933
发现这里开始发起连接了,这里的连接的端口是服务端创建的远程对象绑定的端口
image-20221222150024244
并且将参数序列化写入OutputStream后,开始调用远程对象
然后进入服务端的dispath(下面为服务端)
image-20221222143032473
这里的远程对象是我们第一步注册的,根据上面的分析,是没有骨架的,所以就不进入这个判断了,
image-20221222143710396
接着找到调用的方法后,便开始先解包,这里是解参数的包
image-20221222144306748
接着进入unmarshalValue,这里也是反序列化的点
image-20221222144326077
接着便调用方法
image-20221222144425634
image-20221222150147733
调用对应方法,
image-20221222150336245
之后便封装好返回结果,再发给客户端
客户端获取返回信息并解包
图片.png
到此一整个调用流程便完成了。
流程如下
image-20221222162555888

参考

https://www.cnblogs.com/zpchcbd/p/13517074.html
https://www.cnblogs.com/zpchcbd/p/13517074.html
https://paper.seebug.org/1091/

Process finished with exit code 0