來(lái)翻譯一篇rmi原理性文章:
成文動(dòng)機(jī)
寫這篇文章是出于我個(gè)人的經(jīng)驗(yàn),我是最近才知道java rmi這個(gè)東西的。但是立即我對(duì)這部分內(nèi)容產(chǎn)生了濃厚的興趣,尤其是 stubs 和skeletons。讓我特別的感興趣的是RMI的設(shè)計(jì)者設(shè)計(jì)的這個(gè)框架使得rmi客戶端感覺(jué)就好像在調(diào)用本地方法一樣,但是實(shí)際上是在遠(yuǎn)程對(duì)象上執(zhí)行的。隨著我學(xué)習(xí)的深入,我開始了解了RMIRegistry和更多的東西。對(duì)很多的問(wèn)題我都很困惑,比如RMIRegistry 真正的角色是什么?他是必須存在的么?stub對(duì)象是在哪里創(chuàng)建的?(服務(wù)端?客戶端?注冊(cè)中心?),客戶端怎么知道服務(wù)端監(jiān)聽的端口是哪一個(gè)?難道所有的服務(wù)端都是使用1099來(lái)監(jiān)聽客戶端的嗎?
這篇文章試圖來(lái)回答我之前的這些疑惑。我參考了網(wǎng)上很多關(guān)于rmi書籍和文章,但是都不能找到這些疑惑的線索,沒(méi)有人告訴我這些事情都是怎么運(yùn)轉(zhuǎn)的,每個(gè)人都在告訴我怎么寫代碼,如此而已,沒(méi)有任何關(guān)于底層的工作機(jī)制。經(jīng)過(guò)很長(zhǎng)時(shí)間的研究和實(shí)驗(yàn),我得到了我想要的答案。所以我想分享我的知識(shí),因?yàn)檫€有很多人可能遇到和我同樣的疑惑。
這篇文章試圖來(lái)回答關(guān)于RMI原理的幾個(gè)問(wèn)題:
1、誰(shuí)創(chuàng)建了stubs對(duì)象,服務(wù)器?客戶端?注冊(cè)中心?
2、怎么知道服務(wù)器監(jiān)聽的端口是哪一個(gè)?
3、注冊(cè)中心對(duì)于RMI系統(tǒng)來(lái)說(shuō)是必須的嗎?
4、如果沒(méi)有注冊(cè)中心,RMI可以運(yùn)行嗎?
在下面的部分我會(huì)詳細(xì)解答這些問(wèn)題,如果你想快速得到這些問(wèn)題的答案,可以直接去看對(duì)應(yīng)的部分,這里我會(huì)一步一步的引導(dǎo)你理解rmi中到底發(fā)生了什么
首先,請(qǐng)忘記注冊(cè)中心 ?。?/h2>
是的,從現(xiàn)在開始忘掉注冊(cè)中心,假設(shè)這玩意從現(xiàn)在開始不存在
最開始的場(chǎng)景大概是這樣的:我們有一個(gè)server和一個(gè)client。server 繼承了 java.rmi.server.UnicastRemoteObject。client和server運(yùn)行在不同的機(jī)器上面
現(xiàn)在我們的需求是這樣的:client想執(zhí)行一個(gè)在遠(yuǎn)程機(jī)器上server的一個(gè)方法。
我們?nèi)绻龅竭@一點(diǎn)?java rmi 會(huì)處理這些問(wèn)題,解決方案肯定會(huì)涉及到socket網(wǎng)絡(luò)編程,因?yàn)閟erver運(yùn)行在遠(yuǎn)程機(jī)器上,解決這個(gè)問(wèn)題的關(guān)鍵點(diǎn)在于
1.客戶端如何從處理網(wǎng)絡(luò)連接中解耦開來(lái)
2.客戶端如何能就像調(diào)用本地方法一樣來(lái)調(diào)用遠(yuǎn)程機(jī)器上的方法,因此rmi的開發(fā)人員就引入了stub和skeleton模型。
所有與網(wǎng)絡(luò)相關(guān)的代碼都放在了stub和skeleton中,這樣客戶端和服務(wù)端就不需要處理網(wǎng)絡(luò)相關(guān)的代碼了。stub同樣也實(shí)現(xiàn)了和服務(wù)端同樣 java.rmi.Remote接口,這樣當(dāng)client想調(diào)用server上方法的時(shí)候,就可以調(diào)用stub上的相同的方法,但是stub里面只有和網(wǎng)絡(luò)相關(guān)的處理邏輯,并沒(méi)有對(duì)應(yīng)的業(yè)務(wù)處理邏輯,比如說(shuō)server上有一個(gè)add方法,stub中同樣也有一個(gè)add方法,但是stub上的這個(gè)add方法并不包含添加的邏輯實(shí)現(xiàn),他僅僅包含如何連接到遠(yuǎn)程的skeleton、調(diào)用方法的詳細(xì)信息、參數(shù)、返回值等等。
所以,目前看來(lái)大概是這個(gè)樣子的:
Client<-->stub<-->[NETWORK]<-->skeleton<-->Server
客戶端和stub對(duì)話,stub和skeleton對(duì)話,skeleton和server對(duì)話,server執(zhí)行真正的方法,然后把結(jié)果原路返回,這樣看來(lái)就有4個(gè)獨(dú)立的部分,也就是說(shuō)你大概需要4個(gè)class了。
在jdk1.2之后,skeleton就被合并到server中了,所以看起來(lái)是這個(gè)樣子滴:
Client<--->stub<--->[NETWORK]<--->Server_with_skeleton
socket 層的詳細(xì)內(nèi)容
現(xiàn)在,你可以學(xué)習(xí)在socket層通信是如何完成了的,這部分非常重要!,這部分內(nèi)容開始變的有點(diǎn)繞,所以,請(qǐng)?zhí)貏e注意這部分內(nèi)容。
1.server在遠(yuǎn)程機(jī)器上監(jiān)聽一個(gè)端口,這個(gè)端口是jvm或者os在運(yùn)行時(shí)隨機(jī)選擇的一個(gè)端口。可以說(shuō)server在遠(yuǎn)程機(jī)器上在這個(gè)端口上導(dǎo)出自己。
2.client并不知道server在哪,以及sever監(jiān)聽哪個(gè)端口,但是他有stub,stub知道所有這些東西,這樣client可以調(diào)用stub上他想調(diào)用的任何方法。
3.client調(diào)用給你stub上的方法
4.stub鏈接server監(jiān)聽的端口并發(fā)送參數(shù),詳細(xì)過(guò)程如下:
a.client連接server監(jiān)聽的端口
b.server收到請(qǐng)求并創(chuàng)建一個(gè)socket來(lái)處理這個(gè)鏈接
c.server繼續(xù)監(jiān)聽到來(lái)的請(qǐng)求
d.使用雙方協(xié)定的歇息,傳送參數(shù)和結(jié)果
e.協(xié)議可以是JRMP或者 iiop
5.方法在遠(yuǎn)程server上執(zhí)行,并發(fā)執(zhí)行結(jié)果返回給stub
6.stub返回結(jié)果給client,就好像是stub執(zhí)行了這個(gè)方法一樣。
所以整個(gè)過(guò)程就結(jié)束了,但是等一下!回頭再看第2點(diǎn),說(shuō)stub知道server在哪,他監(jiān)聽的端口,這怎么么可能?如果client不知道server的host和port,他怎么能創(chuàng)建一個(gè)知道所有這一切的stub對(duì)象呢?更何況是在服務(wù)端端口是隨機(jī)選擇的
啟動(dòng)的窘境
這是在很多現(xiàn)實(shí)生活中最大的問(wèn)題之一。解決方案在于我們?nèi)绾瓮ㄖ猚lient這些server端的細(xì)節(jié)。RMI的設(shè)計(jì)者們有應(yīng)對(duì)啟動(dòng)問(wèn)題的應(yīng)急措施,那就是RMIRegistry 存在的必要了。RMIRegistry 可以認(rèn)為是一個(gè)服務(wù),它提供了一個(gè)hashmap,里面是 public_name, Stub_object 名值對(duì)。比如我有一個(gè)遠(yuǎn)程服務(wù)對(duì)象叫做 Scientific_Calculator,然后我想把這個(gè)服務(wù)對(duì)外公布為 calc,這樣會(huì)在server上創(chuàng)建一個(gè)stub對(duì)象,燃火把他注冊(cè)到RMIRegistry ,這樣client就可以從RMIRegistry 中得到這個(gè)stub對(duì)象了,你可以使用一個(gè)工具類 java.rmi.Naming 來(lái)方便的操作注冊(cè)和操作。
用一個(gè)栗子來(lái)說(shuō)明整個(gè)過(guò)程
考慮一個(gè)計(jì)算的應(yīng)用,它有一個(gè)add的方法,你想把這個(gè)方法對(duì)外發(fā)布成一個(gè)遠(yuǎn)程對(duì)象,遠(yuǎn)程接口叫做Calc


