java的RMI(Remote Method Invocation)

RMI 相關知識
RMI全稱是Remote Method Invocation-遠程方法調用,Java RMI在JDK1.1中實現的,其威力就體如今它強大的開發分佈式網絡應用的能力上,是純Java的網絡分佈式應用系統的核心解決方案之一。其實它能夠被看做是RPC的Java版本。可是傳統RPC並不能很好地應用於分佈式對象系統。而Java RMI 則支持存儲於不一樣地址空間的程序級對象之間彼此進行通訊,實現遠程對象之間的無縫遠程調用。
RMI目前使用Java遠程消息交換協議JRMP(Java Remote Messaging Protocol)進行通訊。因爲JRMP是專爲Java對象制定的,Java RMI具備Java的"Write Once,Run Anywhere"的優勢,是分佈式應用系統的百分之百純Java解決方案。用Java RMI開發的應用系統能夠部署在任何支持JRE(Java Run Environment Java,運行環境)的平臺上。但因爲JRMP是專爲Java對象制定的,所以,RMI對於用非Java語言開發的應用系統的支持不足。不能與用非Java語言書寫的對象進行通訊。
RMI可利用標準Java本機方法接口JNI與現有的和原有的系統相鏈接。RMI還可利用標準JDBC包與現有的關係數據庫鏈接。RMI/JNI和RMI/JDBC相結合,可幫助您利用RMI與目前使用非Java語言的現有服務器進行通訊,並且在您須要時可擴展Java在這些服務器上的使用。RMI可幫助您在擴展使用時充分利用Java的強大功能。java

1、RMI(遠程方法調用)的組成
一個正常工做的RMI系統由下面幾個部分組成:
·遠程服務的接口定義
·遠程服務接口的具體實現
·樁(Stub)和框架(Skeleton)文件
·一個運行遠程服務的服務器
·一個RMI命名服務,它容許客戶端去發現這個遠程服務
·類文件的提供者(一個HTTP或者FTP服務器)
·一個須要這個遠程服務的客戶端程序 數據庫

2、RMI(遠程方法調用)原理示意圖 tomcat


方法調用從客戶對象經佔位程序(Stub)、遠程引用層(Remote Reference Layer)和傳輸層(Transport Layer)向下,傳遞給主機,而後再次經傳 輸層,向上穿過遠程調用層和骨幹網(Skeleton),到達服務器對象。 佔位程序扮演着遠程服務器對象的代理的角色,使該對象可被客戶激活。 遠程引用層處理語義、管理單一或多重對象的通訊,決定調用是應發往一個服務器仍是多個。傳輸層管理實際的鏈接,而且追蹤能夠接受方法調用的遠程對象。服務器端的骨幹網完成對服務器對象實際的方法調用,並獲取返回值。返回值向下經遠程引用層、服務器端的傳輸層傳遞迴客戶端,再向上經傳輸層和遠程調用層返回。最後,佔位程序得到返回值。
要完成以上步驟須要有如下幾個步驟:
一、 生成一個遠程接口
二、 實現遠程對象(服務器端程序)
三、 生成佔位程序和骨幹網(服務器端程序)
四、 編寫服務器程序
五、 編寫客戶程序
六、 註冊遠程對象
七、 啓動遠程對象 安全

