欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深入理解rmi原理

程序员文章站 2022-05-12 18:52:19
...
深入理解rmi原理

http://www.jianshu.com/p/2c78554a3f36


来翻译一篇rmi原理性文章:

成文动机

写这篇文章是出于我个人的经验,我是最近才知道java rmi这个东西的。但是立即我对这部分内容产生了浓厚的兴趣,尤其是 stubs 和skeletons。让我特别的感兴趣的是RMI的设计者设计的这个框架使得rmi客户端感觉就好像在调用本地方法一样,但是实际上是在远程对象上执行的。随着我学习的深入,我开始了解了RMIRegistry和更多的东西。对很多的问题我都很困惑,比如RMIRegistry 真正的角色是什么?他是必须存在的么?stub对象是在哪里创建的?(服务端?客户端?注册中心?),客户端怎么知道服务端监听的端口是哪一个?难道所有的服务端都是使用1099来监听客户端的吗?

这篇文章试图来回答我之前的这些疑惑。我参考了网上很多关于rmi书籍和文章,但是都不能找到这些疑惑的线索,没有人告诉我这些事情都是怎么运转的,每个人都在告诉我怎么写代码,如此而已,没有任何关于底层的工作机制。经过很长时间的研究和实验,我得到了我想要的答案。所以我想分享我的知识,因为还有很多人可能遇到和我同样的疑惑。

这篇文章试图来回答关于RMI原理的几个问题:
1、谁创建了stubs对象,服务器?客户端?注册中心?
2、怎么知道服务器监听的端口是哪一个?
3、注册中心对于RMI系统来说是必须的吗?
4、如果没有注册中心,RMI可以运行吗?

在下面的部分我会详细解答这些问题,如果你想快速得到这些问题的答案,可以直接去看对应的部分,这里我会一步一步的引导你理解rmi中到底发生了什么

首先,请忘记注册中心 !!

是的,从现在开始忘掉注册中心,假设这玩意从现在开始不存在
最开始的场景大概是这样的:我们有一个server和一个client。server 继承了 java.rmi.server.UnicastRemoteObject。client和server运行在不同的机器上面
现在我们的需求是这样的:client想执行一个在远程机器上server的一个方法。

我们如果做到这一点?java rmi 会处理这些问题,解决方案肯定会涉及到socket网络编程,因为server运行在远程机器上,解决这个问题的关键点在于
1.客户端如何从处理网络连接中解耦开来
2.客户端如何能就像调用本地方法一样来调用远程机器上的方法,因此rmi的开发人员就引入了stub和skeleton模型。

所有与网络相关的代码都放在了stub和skeleton中,这样客户端和服务端就不需要处理网络相关的代码了。stub同样也实现了和服务端同样 java.rmi.Remote接口,这样当client想调用server上方法的时候,就可以调用stub上的相同的方法,但是stub里面只有和网络相关的处理逻辑,并没有对应的业务处理逻辑,比如说server上有一个add方法,stub中同样也有一个add方法,但是stub上的这个add方法并不包含添加的逻辑实现,他仅仅包含如何连接到远程的skeleton、调用方法的详细信息、参数、返回值等等。

所以,目前看来大概是这个样子的:
Client<-->stub<-->[NETWORK]<-->skeleton<-->Server

客户端和stub对话,stub和skeleton对话,skeleton和server对话,server执行真正的方法,然后把结果原路返回,这样看来就有4个独立的部分,也就是说你大概需要4个class了。

在jdk1.2之后,skeleton就被合并到server中了,所以看起来是这个样子滴:
Client<--->stub<--->[NETWORK]<--->Server_with_skeleton

socket 层的详细内容

现在,你可以学习在socket层通信是如何完成了的,这部分非常重要!,这部分内容开始变的有点绕,所以,请特别注意这部分内容。

