對RMI的學習已經過去了3天時間,概念很簡單,但是在使用過程中總是遇到這樣那樣的問題,也再次從一個側面證明了,技術的學習一定要通過真實環境的測試,否則知其然不知其是以然也是沒有辦法在生産環境中來使用的,下面對rmi的原理與問題進行分析。
下面的描述部分内容收集來自網際網路.
黑體部分是我加注并需要注意的
RMI(即Remote Method Invoke 遠端方法調用)。在Java中,隻要一個類extends了java.rmi.Remote接口,即可成為存在于伺服器端的遠端對象,供用戶端通路并提供一定的服務。JavaDoc描述:Remote 接口用于辨別其方法可以從非本地虛拟機上調用的接口。任何遠端對象都必須直接或間接實作此接口。隻有在“遠端接口”(擴充 java.rmi.Remote 的接口)中指定的這些方法才可遠端使用。
注意:extends了Remote接口的類或者其他接口中的方法若是聲明抛出了RemoteException異常,則表明該方法可被用戶端遠端通路調用。
同時,遠端對象必須實作java.rmi.server.UniCastRemoteObject類,這樣才能保證用戶端通路獲得遠端對象時,該遠端對象将會把自身的一個拷貝以Socket的形式傳輸給用戶端,此時用戶端所獲得的這個拷貝稱為“存根”,而伺服器端本身已存在的遠端對象則稱之為“骨架”。其實此時的存根是用戶端的一個代理,用于與伺服器端的通信,而骨架也可認為是伺服器端的一個代理,用于接收用戶端的請求之後調用遠端方法來響應用戶端的請求。
RMI 架構的基本原理大概如下圖,應用了代理模式來封裝了本地存根與真實的遠端對象進行通信的細節。
下面給出一個簡單的RMI 應用,其中類圖如下:其中IService接口用于聲明伺服器端必須提供的服務(即service()方法),ServiceImpl類是具體的服務實作類,而Server類是最終負責注冊伺服器遠端對象,以便在伺服器端存在骨架代理對象來對用戶端的請求提供處理和響應。
======================================================這裡是分隔線================================================
public interface WareHouse extends Remote { double getPrice(String decription) throws RemoteException; } |
public class WareHouseImpl extends UnicastRemoteObject implements WareHouse { private Map<String, Double> prices; protected WareHouseImpl() throws RemoteException { prices = new HashMap<String, Double>(); prices.put("Blackwell Toaster", 24.95); prices.put("Zap", 49.95); } @Override public double getPrice(String decription) throws RemoteException { Double price = prices.get(decription); return price == null ? 0 : price; } } |
public class WareHouseServer { public static void main(String[] args) throws RemoteException, NamingException, MalformedURLException, AlreadyBoundException { //這裡是RMI遠端調用的一個最容易不被主要到的點,如果沒有下面的設定,可能你在本地測試都是沒有問題 //但是如果開啟防火牆的主機上面以測試就會遇到問題 try { RMISocketFactory.setSocketFactory(new SMRMISocket()); } catch (Exception ex) { } WareHouseImpl centralWareHouse = new WareHouseImpl(); LocateRegistry.createRegistry(8888); Naming.rebind("rmi://host:8888/central_warehouse", centralWareHouse); System.out.println("遠端服務啟動完成..."); } } public class SMRMISocket extends RMISocketFactory { public Socket createSocket(String host, int port) throws IOException { return new Socket(host, port); } public ServerSocket createServerSocket(int port) throws IOException { //在這裡可以指定 RMI 服務監聽端口 if (port == 0) { port = 21100; // 由于測試這裡就寫硬代碼了 } return new ServerSocket(port); } } |
public class WareHouseClient { public static void main(String[] args) throws NamingException, RemoteException, MalformedURLException, NotBoundException { Registry registry = LocateRegistry.getRegistry("host", 8888); for (String s : registry.list()) { System.out.println(s); } WareHouse wareHouse = (WareHouse) Naming.lookup("rmi://host:8888/central_warehouse"); String des = "Blackwell Toaster"; double price = wareHouse.getPrice(des); System.out.println(des + " -> " + price); } } |
上面黑體部分是我遇到最大的問題,開始沒有加,我在本機測試始終是沒有問題的,後面部署到linux上面就始終通路不到,查了很久資料發現網上很多資料都隻是在本地測試,并且基本上都是demo階段,大家都是不求甚解的(呵呵…)。後面總算在一個大神的blog中發現了蛛絲馬迹,原因如下:
RMI 遠端調用分為三部分 系統資料庫服務 、我們的服務 、用戶端
上面代碼中8888端口隻是系統資料庫服務,在我們的server端代碼中我們開啟了系統資料庫服務,然後我們向它注冊了一個WareHouse服務,但是WareHouse服務并不是通過8888來通路的,而是被随機配置設定了一個(在我們沒有指定RMISocketFactory的情況下),是以在linux上面因為這個随機端口問題,我們始終被防火牆攔在了外面。