2. RMI的jdk的实现

来源:互联网 发布:linux rpm下载 编辑:程序博客网 时间:2024/06/05 15:20

一. RMI总体思路 :

  1. 服务端把远程对象的存根注册到jndi容器
  2. 远程对象实现的接口继承Remote : remote接口内无任何内容, 只起到打标记的作用, 标记该接口的方法为远程方法, 该方法的调用为远程调用; 接收方也要存储, 远程对象继承unicastRemoteObj : 发送方存储)
  3. 客户端通过url找到服务端的存根对象下载下来, 客户端调用存根对象的方法就是远程调用

二.远程对象的工厂设计模式

  1. 优点 : 工厂设计模式解决了当服务器有多个远程对象时, 造成每个对象名称唯一, 并且有的存根对象在服务器产生后, 从未被客户端调用引起的空间浪费. 采用工厂设计模式, 服务端一开始只产生工厂远程对象, 客户端得到工厂存根对象后, 在调用远程方法, 获取想要得到的原成对象
  2. 步骤 :
  • 让远程对象由工厂创建, 这个工厂对象也作为远程对象
  • 远程对象必须直接或间接实现Remote接口, 所有远程方法抛出RemoteException
  • 继承UnicastRemoteObj的类才可以导出为远程对象, UnicastRemoteObj的构造方法和exporrtRemoteObj()均能生成远程对象,并在客户端生成存根对象
  • 服务端 : 注册工厂实现对象到jndi容器
  • 客户端 : 让jndi容器lookup地址,生成代理对象,执行方法
  1. 执行步骤 :
    切换到remote接口的目录下, 执行start rmiregistry开启rmi注册表 , 执行服务器,客户端程序
  2. [注]: 客户端调用远程方法时, 会向服务端传递参数, 这些参数必须满足以下几点:
    (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
原创粉丝点击