1.server在远程机器上监听一个端口,这个端口是jvm或者os在运行时随机选择的一个端口。可以说server在远程机器上在这个端口上导出自己。
2.client并不知道server在哪,以及sever监听哪个端口,但是他有stub,stub知道所有这些东西,这样client可以调用stub上他想调用的任何方法。
3.client调用给你stub上的方法
4.stub链接server监听的端口并发送参数,详细过程如下:
a.client连接server监听的端口
b.server收到请求并创建一个socket来处理这个链接
c.server继续监听到来的请求
d.使用双方协定的歇息,传送参数和结果
e.协议可以是JRMP或者 iiop
5.方法在远程server上执行,并发执行结果返回给stub
6.stub返回结果给client,就好像是stub执行了这个方法一样。

所以整个过程就结束了,但是等一下!回头再看第2点,说stub知道server在哪,他监听的端口,这怎么么可能?如果client不知道server的host和port,他怎么能创建一个知道所有这一切的stub对象呢?更何况是在服务端端口是随机选择的

启动的窘境

这是在很多现实生活中最大的问题之一。解决方案在于我们如何通知client这些server端的细节。RMI的设计者们有应对启动问题的应急措施,那就是RMIRegistry 存在的必要了。RMIRegistry 可以认为是一个服务,它提供了一个hashmap,里面是 public_name, Stub_object 名值对。比如我有一个远程服务对象叫做 Scientific_Calculator,然后我想把这个服务对外公布为 calc,这样会在server上创建一个stub对象,燃火把他注册到RMIRegistry ,这样client就可以从RMIRegistry 中得到这个stub对象了,你可以使用一个工具类 java.rmi.Naming 来方便的操作注册和操作。

用一个栗子来说明整个过程

考虑一个计算的应用,它有一个add的方法,你想把这个方法对外发布成一个远程对象,远程接口叫做Calc



Paste_Image.png



Paste_Image.png

这就是所有的类,编译这些类得到class,使用RMI 编译器生成stub的class,使用.keep选项得到源代码。



Paste_Image.png

Source code for CalcImpl_Stub.java:



Paste_Image.png

现在看一个这个生成的stub源代码,注意到他的构造参数需要个RemoteRef类型的对象,这个对象从哪里获取呢?继续下面看。
下面先运行程序:
开2个console



Paste_Image.png

然后就可以看到结果:7!!!
这里到底是怎么搞得?

我会给你看这个背后到底发生了什么
1.首先RMIRegistry 运行在server端,RMIRegistry 自身也是一个远程对象。有一点需要注意的是:所有的远程对象(继承了UnicastRemoteObject对象)都会在sever上任意的端口导出自己,因为RMIRegistry 也是一个远程对象,他也在server上导出自己,只是这个端口是广为人知的1099,“广为人知”的意思是所有的client都知道这个端口

2.服务端运行在server上,在UnicastRemoteObject构造函数里面,他把自己导出在server上一个任意端口上,这个端口client是不知道的

3.当你调用Naming.rebind()的时候,会传入一个CalcImpl 的引用(这里叫做 c)作为第2个参数,Naming 就会构造一个stub对象,详细如下:
a.Naming会使用getClass来获取类的名字,这里就是CalcImpl
b.加上后缀_Stub 变成了 CalcImpl _Stub
c.加载CalcImpl_Stub.class到虚拟机中
d.从c中获取RemoteRef 对象



Paste_Image.png


e.就是这个ref对象中封装了服务端细节,包括服务端的hostname、port
f.获取了RemoteRef 对象之后,就可以构造stub对象了。
g.传递stud对象到RMIRegistry中进行绑定,即(publicname,stub)
f.RMIRegistry 中内部使用一个hashmap来存储(publicname,stub)

4.当客户端使用 Naming.lookup()的时候,会传入public name 作为参数,RMIRegistry 就会返回stub给客户端调用

RMIRegistry 是必须的吗?

No,RMIRegistry 起到的作用只是为了方便client获取到stub对象,如果还有其他的方法让client拿到stub就不需要RMIRegistry 了,因为client一旦拿到了stub就不需要RMIRegistry 了

直接在client new一个stub对象不就可以了?

No,stub构造函数中需要一个RemoteRef 对象,这个对象只能在server端获取。
相关标签: RMI