3、RMI(遠程方法調用)的優勢
從最基本的角度看,RMI是Java的遠程過程調用(RPC)機制。與傳統的RPC系統相比,RMI具備若干優勢,由於它是Java面向對象方法的一部分。傳統的RPC系統採用中性語言,因此是最普通的系統--它們不能提供全部可能的目標平臺所具備的功能。
RMI以Java爲核心,可與採用本機方法與現有系統相鏈接。這就是說,RMI可採用天然、直接和功能全面的方式爲您提供分佈式計算技術,而這種技術可幫助您以不斷遞增和無縫的方式爲整個系統添加Java功能。
RMI的主要優勢以下:
面向對象:RMI可將完整的對象做爲參數和返回值進行傳遞,而不只僅是預約義的數據類型。也就是說,您能夠將相似Java哈希表這樣的複雜類型做爲一個參數進行傳遞。而在目前的RPC系統中,您只能依靠客戶機將此類對象分解成基本數據類型,而後傳遞這些數據類型,最後在服務器端從新建立哈希表。RMI則不需額外的客戶程序代碼(將對象分解成基本數據類型),直接跨網傳遞對象。
可移動屬性:RMI可將屬性(類實現程序)從客戶機移動到服務器,或者從服務器移到客戶機。這樣就能具有最大的靈活性,由於政策改變時只須要您編寫一個新的Java類,並將其在服務器主機上安裝一次便可。
設計方式:對象傳遞功能使您能夠在分佈式計算中充分利用面向對象技術的強大功能,如二層和三層結構系統。若是您可以傳遞屬性,那麼您就能夠在您的解決方案中使用面向對象的設計方式。全部面向對象的設計方式無不依靠不一樣的屬性來發揮功能,若是不能傳遞完整的對象--包括實現和類型--就會失去設計方式上所提供的優勢。
安  全:RMI使用Java內置的安全機制保證下載執行程序時用戶系統的安全。RMI使用專門爲保護系統免遭惡意小應用程序侵害而設計的安全管理程序,可保護您的系統和網絡免遭潛在的惡意下載程序的破壞。在狀況嚴重時,服務器可拒絕下載任何執行程序。
便於編寫和使用:RMI使得Java遠程服務程序和訪問這些服務程序的Java客戶程序的編寫工做變得輕鬆、簡單。遠程接口實際上就是Java接口。服務程序大約用三行指令宣佈自己是服務程序,其它方面則與任何其它Java對象相似。這種簡單方法便於快速編寫完整的分佈式對象系統的服務程序,並快速地製作軟件的原型和早期版本,以便於進行測試和評估。由於RMI程序編寫簡單,因此維護也簡單。
可鏈接現有/原有的系統:RMI可經過Java的本機方法接口JNI與現有系統進行進行交互。利用RMI和JNI,您就能用Java語言編寫客戶端程序,還能使用現有的服務器端程序。在使用RMI/JNI與現有服務器鏈接時,您能夠有選擇地用Java從新編寫服務程序的任何部分,並使新的程序充分發揮Java的功能。相似地,RMI可利用JDBC、在不修改使用數據庫的現有非Java源代碼的前提下與現有關係數據庫進行交互。
編寫一次,處處運行:RMI是Java「編寫一次,處處運行 」方法的一部分。任何基於RMI的系統都可100%地移植到任何Java虛擬機上,RMI/JDBC系統也不例外。若是使用RMI/JNI與現有系統進行交互工做,則採用JNI編寫的代碼可與任何Java虛擬機進行編譯、運行。
分佈式垃圾收集:RMI採用其分佈式垃圾收集功能收集再也不被網絡中任何客戶程序所引用的遠程服務對象。與Java 虛擬機內部的垃圾收集相似,分佈式垃圾收集功能容許用戶根據本身的須要定義服務器對象,而且明確這些對象在再也不被客戶機引用時會被刪除。
並行計算:RMI採用多線程處理方法,可以使您的服務器利用這些Java線程更好地並行處理客戶端的請求。Java分佈式計算解決方案:RMI從JDK 1.1開始就是Java平臺的核心部分,所以,它存在於任何一臺1.1 Java虛擬機中。全部RMI系統均採用相同的公開協議,因此,全部Java 系統都可直接相互對話,而沒必要事先對協議進行轉換。服務器

4、RMI與CORBA的關係
RMI 和 CORBA 常被視爲相互競爭的技術,由於二者都提供對遠程分佈式對象的透明訪問。但這兩種技術其實是相互補充的,一者的長處正好能夠彌補另外一者的短處。RMI 和 CORBA 的結合產生了 RMI-IIOP,RMI-IIOP 是企業服務器端 Java 開發的基礎。1997 年,IBM 和 Sun Microsystems啓動了一項旨在促進 Java 做爲企業開發技術的發展的合做計劃。兩家公司特別着力於如何將 Java 用做服務器端語言,生成能夠結合進現有體系結構的企業級代碼。所須要的就是一種遠程傳輸技術,它兼有 Java 的 RMI(Remote Method Invocation,遠程方法調用)較少的資源佔用量和更成熟的 CORBA(Common Object Request Broker Architecture,公共對象請求代理體系結構)技術的健壯性。出於這一須要,RMI-IIOP問世了,它幫助將 Java 語言推向了目前服務器端企業開發的主流語言的領先地位。 網絡

