對
RMI
的簡單理解
RMI (遠程方法)是 Java 平臺中建立分布式計算的基礎, 2 年前我剛開始接觸 J2EE 時,怎么看書都是不得要領,最近這幾天閑著沒事又翻了翻以前沒有看懂的書,突然之間頓悟了。
一、 簡單的 RMI 示例:
要快速入門,最簡單的方法就是看簡單的例子。下面是我寫的一個簡單的示例:
首先,定義一個接口 IServer ,代碼如下:
IServer.java

2

3

4

5



6

7

8

9

需要注意的是,這個接口從java.rmi.Remote接口擴展,并且這個接口中定義的方法都需要拋出java.rmi.RemoteException異常。
接著,我們要根據這個接口來實現自己的服務器對象,所謂服務器對象,就是我們大腦中想的遠程對象,這個對象中定義的方法都是被別人來調用的。代碼如下:
ServerImp.java






















































?
這個類很容易理解, doSomeThing() 方法只簡單的輸出被調用的信息。唯一的難點就在 main() 函數中,我們通過 java.rmi.Naming.rebind() 把我們的遠程對象注冊到 rmi 注冊表中,這樣,別人就可以通過 java.rmi.Naming.lookup() 來查找我們的遠程對象。那么, rmi 注冊表在哪里呢? J2SDK 的 bin 目錄下有一個程序 rmiregistry ,運行它就可以得到一個注冊表進程,我們可以通過它來綁定或者查找遠程對象, java.rmi.Naming.rebind 函數的第一個參數就是要指定注冊表進程的位置,因為我這里運行在自己的機器上,所以是 //localhost/ ,如果是在別的機器上,可以用 IP 地址代替。
最后,我們寫一個客戶機,來調用這個遠程對象的方法。代碼如下:
Client.java

?2

?3

?4

?5



?6

?7



?8

?9

10



11

12

13



14

15

16

17

18

19



20

21

22



23

24

25

26

27

28

29

30

?
可以看到,我們的客戶端程序只用到了 IServer 接口,而不需要 ServerImp 類,它只通過 java.rmi.Naming.lookup() 來查找遠程對象的引用。
下面,我們就可以開始測試我們的程序了。先編譯以上程序,然后:
第一步,要先啟動 Rmi 注冊表,如下:
第二步,使用 rmic 對 ServerImp.class 進行編譯,生成代理類 ServerImp_Stub.class ,如下:
?
第三步,啟動服務器端程序,如下:
?
第四步,啟動客戶端程序,我們多調用幾次,如下:
這個時候,我們再看看服務器端是什么反應:
可以看到,服務器端的方法被調用,在服務器端的控制臺上打印出了這樣幾行消息。
下面,我們使用一個簡單的圖表來表示客戶機、服務器和 RMI 注冊表之間的關系,綠色的數字代表順序:
二、參數傳遞
前面的例子沒有涉及到參數的傳遞。如果我們需要向遠程方法傳遞參數,或者要從遠程方法接受返回值,是不是有什么特殊的約定呢?不錯,如果我們要在客戶機和服務器之間傳遞參數,則該對象要么是實現Serializable接口的對象,要么是擴展自UnicastRemoteObject的對象,這兩種對象是有差別的。
如果參數是實現Serializable接口的對象,則該對象是按值傳遞的,也就是把這整個對象傳遞到遠程方法中。請看下面的例子,我們定義了一個ISerializableWorker接口,擴展自Serializable接口,客戶端創建一個SerializableWorkerImp對象wk,并把它傳遞到服務器端,服務器端調用wk.work()方法,這個方法在服務器端執行,這就說明了我們成功把這個對象傳遞到了服務器端。服務器端返回的String對象,也可以成功傳遞到客戶端。
ISerializableWorker.java

2

3

4

5



6

7

SerializableWorkerImp.java

2

3



4

5



6

7

8

9

IServer.java

2

3

4

5

6



7

8

9

ServerImp.java

?2

?3

?4

?5

?6



?7

?8



