这篇文章试图来回答关于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
这就是所有的类,编译这些类得到class,使用RMI 编译器生成stub的class,使用.keep选项得到源代码。
Source code for CalcImpl_Stub.java:
现在看一个这个生成的stub源代码,注意到他的构造参数需要个RemoteRef类型的对象,这个对象从哪里获取呢?继续下面看。
下面先运行程序:
开2个console
然后就可以看到结果: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 对象
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端获取。
转载自:http://www.jianshu.com/p/2c78554a3f36