這就是所有的類,編譯這些類得到class,使用RMI 編譯器生成stub的class,使用.keep選項(xiàng)得到源代碼。

Source code for CalcImpl_Stub.java:

現(xiàn)在看一個(gè)這個(gè)生成的stub源代碼,注意到他的構(gòu)造參數(shù)需要個(gè)RemoteRef類型的對(duì)象,這個(gè)對(duì)象從哪里獲取呢?繼續(xù)下面看。
下面先運(yùn)行程序:
開2個(gè)console

然后就可以看到結(jié)果:7!??!
這里到底是怎么搞得?
我會(huì)給你看這個(gè)背后到底發(fā)生了什么
1.首先RMIRegistry 運(yùn)行在server端,RMIRegistry 自身也是一個(gè)遠(yuǎn)程對(duì)象。有一點(diǎn)需要注意的是:所有的遠(yuǎn)程對(duì)象(繼承了UnicastRemoteObject對(duì)象)都會(huì)在sever上任意的端口導(dǎo)出自己,因?yàn)镽MIRegistry 也是一個(gè)遠(yuǎn)程對(duì)象,他也在server上導(dǎo)出自己,只是這個(gè)端口是廣為人知的1099,“廣為人知”的意思是所有的client都知道這個(gè)端口
2.服務(wù)端運(yùn)行在server上,在UnicastRemoteObject構(gòu)造函數(shù)里面,他把自己導(dǎo)出在server上一個(gè)任意端口上,這個(gè)端口client是不知道的
3.當(dāng)你調(diào)用Naming.rebind()的時(shí)候,會(huì)傳入一個(gè)CalcImpl 的引用(這里叫做 c)作為第2個(gè)參數(shù),Naming 就會(huì)構(gòu)造一個(gè)stub對(duì)象,詳細(xì)如下:
a.Naming會(huì)使用getClass來(lái)獲取類的名字,這里就是CalcImpl
b.加上后綴_Stub 變成了 CalcImpl _Stub
c.加載CalcImpl_Stub.class到虛擬機(jī)中
d.從c中獲取RemoteRef 對(duì)象

