声明:本案例使用的IDE是Eclipse,客户端程序和服务器程序位于同一个project中。
一、基本原理
RMI是Remote Method Invoke的缩写,是JDK提供的一个完善的、简单易用的远程调用框架,它要求客户端和服务器端都是Java程序。下面简述RMI的基本原理:如下图所示,RMI采用代理来负责客户端和服务器之间socket通信的细节。RMI框架分别为远程对象生成了客户端代理和服务器端代理,位于客户端的代理称为存根(Stub),位于服务器端的代理称为骨架(Skeleton)。
远程对象会在客户端生成存根对象。当客户端调用远程对象的方法时,实际上是调用本地存根的相应方法。然后,存根会把被访问的远程对象名、方法名以及参数编组后发送给服务器,由骨架去调用相应的远程方法并把返回值或异常返回给客户端。
二、基本步骤
一般来说,只要继承java.rmi.server.UnicastRemoteObject类和实现java.rmi.Remote 接口就可以成为远程对象。由于java的单继承,继承了UnicastRemoteObject类之后就不能继承其他的类,这时可以在构造方法中调用exportObect()方法,同样可以将其导为远程对象。实际上,UnicastRemoteObject的构造器也会去调用自身的exportObect()的静态方法。 下面是创建一个RMI程序的基本步骤: (1)创建远程接口,继承java.rmi.Remote接口; (2)创建远程类,实现远程接口; (3)创建服务器程序,在rmiregistry注册表中注册远程对象; (4)创建客户端程序,负责定位远程对象,并且调用远程方法。
*创建远程接口
远程接口中声明了可以被客户端访问的远程方法,远程接口应符合以下条件: (1)直接或间接继承java.rmi.Remote接口; (2)接口中的所有方法声明抛出java.rmi.RemoteException异常或父异常。
package main;
import java.rmi.Remote;
import java.rmi.RemoteException;
// Inherit the java.rmi.Remote interface
public interface HelloService extends Remote {
// Remote method should throw RemoteException
public String service(String data) throws RemoteException;
}
三、创建远程类
远程类应符合以下条件: (1)继承java.rmi.server.UnicastRemoteObject类并实现远程接口; (2)构造器必须抛出java.rmi.RemoteException异常。
package main;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
// Inherit UnicastRemoteObject and implement HelloService interface
public class HelloServiceImpl extends UnicastRemoteObject
implements HelloService {
private static final long serialVersionUID = 1L;
private String name;
public HelloServiceImpl(String name) throws RemoteException {
super();
this.name = name;
// UnicastRemoteObject.exportObject(this, 0);
}
@Override
public String service(String data) throws RemoteException {
return data + name;
}
}
四、创建服务器程序
在这里要介绍几个方法:
- bind(String name, Object obj): 注册对象,把对象与服务名绑定。如果该服务名已与其他对象绑定,则会抛出NameAlreadyBoundException异常。
- rebind(String name, Object obj): 注册对象,把对象与服务名绑定。如果该服务名已与其他对象绑定,不会抛异常,而是将新的对象绑定到该服务名上。
- lookup(String name): 查找对象,返回与指定名称相同的对象。
服务器端首先要创建注册表实例,然后将远程对象注册到注册表上。服务器程序运行就绪之后,不会立即结束,而是去监控客户端的连接。关于服务名的命名格式,推荐使用“rmi://主机名:端口号/实例名”的方式,这样可以避免在远程对象很多的时候因服务名一致而引发的冲突。默认的端口号1099可以省略。
package main;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Server {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099);
HelloService service1 = new HelloServiceImpl("service1");
Context namingContext = new InitialContext();
namingContext.rebind("rmi://localhost:1099/HelloService1",
service1);
}
catch (RemoteException | NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Successfully register a remote object.");
}
}
五、创建客户端程序
package main;
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
String url = "rmi://localhost:1099/";
try {
Context namingContext = new InitialContext();
HelloService serv = (HelloService) namingContext.lookup(
url + "HelloService1");
String data = "This is RMI Client.";
System.out.println(serv.service(data));
}
catch (NamingException | RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端先根据服务名查找远程对象,然后才能调用远程方法。因此,此处的服务名必须与在服务器中注册的服务名称一致,否则将无法找到远程对象。
六、运行
在Eclipse中先启动Server,然后启动Client即可。