1.RMI基本原理
RMI框架为远程对象分别生成了客户端代理和服务端代理。(HelloWorld的实例中可以看到客户端端的远程对象为代理类。)位于客户端的代理类称为存根STUB,服务端的代理类称为骨架SKELETON。存根与骨架通过SOCKET进行通信。服务端与客户端之间传送的方法参数或返回值,必须是远程对象(Remote接口也集成Serializable接口)、可序列化对象、基本类型。
2.分布式垃圾回收
RMI框架采用分布式垃圾收集机制(DGC)来管理远程对象的生命周期,DGC的主要规则是,只有当一个远程对象不受到任何本地引用和远程引用,这个对象才会结束生命周期,并且可以被本地Java虚拟机的垃圾回收器回收。
服务器端如何知道客户端持有一个远程对象的远程引用呢?当客户端获得了一个服务器端的远程对象的存根时,就会向服务器发送一条租约通知,告诉服务器自己持有这个远程对象的引用了。客户端对这个远程对象有一个租约期限。租约期限可通过系统属性java.rmi.dgc.leaseValue来设置。默认为10分钟。当到达了租约期限的一半时间,客户端如果还持有远程引用,就会像服务器发送租约通知。如果在租约到期后,服务器端没有继续受到客户端的新的租约通知,服务器端就会认为这个客户已经不在持有远程对象的引用了。
RMI框架管理远程对象的生命周期的过程对应用程序是透明的。远程对象希望在不受到任何引用时执行一些操作,如释放占用的相关资源,以便安全地结束生命周期,这样的远程对象需要实现java.rmi.server.Unreferenced接口。
3.一个复杂点的应用
该程序模拟股票服务端往APP端推送股票实时价格。
package com.wilian.rmi.stock.server;
import java.rmi.Remote;
import java.rmi.RemoteException;
import com.wilian.rmi.stock.client.StockQuote;
/**
* 服务端注册类,用户管理客户端的连接与释放
* @author wilian
*
*/
public interface StockQuoteRegistry extends Remote {
void registerClient(StockQuote client) throws RemoteException;
void unregisterClient(StockQuote client) throws RemoteException;
}
package com.wilian.rmi.stock.server;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.server.Unreferenced;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
import com.wilian.rmi.stock.client.StockQuote;
/**
* 服务端实现类型
* @author wilian
*
*/
public class StockQuoteRegistryImpl extends UnicastRemoteObject implements
Runnable,Unreferenced, StockQuoteRegistry {
protected HashSet<StockQuote> clients;
protected StockQuoteRegistryImpl() throws RemoteException {
this.clients=new HashSet<StockQuote>();
}
@Override
public void registerClient(StockQuote client) throws RemoteException {
clients.add(client);
System.out.println("加入一个客户端");
}
@Override
public void unregisterClient(StockQuote client) throws RemoteException {
clients.remove(client);
System.out.println("删除一个无效客户端");
}
/**
* 模拟进行股票价格的刷新
*/
@Override
public void run() {
String[] symbols=new String[]{"SUNW","DAL","MSFT","DAL","WUTK","SAMY","KATY"};
Random rand=new Random();
double[] values=new double[symbols.length];
for(int i=0;i<values.length;i++){
values[i]=25.0+rand.nextInt(100);
}
for(;;){
int sym=rand.nextInt(symbols.length);
int change=100-rand.nextInt(201);
values[sym]=values[sym]+((double)change)/100.0;
if(values[sym]<0) values[sym]=0.01;
Iterator<StockQuote> iter = clients.iterator();
while(iter.hasNext()){
StockQuote client = iter.next();
try {
client.quote(symbols[sym], values[sym]);
} catch (RemoteException e) {
System.out.println("删除一个无效的客户端");
iter.remove();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void unreferenced() {
}
}
package com.wilian.rmi.stock.client;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* 股票打印远程实现对象
* @author wilian
*
*/
public class StockQuoteImpl extends UnicastRemoteObject implements StockQuote {
protected StockQuoteImpl() throws RemoteException {}
@Override
public void quote(String stockSymbol, double price) throws RemoteException {
System.out.println(stockSymbol+":"+price);
}
}
package com.wilian.rmi.stock.client;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 股票打印接口
* @author Wilian
*
*/
public interface StockQuote extends Remote {
void quote(String stockSymbol,double price) throws RemoteException;
}
package com.wilian.rmi.stock.server;
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
/**
* 服务器端实现
* @author wilian
*
*/
public class Server {
public static void main(String[] args) {
try {
StockQuoteRegistryImpl registry = new StockQuoteRegistryImpl();
Context namingContext=new InitialContext();
namingContext.rebind("rmi:StockQuoteRegistry", registry);
//开启后台刷新线程
new Thread(registry).start();
} catch (RemoteException e) {
e.printStackTrace();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
package com.wilian.rmi.stock.client;
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import com.wilian.rmi.server.Flight;
import com.wilian.rmi.server.FlightFactory;
import com.wilian.rmi.stock.server.StockQuoteRegistry;
/**
* 客户端
* @author wilian
*
*/
public class Client {
public static void main(String[] args) {
String url="rmi://localhost/";
try {
Context namingContext = new InitialContext();
StockQuoteRegistry registry = (StockQuoteRegistry)namingContext.lookup(url+"StockQuoteRegistry");
StockQuote client =new StockQuoteImpl();
registry.registerClient(client);
} catch (NamingException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
运行截图:
从上面的例子可以看出,服务端也可以通过远程对象回调客户端。
参考资料:《Java网络编程精解》