e.就是這個(gè)ref對(duì)象中封裝了服務(wù)端細(xì)節(jié),包括服務(wù)端的hostname、port
f.獲取了RemoteRef 對(duì)象之后,就可以構(gòu)造stub對(duì)象了。
g.傳遞stud對(duì)象到RMIRegistry中進(jìn)行綁定,即(publicname,stub)
f.RMIRegistry 中內(nèi)部使用一個(gè)hashmap來(lái)存儲(chǔ)(publicname,stub)
4.當(dāng)客戶端使用 Naming.lookup()的時(shí)候,會(huì)傳入public name 作為參數(shù),RMIRegistry 就會(huì)返回stub給客戶端調(diào)用
RMIRegistry 是必須的嗎?
No,RMIRegistry 起到的作用只是為了方便client獲取到stub對(duì)象,如果還有其他的方法讓client拿到stub就不需要RMIRegistry 了,因?yàn)閏lient一旦拿到了stub就不需要RMIRegistry 了
直接在client new一個(gè)stub對(duì)象不就可以了?
No,stub構(gòu)造函數(shù)中需要一個(gè)RemoteRef 對(duì)象,這個(gè)對(duì)象只能在server端獲取。
阿里云服務(wù)器限時(shí)打折!點(diǎn)擊獲取
