使用JavaRmi
定义远程接口
- 一个远程对象就是实现了一个远程接口的类的实例
- 这个远程接口必须继承自
java.rmi.Remote
接口并且申明了一系列远程方法 - 这些方法必须抛出
RemoteException
异常(或者抛出这个异常的父类)
远程接口样例:
package example.hello;
import java.rmi.Remote;
import java.rmii.RemoteException;
public interface Hello extends Remote{
String sayHello() throws RemoteException;
}
在运行失败的时候,远程方法通过抛出异常记录这样的失败
实现服务器
一个服务器类有一个main方法可以实现创建远程对象的实例,并且导出远程对象,然后将这个对象绑定到
Java RMI registry
的一个name
服务器类样例:
下面的main方法实现了远程接口:
- 创建并且导出一个远程对象
- 在
java RMI registry
注册远程对象
package com.example.hello;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class Server implements Hello{
public Server() {}
public String sayHello() {
return "Hello world";
}
public static void main(String args[]){
try{
Server obj = new Server();
Hello stub = (Hello) UnicastRemoteObject.exportObject(obj,0);
//在registry的stub绑定远程对象
Registry registry = LocateRegistry.getRegistry();
registry.bind("Hello",stub);
System.out.println("Server Ready");
}catch(Exception e){
System.out.println("Server Exception: "+e.toString());
e.printStackTrace();
}
}
}
一个类可以定义远程方法中未定义的方法,但是这些方法只能在本地调用,不能远程调用
创建并且导出远程对象
服务器端的main方法创建远程对象来提供服务,除夕之外,远程对象必须导出到
Java RMI runtime
以便于他能够接受远程调用的输入
Server obj = new Server();
Hello stub = (Hello) UnicastRemoteObject.exportObject(obj,0);
静态方法UnicastRomoteObject.exportObject
将提供的远程对象导出,在一个匿名的端口接受远程方法调用并且为远程对象返回一个远程stub
给客户端。在导出对象被掉用之后,服务器可能会监听一个新的socket接口,也可能会使用一个共享的服务接口来接收调用远程对象的输入。
被返回的stub
实现与远程对象的类相同的接口集,并且包含可以联系远程对象的主机名和端口
在RMI registry 注册远程对象
对一个调用远程对象方法的客户端:
客户端首先拥有一个stub(指向远程对象):
作为引导,
Java RMI
为应用提供一个registry(注册表)接口来绑定name(指向远程对象的引用(stub))客户端通过
name
执行远程对象方法(也是以这种方式客户端拥有了远程对象的引用)
一个Java RMI注册表是一个简化的名称服务,为客户端提供一个指向远程对象的引用。总之,注册表仅仅用于定位客户端使用的第一个远程对象,这个远程对像将为查找其他对象提供特定于应用程序的支持,例如这个引用可以被拿来做另一个远程方法的参数或者返回值
一旦远程对象在server中被注册,调用者可以通过name查询这个对象,并且拥有这个对象的引用以及调用对象的远程方法
下面服务器的代码体现了一个本地注册表和默认注册端口的引用,之后使用注册表引用将名字“hello”绑定到该注册表中远程对象的引用
Registry registry = LocateRegustry.getRegistry();
registry.bind("Hello",stub);
静态方法LocateRegistry.getRegistry不需要参数,返回一个实现远程接口java.rmi.registry.Registry
的引用(registry)并且在默认注册表端口1099上向服务器本地主机的注册表发送调用.
registry然后调用bind方法将远程对象的引用绑定到的name
放在注册表中
注意:
LocateRegistry.getRegistry
简单地返回一个注册表的引用,这种调用不会检查注册表收否真的在本地运行,如果注册表并没有运行在本地的1099端口的时候调用bind方法,服务器会抛出RemoteException
实现客户端
客户端程序需要获取服务器主机上的registry的引用,通过名字查询注册表上远程对象的引用然后通过引用实现远程对象的sayHello方法
客户端样例:
package com.example.hello;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Client {
private Client(){};
public static void main(String[] args){
String host = (args.length<1)?null:args[0];
try{
Registry registry=LocateRegistry.getRegistry(host);
Hello stub = (Hello) registry.lookup("Hello");
String response = stub.sayHello();
System.out.println("response: "+response);
}catch(Exception e){
System.out.println("Client Exception: "+e.toString());
e.printStackTrace();
}
}
}
客户端首先通过调用静态方法
LocateRegistry.getRegistry
(带有命令行指的参数)获取目标服务器的注册表,如果没有指定服务器(参数),那么就获取本地的注册表之后,客户端调用注册表引用上的远程方法lookup,从服务器上注册表获取远程对象的引用
最后客户端调用远程对象引用
sayHello
方法,这会导致下面的action
:- 客户端运行时利用远程对象的引用的信息(主机和端口号)与服务器建立链接,并且序列化调用要使用的数据
- 服务器端运行时接受远程调用,分配这个调用给远程对象并且序列化结果(回复的”Hello World”)传给客户端
- 客户端运行时,接收反序列化结果,并返回给调用者
远程对象上的远程调用返回的响应消息打印到System.out
编译源文件
javac -d destDir Hello.java Server.java Client.java
destDir
用来存放类文件
开始Java RMI registry,server,client
通过使用命令开启rmiregistry
rmiregistry #默认在1099端口 start rmiregistry#windows平台 start rmiregistry 2001#指定端口
如果
rmiregistry
在1099以外的端口运行,纳闷服务器端和客户端代码需要指定注册表端口Registry registry = LocateRegistry.getRegistry(2001);
开启服务器
使用下面命令开启java服务
java -classpath classDir -Djava.rmi.server.codebase=file:classDir/ example.hello.Server &
classDir存放类文件的根目录
windows平台
start java -classpath classDir -Djava.rmi.server.codebase=file:classDir/ example.hello.Server &
classDir是类文件的根目录
设置java.rmi.server.codebase系统属性确保注册报可以加载远程接口定义,注意斜杠
正确启动之后会输出:
Server ready
后台启动服务器会一直运行,除非
kill
运行客户端
java -classpath classDir com.example.hello.Client
输出应该是
response: Hello world!