RMI
分佈式對象 ---- 力求“無處不在的對象”。RMI(遠程方法調用),支持java分佈式對象的方法調用。是分佈式對象軟件包,簡化了在多個計算機中的java應用之間的通信。
What is RMI?
RMI is 一種計算機之間對象互相調用對方方法,啓動對方進程的機制,某一臺計算機上的對象在調用另外一臺計算機上的方法時,使用的程序語法規則和在本地機上對象間的方法調用的語法規則一樣。
優點
這種機制給分佈計算的系統設計、編程都帶來了極大的方便。
只要按照RMI規則設計程序,可以不必再過問在RMI之下的網絡細節了,如:TCP和Socket等等。
任意兩臺計算機之間的通訊完全由RMI負責。調用遠程計算機上的對象就像本地對象一樣方便。
說白了,RMI就是提供一種遠程方法調用,所有的遠程服務的底層交互都有RMI來完成(RMI將其內部實現細節隱藏),使得我們調用遠程對象的方法就像是在訪問本地對象一樣,使得分佈式編程輕鬆、簡單。
下面來創建簡單的RMI程序:
步驟->
1、 定義遠程接口,該接口必須繼承自java.rmi.Remote接口,且聲明的可以被遠程調用的方法必須拋出RemoteException異常。
2、 定義一個實現該接口的類。
3、 使用rmic生成存根和框架。
4、 創建一個服務器,用於發佈遠程對象(如2中的對象)。
5、 創建一個客戶端程序進行RMI調用。
6、 啓動rmiRegistry並運行服務器和客戶端程序。
定義接口
package org.shniu.rmi.simple2;
import java.rmi.Remote; import java.rmi.RemoteException;
/** * 遠程方法調用的接口,必須繼承java.rmi.Remote * 所有可以被遠程調用的對象都必須實現Remote * * @author shniu */ publicinterface IProduct extends Remote { /** * 通過產品id獲取產品名稱 * * @throws RemoteException 接口中定義的方法必須聲明拋出RemoteException異常 */ String getProductNameById(long productId) throws RemoteException;
/** * 返回產品的相關信息 * * @throws RemoteException 接口中定義的方法必須聲明拋出RemoteException異常 */ Object getProductInfo() throws RemoteException;
/** * @注:必須聲明拋出RemoteException的原因 * * 由於任何遠程方法調用實際上要進行許多低級網絡操作,因此網絡錯誤可能在調用過程中隨時發生。 * * 因此,所有的RMI操作都應放到try-catch塊中。 */ }
|
接口實現:
package org.shniu.rmi.simple2;
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject;
/** * * 實現類,該類必須實現IProduct接口和繼承UnicastRemoteObject類 * * UnicastRemoteObject,是讓客戶機與服務器對象實例建立一對一的連接。 * * @author shniu */ publicclass Book extends UnicastRemoteObject implements IProduct { private String bookName;
private String bookDesc;
private String author;
privatelongcode;
public String getBookName() { returnthis.bookName; }
publicvoid setBookName(String bookName) { this.bookName = bookName; }
public String getBookDesc() { returnthis.bookDesc; }
publicvoid setBookDesc(String bookDesc) { this.bookDesc = bookDesc; }
public String getAuthor() { returnthis.author; }
publicvoid setAuthor(String author) { this.author = author; }
publiclong getCode() { returnthis.code; }
publicvoid setCode(long code) { this.code = code; }
/** <默認構造函數> */ protected Book() throws RemoteException { super(); }
/** * 帶參構造 */ public Book(String bookName, String bookDesc, String author, long code) throws RemoteException { super(); this.bookName = bookName; this.bookDesc = bookDesc; this.author = author; this.code = code; }
/** * @return * @throws RemoteException */ public Object getProductInfo() throws RemoteException { returnthis.toString(); }
/** * @param productId * @return * @throws RemoteException */ public String getProductNameById(long productId) throws RemoteException { if (productId >= 0) { returnthis.bookName; } returnnull; }
@Override public String toString() { return"Book [author=" + this.author + ", bookDesc=" + this.bookDesc + ", bookName=" + this.bookName + ", code=" + this.code + "]"; } }
|
服務端實現:
package org.shniu.rmi.simple2;
import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.RemoteException;
/** * 用於發佈遠程對像 的類 * * * @author shniu */ public class ProductServer { public static void main(String[] args) { try { // 實例化 要發佈的類 Book book1 = new Book("java程序設計", "講述java知識,包括javaSE等基礎內容", "shniu", 123456789L); Book book2 = new Book("SSH詳解", "講述SSH三大框架的使用與集成", "shniu", 987654321L);
// 綁定RMI名稱進行發佈 // 由於是本地測試,省略了,缺省爲"rmi://localhost:1099/java_book" Naming.rebind("java_book", book1); Naming.rebind("java_SSH", book2);
System.out.println("Ready and Waiting for invocation ..."); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } |
客戶端實現
package org.shniu.rmi.simple2;
import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RMISecurityManager; import java.rmi.RemoteException;
/** * 客戶端調用 * * @author shniu */ public class ClientInvocation { public static void main(String[] args) { try { // 註冊安全管理器 System.setSecurityManager(new RMISecurityManager());
// 通過RMI查找遠程對象 IProduct product = (IProduct)Naming.lookup("java_book");// lookup方法中的參數,組成應該是"rmi://ip地址:port/RMI名稱" IProduct SSH = (IProduct)Naming.lookup("java_SSH"); // 由於是本地測試,省略了,缺省爲"rmi://localhost:1099/java_book" // 應與服務端url一致
// 調用遠程對象 String name = product.getProductNameById(123456789L); Object obj = product.getProductInfo(); System.out.println("#productName : " + name); System.out.println("#productInfo : " + obj);
String ssh_name = SSH.getProductNameById(987654321L); Object ssh_obj = SSH.getProductInfo(); System.out.println(); System.out.println("#productName : " + ssh_name); System.out.println("#productInfo : " + ssh_obj); } catch (MalformedURLException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } } } |
使用rmic編譯Book.class(先javac編譯)生成Stub存根Book_Stub.class
rmic org.shniu.rmi.simple2.Book
啓動rmiregistry,並運行服務器
DOS下運行:start rmiregistry [port]
Port 是可選的 ,如果不加,默認啓動1099端口
啓動服務進程
start java org.shniu.rmi.simple2.ProductServer
客戶端運行
在運行客戶端之前,要將Stub和IProduct加入類路徑
做法1、將Stub和IProduct打成jar包,加入classpath
做法2、運行客戶端程序是使用
java –Djava.rmi.server.codebase=file:/Stub和IProduct的存放位置 ClientInvocation (此法還未驗證)
注:在運行客戶端是可能會出現的問題
通常會報的錯誤是Access denied(java.net.SocketPermission .....)
問題原因:這是訪問權限限制的問題,RMI的服務需要授權,外部的程序才能訪問,所以改動jre的安全配置文件,來開放權限。
打開jdk目錄下的%JAVA_HOME%/jre/lib/security/java.policy,在文件的最後加入下面代碼:
Grant
{
Permission java.net.SocketPermission “*:1024-65535”
“connect,accept”;
Permission java.net.SocketPermission “*:80” “connect”;
};
應該修改的是服務端的java.policy文件。
RMI運行機理:
RMI應用程序通常包括兩個獨立的程序:服務器程序和客戶機程序。典型的服務器應用程序將創建多個遠程對象,使這些遠程對象能夠被引用,然後等待客戶機調用這些遠程對象的方法。而典型的客戶機程序則從服務器中得到一個或多個遠程對象的引用,然後調用遠程對象的方法。RMI爲服務器和客戶機進行通信和信息傳遞提供了一種機制。
理解客戶與服務器
在RMI中,如果一個對象的方法發起遠程調用,則該對象稱爲客戶端對象,相應的遠程對象則稱爲服務器端對象。客戶/服務器的概念是針對一次調用而言的,而他們的角色是隨時發生變化的。