深入理解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端获取。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • 代理模式是什么 如上图所示,代理代表着另一终端中的某个真实服务对象,Client 调用代理(Client help...
    野生西瓜阅读 2,284评论 2 14
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,537评论 18 399
  • Java是一个支持并发、基于类和面向对象的计算机编程语言。下面列出了面向对象软件开发的优点: 代码开发模块化,更易...
    安安静静写代码阅读 1,073评论 0 8
  • “酸甜苦辣”化劳动 “酸” 心中苦苦“心塞” “甜” 开朗,乐观,快乐 “苦” 默默的哭 ...
    小小小朱阅读 478评论 0 0