14.4.1 RMI
介绍
RMI
是
Remote
Method
Invocation
(远程方法调用)的
所写。它允许一个
Java
程序调
用网络中另一台计算机上的
Java
方法,就如调用本机的方法一样。实现
RMI
调用的程序和
被调用的方法,都必须是
Java
代码,即客户端和服务器端都必须通过纯
Java
实现。
RMI
是基于
Java
的分布式编程模型,
使用
RMI
进行远程方法调用时,
无须考虑方法底
层的网络传输细节。
要使用
RMI
,必须构建四个主要的类:远程对象的本地接口、远程对象实现、
RMI
客户机和
RMI
服务器。
RMI
服务器生成远程对象实现的一个实例,并用一个专有的
URL
注册。
RMI
客户机在远程
RMI
服务器上查找服务对象,并将它转换成本地接口类型,然后
像对待一个本地对象一样使用它。
下面使用
RMI
的示例程序:
1
、先编写
RMI
服务器端(接口)
:
RMI
需要通过远程接口提供服务。也就是说,所有想被客户机调用的方法都必须在
Remote
接口里声明,否则无法完成调用。远程接口如下:
package
cn.ac.ict.rmi;
import
java.rmi.Remote;
import
java.rmi.RemoteException;
public
interface
Server
extends
Remote
{
//
所有在
Remote
接口里声明的方法都必须抛出
RemoteException
异常
String helloWorld(String name)
throws
RemoteException;
Person getPerson(String name,
int
age)
throws
RemoteException;
}
注意:
(
1
)
远程接口必须继承
java.rmi.Remote
接口。
(
2
)
远程接口里声明的方法会通过网络传输,而网络是不可靠的,因此,所有的远程方
法都必须抛出
RemoteException
。
(
3
)
RMI
服务是典型的面向接口编程,
只有在远程接口里定义的方法才会作为远程服务。
2
、接口实现类
远程服务提供类必须实现远程接口,并继承
java.rmi.server.UnicastRemoteObject
对象,继承
该类能提供远程服务。
package
cn.ac.ict.rmi;
import
java.rmi.RemoteException;
import
java.rmi.server.UnicastRemoteObject;
//
远程服务类,远程服务类必须继承
UnicastRemoteObject
,并实现
Remote
接口
public
class
ServerImpl
extends
UnicastRemoteObject
implements
Server
{
//
远程服务类必须拥有构造器,且构造器必须抛出
RemoteException
异常
public
ServerImpl()
throws
RemoteException {
super
();
}
//
实现
Remote
接口必须实现的方法
public
String helloWorld(String name)
throws
RemoteException {
return
name +
",
您好
!"
;
}
//
实现
Remote
接口必须实现的方法
public
Person getPerson(String name,
int
age)
throws
RemoteException
{
return
new
Person(name,age);
}
//
下面是服务类的本地方法,不会提供为远程服务。
public
void
info() {
S
ystem.
out
.println(
"I am a Local Method "
);
}
}
3
、
RMI
服务器类
package
cn.ac.ict.rmi;
import
java.rmi.Naming;
import
java.rmi.registry.LocateRegistry;
public
class
HelloServer {
public
static
void
main(String[] args) {
try
{
System.
out
.println(
"Start RMI servering..."
);
//
创建远程服务类实例
Server imp =
new
ServerImpl();
//
注册远程服务的端口
LocateRegistry.
createRegistry
(1099);
//
将远程服务实例绑定为远程服务。
Naming.
rebind
(
"rmi://168.160.194.155:1099/aa"
, imp);
System.
out
.println(
"Waiting client invoke..."
);
}
catch
(Exception e){
e.printStackTrace();
}
}
}
将两个编辑好的源文件存盘,然后编译。
注册远程服务端口:
LocateRegistry.
createRegistry
(1099);
1099
是
RMI
服务的默认端口。然后执行如下代码绑定远程服务
Naming.
rebind
(
"rmi://168.160.194.155:1099/aa"
, imp);
注意:
1
、如果一个类继承自
UnicastRemoteObject
,那么它必须提供一个构造函数并且声明
抛出一个
RemoteException
对象,即使找个构造器什么都不做。当这个构造函数调用了
super()
,
它就激活
UnicastRemoteObject
中的代码完成
RMI
的连接和远程对象的初始化。
2
、这种在程序中直接注册的方式(不使用
rmiregistry.exe
工具)
,不必再使用
rmic
命
令编译服务类生成
stub
类:在类的根目录下运行
rmic
命令,且类包含完整的包名:
bin> rmic cn.ac.ict.rmi.ServerImpl
4
、客户端
客户端程序面向接口编程,客户端部分需要
Server
接口的
class
文件,还需要
stub
文件。客
户端的源代码如下:
package
cn.ac.ict.rmi;
import
java.rmi.Naming;
import
java.rmi.RMISecurityManager;
public
class
RMIClient {
public
static
void
main(String[] args)
throws
Exception {
//
//
安全管理器类
RMISecurityManager.
这是因为
RMI
需要传递对象并进行动态类
装入,所以必须加载安全管理器以防止别人发送伪类
//
if(System.getSecurityManager() == null) {
//
//
System.setSecurityManager(new RMISecurityManager());
// }
try
{
/
/
通过
JNDI
查找远程服务
S
erver ser = (Server)Naming.
lookup
(
"rmi://
168.160.194.155:1099/aa"
);
/
/
调用远程方法
S
ystem.
out
.println(ser.helloWorld(
"cuixingchen"
));
/
/
调用远程方法。
S
ystem.
out
.println(ser.getPerson(
"cuixingchen"
,28));
}
catch
(Exception e){
e.printStackTrace();
}
}
}
上面注释了安全管理的代码,如果把注释去掉,那么需要建立一个安全策略文件。
从客户端程序看,无法感受到
Server
的实现在远端。程序调用
Server
实例方法时,与平常
调用方法只有非常细微的区别:调用远程方法必须抛出
RemoteException
。
再看远程方法的返回值,
helloWorld
的返回值是
String
,而
getPerson
的返回值则是
Person
对象。
远程方法的返回值必须有一个要求:实现
Serializable
接口
。因为远程的方法的参数、
返回值都必须在网络上传输,网络只能传输字节流,因此,要求参数、返回值都可以转换成
字节流——即实现序列化。
客户端使用
JNDI
查找,查找远程服务名。将远程服务对象类型转换成远程接口类型,客户
端面向接口编程。
RMI
的具体实现,依然是依赖于底层的
Socket
编程。
RMI
依赖于
TCP/IP
传输协议,服务器端
skeleton
建立
ServerSocket
监听请求,而客户端建立
Socket
请求连接。
RMI
实现了网络传输的多线程、
IO
等底层细节。这些细节的实现就隐藏在
rmic
命令的执行
中:使用
rmic
命令编译时生成的
class
文件:
stub
:该文件用于与客户端交流,建立
Socket
请求连接。
(
skeleton
用于与服务器端交流,建立
ServerSocket
监听请求。
)