1. 功能需求:实现最简单的远程文件访问。Client能够获取在Server上的远程文件信息。实例代码中,客户端获取了服务器端的文件“D:\\test\testDate.java”的内容和文件名。
2. 实现结构:
a) Server包含:
i. FileInformation.java
描述文件信息的接口,作为远程访问方法的返回值类型
ii. FileInformationImple.java
描述文件信息的接口实现
iii. Hello.java
提供的远程访问方法的接口
iv.HelloImple.java
远程访问方法的接口实现
v. Server.java
服务器开启程序
b) Client 包含:
i. FileInformation.java描述文件信息的接口 (与上同)
ii.Hello.java提供的远程访问方法的接口(与上同)
iii. Test.java客户端的运行程序
3. 服务器端实现的具体步骤:
a) 设计远程接口涉及的参数类型接口
package rmi;
import java.io.Serializable;
/所有远程接口使用到的参数类型和返回值类型都必须可序列化。
public interface FileInformation extends Serializable {
public String getContent();//获取文件内容
public String getFileName();//获取文件名
public boolean setFileInformation(String name, String currentDir);
//通过指定文件路径来设置文件信息
}
b) 设计远程接口
package rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
//远程接口必须扩展接口java.rmi.Remote
public interface Hello extends Remote {
//远程接口方法必须抛出 java.rmi.RemoteException
//远程接口方法中的参数和返回值都必须可序列化
FileInformation getFile(String name) throws RemoteException;
}
c) 实现参数类型接口
package rmi;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
//实现返回值类型接口,与其它接口实现类没有区别
public class FileInformationImple implements
FileInformation {
private static final long serialVersionUID = -8484348346023818166L;
private String fileName = null;
private String content = null;
public String getContent() {
return content;
}
public String getFileName() {
return fileName;
}
public boolean setFileInformation(String name, String currentDir) {
String path = currentDir + "/" + name;
try {
BufferedInputStream in = new BufferedInputStream(new FileInputStream(new File(path)));
byte[] b = new byte[10000];
int i = in.read(b);
if (i == -1) {
return false;
}
content = new String(b);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
d) 实现远程接口
package rmi;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
//扩展了UnicastRemoteObject类,并实现远程接口 Hello,Serializable
public class HelloImple extends UnicastRemoteObject implements Hello, Serializable {
//必须定义构造方法,因为它必须抛出RemoteException异常
protected HelloImple() throws RemoteException {
super();
}
private static final long serialVersionUID = -7671514615926445886L;
//远程接口方法的实现
public FileInformation getFile(String name) throws RemoteException {
FileInformation fInfor = new FileInformationImple();
if(!fInfor.setFileInformation(name, "D:/test")) {
throw new RemoteException("invalid path");
}
return fInfor;
}
}
e)
编写服务器启动程序
package rmi;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
//启动 RMI 注册服务并进行对象注册
public class Server {
public static void main(String[] args) {
try {
//启动RMI注册服务,指定端口为1099(1099为默认端口),也可以通过命令启动$java_home/bin/rmiregistry 1099
//这里用这种方式避免了再打开一个DOS窗口,而且用命令rmiregistry启动注册服务还必须事先用RMIC生成一个stub类为它所用
LocateRegistry.createRegistry(1099);
//创建远程对象的一个或多个实例,下面是hello对象,用不同名字注册不同的实例
Hello hello = new HelloImple();
//把hello注册到另一台启动了RMI注册服务的机器上,命名为Hello
Naming.rebind("rmi:/127.0.0.1:1099/Hello", hello);
System.out.println("Server start!");
} catch (Exception e1) {
e 1.printStackTrace();
}
}
}
4. 编写客户端测试用例
package rmi; import java.rmi.Naming;
public class Test {
//查找远程对象并调用远程方法
public static void main(String[] args) {
try {
//从另一台启动了RMI注册服务的机器上查找hello实例
Hello h = (Hello) Naming.lookup("rmi:/127.0.0.1:1099/Hello");
//调用远程方法
FileInformation f = h.getFile("testDate.java");
System.out.println(f.getContent());
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. 执行程序:可以在eclipse中直接执行,但是必须先运行服务器,再运行客户端。也可以在命令行下运行。次序要求同在eclipse中。步骤如下:
(1)
打开一个Dos窗口,执行命令java
rmi.Server 启动服务器。启动成功会在命令行显示“Server start!”
(2)
打开另一个Dos窗口,执行命令java
rmi.Test 运行客户端程序。运行成功则会在屏幕打印“D:\\test\testDate.java”的文件内容。
6. 注意事项
(1)
本实例中并没有用到JDK所带的命令 rmic 编译实现类得到存根(Stub)类,也没用命令 rmiregistry 命令来启动RMI注册服务。在启动 rmiregistry之前必须能让它加载到相应的stub类,这就是造成**_Stub 类找不到的原因。本实例中通过“LocateRegistry.createRegistry(1099);”避免了这种错误。
(2)
如果要从另一台启动了RMI注册服务的机器上查找hello实例:
Hello hello = (Hello) Naming.lookup
("//192.168.1.105:1099/Hello") ;
其中的IP地址和端口号1099为 RMI 注册服务器的IP和端口号,这样Client就可以在另一台机器运行。
(3)
无论是服务端还是客户端都可以用参数 。
-Djava.rmi.server.codebase=http://YourServerName/YourPackagePathName/
此时可以像JNP一样从网络上动态加载类文件。而你Web Server的YourServerName/YourPackagePathName/目录下应该有一个与你的包同名的目录,那个目录下有相应stub文件。
如果出现错误:
java.lang.ClassNotFoundException:rmi.XXX_Stub(no security manager:RMI class loader disabled)
这是因为在Client端没有设置安全管理器,要象Server起动时那样给一个java.security.policy属性,允许它做相应socket工作(否则会出安全性异常),之后客户端就能自动从主机下载了stub文件了。
(4)
代码中 Server 和 Client 省略了设置安全管理器的过程
System.setSecurityManager(new RMISecurityManager());
如果设置的安全管理则必须编写相应的访问策略文件。 在命令行下输入命令 policytool-->添加规则项目-->添加权限,在权限窗口的第一个下拉框中选择 All
Permission,确认-->完成。然后在主菜单中选择文件->另存,将设置保存为 .java.policy.
并且在执行时指定参数。
(5)
可以拿单独一台机器运行
rmiregistry 。
start rmiregistry
rmiregistry需要能加载到相应的stub类,可以通过设置classpath。或者使LocateRegistry.createRegistry(port)
,只作为 RMI远程对象的RMI集中注册的服务器,真正提供服务对象只往上注册,客户端只需从注册服务器上查找远程对象引用,然后调用远程方法,具体由谁提供服务由注册服务器来帮助联络。
(6)
还可以用 RMI
Activation 编程方式来实现RMI远程方法调用,具体请参考 http://java.sun.com/j2se/1.4.2/docs/guide/rmi/activation.html
(7)
为什么要序列化远端接口中的参数(返回值)
这是因为需要将客户端的对象(参数)转化成byte stream,通过网络协议传输到服务端,再还原成服务端的对象进行调用。或者是需要将服务端中, java.lang包和java.util包下的类都已经实现了序列化,直接可以用在远程接口中作参数或返回值;所有的基本类型也可以直接用在远程接口中作参数或返回值;的对象(返回值)转化成byte stream,通过网络协议传输到服务端,再还原成客户端的对象进行调用。在
jdk