?9

10

11

12

13



14

15

16

17

18

19



20

21

22

23

24


25

26

27



28

29

30



31

32



33

34

35

36

37

38



39

40

41



42

43

44

45

46

47

48

Client.java

?2

?3

?4

?5



?6

?7


?8

?9

10



11

12

13



14

15

16



17

18

19

20

21

22



23

24

25

26

27

28



29

30

31

32

33

34

35

36

37

程序的運行方法同前,我就不再羅嗦了。這里需要注意的是,該示例在單機上運行可以,但是真的在分布環境下運行就會出錯,畢竟,別人要把一個對象傳遞到你的機器上,怎么著你也要放著別人的對象搞破壞吧。最后我們會討論安全問題。
另外一種參數的傳遞方式,就是按照引用傳遞,如果作為參數的對象是擴展自java.rmi.server.UnicastRemoteObject類的話,那么該對象傳遞給遠程方法的只是它的引用。比如,客戶端創建了一個擴展自java.rmi.server.UnicastRemoteObject的對象A,把對象A傳遞到服務器端,這個時候服務器端得到的只是對象A的引用,如果服務器調用對象A的方法,這個方法就會在客戶端執行。
下面的例子說明了這一點,我們定義IRefWorker接口和RefWorkerImp類,在客戶端創建RefWorkerImp類的對象,把該對象傳遞到服務器端,服務器端調用該對象的方法,你會發現該方法在客戶端執行。
IRefWorker.java

2

3

4

5

6



7

8

RefWorkerImp.java

?2

?3

?4

?5

?6

?7

?8



?9

10



11

12

13

14



15

16

17

18

IServer.java

?2

?3

?4

?5

?6



?7

?8

?9

10

ServerImp.java
該類中實現接口中定義的方法,和前面的代碼相比,多了如下一行



2

3

Client.java

?2

?3

?4

?5



?6

?7


?8

?9

10



11

12

13



14

15

16



17

18

19

20

21

22



23

24

25

26

27

28

29

30



31

32

33

34

35

36

37

38

程序的運行方法同前,不再重復。
三、安全管理與授權策略
前面提到過,前面的示例代碼,如果真正運行到分布式環境下的話,是會出錯的,原因就在于安全性問題。J2EE中的安全管理廣泛,我們這里僅僅只用到授權,比如我們可以只授權遠程程序訪問某一個文件夾或某一個文件,或者只授權遠程程序訪問網絡等等。
要使用授權,需要一個授權文件,我們新建一個Policy.txt文件,為了簡單起見,我們授權遠程程序可以訪問所有的本地資源:



2

3

然后,我們需要在服務器端程序中載入安全管理器,我們這里使用默認的RMISecurityManager,下面是經過修改了的ServerImp.java中的mian()函數:



?2

?3

?4



?5

?6



?7

?8

?9

10

11

12



13

14



15

16

17

18

19

20



21

22

23



24

25

26

27

28

然后,我們需要這樣運行服務器端:
java -Djava.security.policy=Policy.txt rmistudy.ServerImp
給幾個貼圖:
1.運行服務器:
2.運行客戶端:
3.運行客戶端后服務器的反應:
總結
J2EE規范雖然龐大而復雜,但是如果我們分開來學習,也是可以逐步理解的。J2EE包含企業數據、企業通訊、企業服務、企業Web支持和企業應用程序等方面。而我們的RMI就是企業通訊中的一種,另外一種就是日益流行起來的Web Service通訊,至于其它通訊架構,我們大可以到需要的時候再去學習,比如CORBA。
EJB是架構在RMI基礎上的,但是它太復雜,很多時候使用簡單的RMI就可以解決很多問題,比如科學領域的分布式計算。大家一定聽說過找外星人的SETI項目,它就是利用全球志愿者的個人PC來進行分布式的運算。在我們中國,大型機還比較缺乏,如果我們的某個實驗室需要強大的計算能力,大可以向SETI項目學習。而使用RMI,建立分布式計算平臺是多么的簡單。