Java RMI不是什麼新技術(在Java1.1的時代都有了),但倒是是很是重要的底層技術。
大名鼎鼎的EJB都是創建在rmi基礎之上的,如今還有一些開源的遠程調用組件,其底層技術也是rmi。

在大力鼓吹Web Service、SOA的時代,是否是每一個應用都應該選用笨拙的Web Service組件來實現,經過對比測試後,RMI是最簡單的,在一些小的應用中是最合適的。

下面經過一個簡單的例子來講明RMI的原理和應用,下面這個例子是一個簡單HelloWorld,但已涵蓋RMI的核心應用與開發模式。多線程

接口代碼框架

1 public interface IHello extends Remote{
2     
3     public String helloWorld() throws RemoteException;
4     
5     public void sayHelloToSomebody(String somebody) throws RemoteException;
6 }

實現類:分佈式

 1 public class Hello extends UnicastRemoteObject implements IHello/*,Serializable*/ {
 2 
 3     public Hello() throws RemoteException {
 4         super();
 5         // TODO Auto-generated constructor stub
 6     }
 7 
 8     @Override
 9     public String helloWorld() throws RemoteException {
10         return "helloWorld";
11     }
12 
13     @Override
14     public void sayHelloToSomebody(String somebody) throws RemoteException {
15         System.out.println("hello "+somebody+"!");
16     }
17 
18 }

 

服務器端:ide

 1 public class HelloServer {
 2     public static void main(String[] args) {
 3         
 4         try {
 5             //建立一個遠程對象
 6             IHello hello = new Hello();
 7             
 8             //本地主機上的遠程對象註冊表Registry的實例,並指定端口爲8888,這一步必不可少(Java默認端口是1099),必不可缺的一步,缺乏註冊表建立,則沒法綁定對象到遠程註冊表上 
 9             LocateRegistry.createRegistry(8888);
10             //把遠程對象註冊到RMI註冊服務器上,並命名爲hello 
11             Naming.bind("rmi://localhost:8888/hello", hello);
12             
13             System.out.println("服務器綁定成功");
14             
15         } catch (RemoteException e) {
16             System.out.println("建立遠程對象發生異常!"); 
17             // TODO Auto-generated catch block
18             e.printStackTrace();
19         } catch (MalformedURLException e) {
20             System.out.println("發生URL畸形異常!"); 
21             // TODO Auto-generated catch block
22             e.printStackTrace();
23         } catch (AlreadyBoundException e) {
24             System.out.println("發生重複綁定對象異常!");
25             // TODO Auto-generated catch block
26             e.printStackTrace();
27         }
28     }
29 }

客戶端:

