2. RMI的jdk的实现
来源:互联网 发布:linux rpm下载 编辑:程序博客网 时间:2024/06/05 15:20
一. RMI总体思路 :
- 服务端把远程对象的存根注册到jndi容器
- 远程对象实现的接口继承Remote : remote接口内无任何内容, 只起到打标记的作用, 标记该接口的方法为远程方法, 该方法的调用为远程调用; 接收方也要存储, 远程对象继承unicastRemoteObj : 发送方存储)
- 客户端通过url找到服务端的存根对象下载下来, 客户端调用存根对象的方法就是远程调用
二.远程对象的工厂设计模式
- 优点 : 工厂设计模式解决了当服务器有多个远程对象时, 造成每个对象名称唯一, 并且有的存根对象在服务器产生后, 从未被客户端调用引起的空间浪费. 采用工厂设计模式, 服务端一开始只产生工厂远程对象, 客户端得到工厂存根对象后, 在调用远程方法, 获取想要得到的原成对象
- 步骤 :
- 让远程对象由工厂创建, 这个工厂对象也作为远程对象
- 远程对象必须直接或间接实现Remote接口, 所有远程方法抛出RemoteException
- 继承UnicastRemoteObj的类才可以导出为远程对象, UnicastRemoteObj的构造方法和exporrtRemoteObj()均能生成远程对象,并在客户端生成存根对象
- 服务端 : 注册工厂实现对象到jndi容器
- 客户端 : 让jndi容器lookup地址,生成代理对象,执行方法
- 执行步骤 :
切换到remote接口的目录下, 执行start rmiregistry开启rmi注册表 , 执行服务器,客户端程序 - [注]: 客户端调用远程方法时, 会向服务端传递参数, 这些参数必须满足以下几点:
(1) 远程方法的调用要求参数和返回值基本数据类型, 远程对象 , 可序列化的
(2) 如果参数/返回值是远程对象, 接收方得到的是发送方远程对象的存根对象
(3) 如果参数/返回值是可序列化的, 接收方接受的是对象反序列化的结果
(4) 如果参数/返回值是基本数据类型, 则接收到的是基本数据的复制品
(3) 服务端与客户端的远程对象接口/序列化类的包目录必须一样, 否则接受存根对象时, 会报classnotfoundexception
//客户端 public interface Flight extends Remote{ public String getFlightNumber() throws RemoteException; public String getOrigin() throws RemoteException; public String getDestination() throws RemoteException; public void setOrigin(String num) throws RemoteException; public void setDestination(String num) throws RemoteException;}public interface FlightFactory extends Remote{ public Flight getFlight(String number) throws RemoteException;}//继承UnicastRemoteException,让类可以导出为远程对象,构造方法和静态方法exportRemoteObj()都能导出远程对象,并在客户端生成存根对象//FlightImpl也可以成为序列化对象的模式,这样的话,客户端要持有完整的FlightImpl类public class FlightImpl extends UnicastRemoteObject implements Flight { protected String flightNum; protected String origin; protected String destination; public FlightImpl(String flightNum, String origin, String destination) throws RemoteException { super(); this.flightNum = flightNum; this.origin = origin; this.destination = destination; } public String getDestination() throws RemoteException { return this.destination; } public String getFlightNum() { return flightNum; } public void setFlightNum(String flightNum) { this.flightNum = flightNum; } public void setOrigin(String origin) { this.origin = origin; } public void setDestination(String destination) { this.destination = destination; } public String getFlightNumber() throws RemoteException { return this.flightNum; } public String getOrigin() throws RemoteException { return this.origin; }}public class FlightFactoryImpl extends UnicastRemoteObject implements FlightFactory { /* Hashtable:线程安全,key,value不允许为空 HashMap : 线程不安全,允许空 */ protected Hashtable<String,Flight> flightTable; public FlightFactoryImpl() throws RemoteException{ super(); this.flightTable = new Hashtable<String, Flight>(); } public Flight getFlight(String number) throws RemoteException { Flight f = flightTable.get(number); if(f != null) return f; f = new FlightImpl(number,null,null); flightTable.put(number, f); return f; }}public class SimpleServer { public static void main(String[] args) { try { System.setProperty("java.rmi.server.hostname","127.0.0.1"); //设置rmi服务端的主机地址,否则客户端可能找不到主机 FlightFactory ff = new FlightFactoryImpl(); Context context = new InitialContext(); context.rebind("rmi:FlightFactory", ff); System.out.println("注册工厂成功"); } catch (RemoteException e) { e.printStackTrace(); } catch (NamingException e) { System.out.println("注册jndi容器失败"); e.printStackTrace(); } }}客户端 : 保留Factory与FlightFactory接口public class SimpleClient { public static void main(String[] args) throws InterruptedException{ System.out.println("开始连接"); System.setProperty("java.security.policy", SimpleClient.class.getResource("client.policy").toString()); try { Context context = new InitialContext(); FlightFactory factory = (FlightFactory)context.lookup("rmi://127.0.0.1:1099/"+"FlightFactory"); System.out.println("连接成功"); Flight f1 = factory.getFlight("795");//客户端以获取存跟对象 f1.setOrigin("beijing"); // 发起一个远程调用 f1.setDestination("shanghai"); System.out.println("f1 从"+f1.getOrigin()+"到"+f1.getDestination()); Flight f2 = factory.getFlight("795"); System.out.println("f2 从"+f2.getOrigin()+"到"+f2.getDestination());//此处直接获取属性能得到北京->上海,说明f1,f2在服务端是一个对象 System.out.println("f1是"+f1.getClass().getName()+"实例,f2是"+f2.getClass().getName()+"实例"); System.out.println("f1==f2:"+(f1==f2)); //不等,说明f1,f2在客户端是2个对象 System.out.println("f1.equals(f2):"+(f1.equals(f2))); } catch (Exception e) { e.printStackTrace(); } Thread.sleep(1000000); }}/** 开始连接 连接成功 f1 从beijing到shanghai f2 从beijing到shanghai f1是com.sun.proxy.$Proxy1实例,f2是com.sun.proxy.$Proxy1实例 f1==f2:false f1.equals(f2):true (1)SimpleClient第一次调用factory.getFlight("795"),服务端不存在FlightImpl对象,会生成FlightImpl的存根对象(代理对象), 第二次访问时,rmi中已存在存根对象.所以他们返回的是同一个存根对象 (2)SimpleClient每次都会获得新的存根对象,这是服务端同意各类产生的两个不同对象,存根类的equals方法,只要两个存根对象由同一个类产生,就返回true*/
// client.proxy安全文件grant{ permission java.net.SocketPermission "localhost:1-1","connect";}
二.RMI的参数/返回值为可序列化对象时的客户端与服务端的传输
rmi传递可序列化对象时, 客户端接收到的是反序列化后生成的本地对象, 不再是远程对象的存根对象. 调用反序列化对象的方法, 是本地调用
服务端 : public class Flight implements Serializable{ // 修改Flight为可序列化类,作为返回值传递, FlightFactory与FlightFactoryImpl不变 protected String flightNum; protected String origin; protected String destination; public Flight(String flightNum, String origin, String destination) throws RemoteException { super(); this.flightNum = flightNum; this.origin = origin; this.destination = destination; } public String getDestination() throws RemoteException { return this.destination; } public String getFlightNum() { return flightNum; } public void setFlightNum(String flightNum) { this.flightNum = flightNum; } public void setOrigin(String origin) { this.origin = origin; } public void setDestination(String destination) { this.destination = destination; } public String getFlightNumber() throws RemoteException { return this.flightNum; } public String getOrigin() throws RemoteException { return this.origin; }}public class Server { public static void main(String[] args) { try { System.setProperty("java.rmi.server.hostname","127.0.0.1"); FlightFactory ff = new FlightFactoryImpl(); Context context = new InitialContext(); context.rebind("rmi:FlightFactory2", ff); System.out.println("jndi容器注册成功"); //start rmiregistry在根目录执行 } catch (RemoteException e) { e.printStackTrace(); } catch (NamingException e) { System.out.println("注册jndi容器失败"); e.printStackTrace(); } }}客户端 : public class Client { //Flight可序列化类和FlightFactory接口在客户端保存一份 public static void main(String[] args) { System.out.println("开始连接"); try { Context context = new InitialContext(); FlightFactory factory = (FlightFactory)context.lookup("rmi://127.0.0.1/"+"FlightFactory2"); System.out.println("连接成功"); Flight f1 = factory.getFlight("795");//factory是远程对象,发起远程方法调用 f1.setOrigin("beijing"); // Flight是序列化类,被直接反序列化生成,这个方法是本地方法调用 f1.setDestination("shanghai"); System.out.println("f1 从"+f1.getOrigin()+"到"+f1.getDestination()); Flight f2 = factory.getFlight("795");//factory是远程对象,发起远程方法调用 System.out.println("f2 从"+f2.getOrigin()+"到"+f2.getDestination()); //f2是序列化对象,发起本地方法调用,所以null->null System.out.println("f1是"+f1.getClass().getName()+"实例,f2是"+f2.getClass().getName()+"实例"); System.out.println("f1==f2:"+(f1==f2)); //不等,说明f1,f2是在客户端产生的2个对象 System.out.println("f1.equals(f2):"+(f1.equals(f2))); } catch (NamingException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } }}/** 开始连接 连接成功 f1 从beijing到shanghai f2 从null到null f1是rmi.seril.Flight实例,f2是rmi.seril.Flight实例 f1==f2:false f1.equals(f2):false*/
三. RMI回调客户端远程对象
股票显示 : 客户端注册股票到服务端, 服务端每隔一段时间更新股票报价,再调用客户端打印方法打印更新后的股票价格
//客户端 :public interface StockPrint extends Remote{ //为调用客户端的StockPrintImpl而在服务端保留的接口 public void printPrice(String stockName,Double price) throws RemoteException;}public interface StockRegistry extends Remote{ public void registStock(StockPrint sp) throws RemoteException;}public class StockRegistryImpl extends UnicastRemoteObject implements StockRegistry,Runnable{ private String[] stocks = {"百发100","恒生","深圳成股"}; protected StockPrint sp = null; protected StockRegistryImpl() throws RemoteException { super(); } public void run(){ for(;;){ try { Thread.sleep(2000); //每2s打印一次 } catch (InterruptedException e) { e.printStackTrace(); } if(this.sp!=null){ Random r = new Random(); try { sp.printPrice(stocks[r.nextInt(3)], r.nextDouble()%100+1000); } catch (RemoteException e) { e.printStackTrace(); } } } } public void registStock(StockPrint sp) throws RemoteException { this.sp = sp; //客户端远程调用该方法,传来客户端的存根对象sp }}public class Server { public static void main(String[] args) { System.setProperty("java.rmi.server.hostname","127.0.0.1"); //设置rmi服务端的主机地址,否则客户端可能找不到主机 try { StockRegistryImpl sri = new StockRegistryImpl(); Context context = new InitialContext(); context.rebind("rmi:STOCK", sri); System.out.println("jndi注册成功"); new Thread(sri).start(); //开启线程远程打印 } catch (NamingException e) { System.out.println("连接失败"); e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } }} //服务端public interface StockPrint extends Remote{ public void printPrice(String stockName,Double price) throws RemoteException;}public class StockPrintImpl extends UnicastRemoteObject implements StockPrint { protected StockPrintImpl() throws RemoteException { super(); } public void printPrice(String stockName, Double price) throws RemoteException{ System.out.println(stockName+":"+price); }}public interface StockRegistry extends Remote{ public void registStock(StockPrint sp) throws RemoteException;}public class Client { public static void main(String[] args) { try { Context context = new InitialContext(); StockRegistry sr = (StockRegistry)context.lookup("rmi://localhost/STOCK"); System.out.println("jndi连接成功"); StockPrint sp = new StockPrintImpl(); sr.registStock(sp); //执行后不会停止客户端,因为服务端持有StockPrint的存根对象,等待调用print方法 } catch (NamingException e) { System.out.println("连接失败"); e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } }}
四 . 远程对象的并发访问
远程对象的方法, 应该自行加synchronized进行同步
五. RMI的分布式垃圾回收
1. 远程对象的三种引用 : (1) 服务器的本地对象对其本地引用 (2) 服务器的rmiregistry注册表对其持有引用 (3) 客户端获取的存根对象, 对其持有远程引用2. 客户端远程引用方式 (1) 客户端接收到服务器远程对象的存根对象后, 会向服务器发送足月通知, 告知其远程引用的时间, 默认10分钟, 每达到租约通知期限的一半时间. 就会再次发出租约通知 (2) 租约时间有参数指定 : java.rmi.dgc.leaseValue (3) 当对象那实现了Unrefernce接口时,当租约期限到期, 服务端会执行unreference()方法
六. RMI框架的安全策略
- 客户端调用远程方法会带来安全隐患, 因为远程方法可能会包含有害的操作 (eg:清空C盘), 加入安全策略, 可以指定客户端访问授权的远程方法
//(1) 创建安全策略文件 :grant{ //设置访问端口1024-65535 permission java.net.SocketPermission "localhost:1024-65535","connect";}//(2) 客户端启动时加上参数 System.setProperty("java.security.policy", SimpleClient.class.getResource("client.policy").toString());
0 0
- 2. RMI的jdk的实现
- RMI的简单实现
- RMI的简单实现
- RMI的实现
- JDK 5.0以后对RMI的改进
- JDK的RMI处理远程程序调用
- JDK中rmi包下的类
- Eclipse中RMI的实现
- Java中RMI的实现
- java RMI一个例子以及模仿RMI的原理实现一个自己的RMI
- 一个简单的RMI会话实现
- 基于jxta的RMI实现技术
- JBOSS实现RMI时注意的问题
- JBOSS实现RMI时注意的问题
- rmi高可用的简单实现-zookeeper
- jdk rmi
- JDK代理的实现
- hadoop 的RMI实现分析。(请参考hadoop RMI 的源码)
- Codeforces 149D Coloring Brackets 【区间dp】
- zookeeper集群配置安装
- Java基础(一)——equals和==的区别
- js右下角定时通知提示框的实现
- git使用教程
- 2. RMI的jdk的实现
- 向每一个错误致敬——导入第三方库遇到的坑
- (转)网页头部<meta name="Robots" 用法 <meta>系列用法.
- oracle序列创建、使用、删除
- orm2 中文文档 4.3 extendsTo(一对一关系)
- python项目之 英汉词典 带GUI tkinter
- Android绘图机制(四)——使用HelloCharts开源框架搭建一系列炫酷图表,柱形图,折线图,饼状图和动画特效,抽丝剥茧带你认识图表之美
- uva272
- 用结构体变量和结构体变量的指针做参数函数