2007-03-18

快快樂樂學 RMI - Start!

身為一個只會寫 Java 的人而言,遇到分散式系統,好像不能不實際來 try 一下 RMI。不過對於用 Eclipse 習慣的人而言... 寫 RMI 實在好麻煩啊... [泣],google 找到的 plug-in 似乎只有這個,但是看到他要收費,馬上就軟掉(?)了。Ant 又完全沒有用的習慣,還是回憶一下 bat 檔怎麼寫吧... Orz

好了,言歸正傳。

因 為種種點點點我不知道的原因(理論上是因為 Java 沒有多重繼承,然後 RMI 又希望溝通的物件是 parent class 是 java.rmi.server.UnicastRemoteObject,不過這純屬猜測... API 懶得看 XD),所以真正要讓 client 可以遠端呼叫的物件,必須讓它 implement 自己訂做的 interface,而且這個 interface 還得 extend java.rmi.Remote。所以,就先來定義一下 interface 吧:

FooInterface.java
package test.remoteInterface;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Date;

public interface FooInterface extends Remote {
public Date getServerDate() throws RemoteException;

public String getServerMessage() throws RemoteException;

public void setServerMessage(String s) throws RemoteException;
}


這 裡留了幾個惡搞的空間。只要 Server 跟 client 不同機器,且時間調的不一樣,那麼 getServerDate() 就可以顯現出差異。而 serverMessage 的 setter 跟 getter 是用來測試 server 端的物件如果被改變,能不能 keep 住。下面這個實做就變得沒啥好講了... [茶]

FooObject.java
package test.remoteObject;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Date;
import test.remoteInterface.FooInterface;

public class FooObject extends UnicastRemoteObject implements FooInterface {
/**
* 為了減少 warning 而加的
*/
private static final long serialVersionUID = 1L;

private String message;

public FooObject() throws RemoteException {
super();
message = "Default Message";
}

public Date getServerDate() throws RemoteException {
return new Date();
}

public String getServerMessage() throws RemoteException {
return message;
}

public void setServerMessage(String s) throws RemoteException {
message = s;
}
}


然後追加一個 server,執行這個就可以讓他接客了 \囧/
FooServer.java
package test;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import test.remoteObject.FooObject;

public class FooServer {
public static void main(String[] args) {
FooObject foo;
try {
foo = new FooObject();
Naming.rebind("rmi://127.0.0.1/FooServer", foo);
} catch (RemoteException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
System.out.println("Foo Server is ready");
}
}


最後,當然是來寫 client 端啊:
FooClient.java
package test;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.Date;
import test.remoteInterface.FooInterface;

public class FooClient {
/**
* 1: ip 位址
* 2: 設定字串
* @param args 參數
*/
public static void main(String[] args) {
try {
FooInterface foo = (FooInterface) Naming.lookup("rmi://" + args[0]
+ "/FooServer");
System.out.println("Server Date : " + foo.getServerDate());
System.out.println("Client Date : " + new Date());
System.out
.println("Before set message : " + foo.getServerMessage());
foo.setServerMessage(args[1]);
System.out.println("After set message : " + foo.getServerMessage());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
} catch (NotBoundException e) {
e.printStackTrace();
}
}
}
給 我等一下![指],並不是這樣子 compile 完,分別執行 FooServer 跟 FooClient 就能用。你還得針對 FooObject 另外作 rmi compile 的處理。在這個 foo 的例子當中,就要下(classpath 等問題請自己搞定)
rmic test.remoteObject.FooObject
這會另外產生 FooObject_Stub.class,在包 client 的執行環境,這個檔也要加進去。

另外,在執行 FooServer 之前,得先執行一個程式 rmiregistry(在 {JDK}/bin/)。如果你也不幸是用 Windows,那在 command line 的環境下打「start rmiregistry」,會另外自動開個黑窗。

一切都順利的話,就可以來真正來玩玩看了。[茶]

錯誤排除區:
java.net.ConnectException→咪的,rmiregistry 啟動了沒?

備註區:
網路上的範例幾乎都有在 client/server 端的程式碼來這行:
System.setSecurityManager(newRMISecurityManager())
但是... 我用了反而問題一大堆(有設定 java.policy 了),還搞出一堆靈異現象。拿掉那行反而一點事情也沒有 [爆]。