public class HelloClient {
    public static void main(String[] args) {
        try {
            //在RMI服務註冊表中查找名稱爲hello的對象,並調用其上的方法 
            IHello hello =  (IHello) Naming.lookup("rmi://localhost:8888/hello");
            
            System.out.println(hello.helloWorld());
            
            hello.sayHelloToSomebody("lc");
        } catch (MalformedURLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NotBoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

從上面的過程來看,RMI對服務器的IP地址和端口依賴很緊密,可是在開發的時候不知道未來的服務器IP和端口如何,可是客戶端程序依賴這個IP和端口。
這也是RMI的侷限性之一。這個問題有兩種解決途徑:一是經過DNS來解決,二是經過封裝將IP暴露到程序代碼以外。
RMI的侷限性之二是RMI是Java語言的遠程調用,兩端的程序語言必須是Java實現,對於不一樣語言間的通信能夠考慮用Web Service或者公用對象請求代理體系(CORBA)來實現。

Java RMI 簡單示例二
如下用一個文件交換程序來介紹RMI的應用。這個應用容許客戶端從服務端交換(或下載)全部類型的文件。第一步是定義一個遠程的接口,這個接口指定的簽名方法將被服務端提供和被客戶端調用。

接口代碼:

1 public interface IfileUtil extends Remote{
2     public byte[] downloadFile(String filename) throws RemoteException;
3 }

實現類代碼:

 1 public class FileUtilImpl extends UnicastRemoteObject implements IfileUtil {
 2 
 3     /**
 4      * 
 5      */
 6     private static final long serialVersionUID = 91357143467249225L;
 7 
 8     public FileUtilImpl() throws RemoteException {
 9         
10     }
11     
12     
13     @Override
14     public byte[] downloadFile(String filename) throws RemoteException {
15         File file = new File(filename);
16 
17         byte[] buffer = new byte[(int) file.length()];
18 
19         int size = buffer.length;
20 
21         System.out.println("download file size = " + size + "b");
22 
23         if (size > 1024 * 1024 * 10) {// 限制文件大小不能超過10M,文件太大可能導制內存溢出!
24 
25             throw new RemoteException("Error:<The File is too big!>");
26 
27         }
28 
29         try {
30 
31             BufferedInputStream input = new BufferedInputStream(
32 
33             new FileInputStream(filename));
34 
35             input.read(buffer, 0, buffer.length);
36 
37             input.close();
38 
39             System.out.println("Info:<downloadFile() hed execute successful!>");
40 
41             return buffer;
42 
43         } catch (Exception e) {
44 
45             System.out.println("FileUtilImpl: " + e.getMessage());
46 
47             e.printStackTrace();
48             
49             return null;
50         }
51         
52     }
53 
54 }

服務器端代碼:

 1 public class FileutilServer {
 2     public static void main(String[] args) {
 3         try {
 4 
 5             IfileUtil file = new FileUtilImpl();
 6 
 7             LocateRegistry.createRegistry(8888);
 8             // //加上此程序,就能夠不要在控制檯上開啓RMI的註冊程序,1099是RMI服務監視的默認端口
 9 
10             Naming.rebind("rmi://localhost:8888/FileUtilServer", file);
11 
12             System.out.print("Ready");
13 
14         } catch (Exception e) {
15 
16             System.out.println("FileUtilServer: " + e.getMessage());
17 
18             e.printStackTrace();
19 
20         }
21     }
22 }

客戶端代碼:

 1 public class FileutilClient {
 2     public static void main(String[] args) {
 3         try {
 4           IfileUtil ifileUtil =    (IfileUtil) Naming.lookup("rmi://localhost:8888/FileUtilServer");
 5           byte[] bytes = ifileUtil.downloadFile("D:\\tomcat\\RUNNING.txt");
 6           
 7           if(bytes == null){
 8               System.out.println("the file is empty");
 9               System.exit(0);
10           }
11           
12          System.out.println(bytes.length);
13             
14             try {
15                 File file = new File("copy.java");
16                 System.out.println("file.getAbsolutePath() = "
17                         + file.getAbsolutePath());
18 
19                 BufferedOutputStream output = new BufferedOutputStream(
20 
21                 new FileOutputStream(file.getAbsolutePath()));
22 
23                 output.write(bytes, 0, bytes.length);
24 
25                 output.flush();
26 
27                 output.close();
28 
29                 System.out.println("~~~~~End~~~~~");
30             } catch (FileNotFoundException e) {
31                 // TODO Auto-generated catch block
32                 e.printStackTrace();
33             } catch (IOException e) {
34                 // TODO Auto-generated catch block
35                 e.printStackTrace();
36             }
37 
38             
39         } catch (MalformedURLException e) {
40             // TODO Auto-generated catch block
41             e.printStackTrace();
42         } catch (RemoteException e) {
43             // TODO Auto-generated catch block
44             e.printStackTrace();
45         } catch (NotBoundException e) {
46             // TODO Auto-generated catch block
47             e.printStackTrace();
48         }
49     }
50 }
相關文章
相關標籤/搜索