深入理解rmi原理

来翻译一篇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端获取。

阿里云服务器限时打折!点击获取

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 131,177评论 18 138
  • 代理模式是什么 如上图所示,代理代表着另一终端中的某个真实服务对象,Client 调用代理(Client help...
    野生西瓜阅读 2,011评论 2 14
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 30,444评论 18 400
  • Java是一个支持并发、基于类和面向对象的计算机编程语言。下面列出了面向对象软件开发的优点: 代码开发模块化,更易...
    安安静静写代码阅读 942评论 0 8
  • “酸甜苦辣”化劳动 “酸” 心中苦苦“心塞” “甜” 开朗,乐观,快乐 “苦” 默默的哭 ...
    小小小朱阅读 394评论 0 0
  • 看了一些类似文章发现很多同学连子串,子序列的概念都是混淆的。子序列:sequence,是不必连续的一组字符子串:s...
    young_dreamer阅读 1,761评论 0 0
  • 在三个月的今天,我第一次见到他 我生活在青海 4月底的青海没有内地那么热 空气中泛着几丝凉意· 或许 故事就是从4...
    DFAGAGGVAYV阅读 201评论 0 0