RMI使您可以以更高的抽象级别编程。它隐藏了套接字服务器,套接字,连接和发送或接收数据的详细信息。它甚至实现了引擎盖下的多线程服务器,而使用套接级编程,则必须明确地实现用于处理多个客户端的线程。 RMI应用程序是可扩展且易于维护的。您可以更改RMI服务器或将其移动到另一台计算机,而无需修改客户端程序,只是重置URL以找到服务器。 (为避免重置URL,您可以修改客户端将URL传递为命令行参数。)在套接级编程中,发送数据的客户端操作需要服务器操作来读取它。在套接字级别的客户端和服务器的实现是紧密同步的。 RMI客户端可以直接调用服务器方法,而插座级编程仅限于传递值。套接字级编程非常原始。避免使用它来开发客户端/服务器应用程序。作为一个类比,套接字级编程就像汇编语言的编程一样,而RMI编程就像以高级语言编程。

本地对象是只能在本地主机内访问。可从远程主机访问的对象称为 远程对象。对于要远程调用的对象,它必须在 Java 接口中定义 服务器和客户端都可以访问。此外,该接口必须扩展 java.lang.rmi.Remote 接口。像 java.io.Serializable 接口,java.rmi.Remote 是一个不包含常量或方法的标记接口。它仅用于识别远程 对象。

image-20211105171220449

image-20211106125338311

普通RMI 调用

客户端如何找到远程对象?RMI注册表提供了服务器的注册表服务,用于注册对象并为客户端找到对象。

您可以在Locateregistry类中使用几个超载的静态getRegisty()方法来返回对注册表的引用,

一旦获得了注册表,您可以使用绑定或Rebind方法在注册表中使用唯一名称绑定对象或使用查找方法找到对象

public:
    Registry: 可在第三方启动,也可在Server启动, 区别是创建还是获取Registry注册中心
    LocateRegistry.createRegistry(65532); //启动注册中心
    CS共有一个接口 
Server:
    Registry registry = LocateRegistry.getRegistry("127.0.0.1", 65532);//获取注册中心
    服务端特有接口的实现类,new一个出来以后包装成Remte对象
    var proxy=  UnicastRemoteObject.exportObject(simply, 1985);
    bind/rebind/unbind/  到key   

Client:
    Registry registry = LocateRegistry.getRegistry("127.0.0.1", 65532);
    list/lookup key得到代理类转为接口,调用接口方法

image-20211107223557870

//client
    //获取到注册中心
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 65532);
    //搜索key-对应的 被代理的接口实现对象
var proxy = (Simple) registry.lookup("key");
    //通过这个代理的接口实现对象 调用接口方法
System.out.println(proxy.say());
//-----------------------------------------
//server
    //创建注册中心(也可以获取第三方注册中心)
var registry=LocateRegistry.createRegistry(65532);
    //新建一个接口实现类
Simple simply=new SimpleRealizer();
    //将这个实现类转换成代理类Remote类
var proxy= UnicastRemoteObject.exportObject(simply, 1985);
    //去注册中心将上一步的代理接口类绑定key
registry.bind("key",proxy);
//-----------------------------------------
//接口
    //继承Remote接口,方法必须抛异常
public interface Simple extends Remote {
    String say() throws RemoteException;
}
//接口实现类
public class SimpleRealizer implements Simple {
    @Override
    public String say() throws RemoteException {
        return "say:hello";
    }
}

客户端调用 stub 的 say() 方法背后的流程:
client 端通过 stub 中包含的 host、port 信息,与 remote object 所在的 server 建立连接 ,然后序列化调用数据
server 端接收调用请求,将调用转发给 remote object,然后序列化结果,返回给 client
client 端接收、反序列化结果

注意事项

cs 共知接口 继承Remote 方法必须抛异常  throws RemoteException
(??此条待定)cs 注册的类必须同包名,不然找不到
c 参数 和 返回值,如果是Object ,必须手动实现序列化接口 implements Serializable 

一句话总结

tips:正常 rmi 是 共知接口1,server有接口1的实现类,将实现类与key绑定,然后被client 获取并调用代理接口实现类的接口1中的已知方法

RMI回调

​ 传统Rmi只能是客户端调用服务端的方法,回调则提供了一种服务器端可以调用客户端方法的办法,具体如下:image-20211107223610637

public:
    接口1和接口2 由 cs 共同知晓
    c有接口2的实现类,s有接口1的实现类
    接口1中有方法将接口2作为参数
C:
    new接口2的实现类       I2 cl=new I2implenter();
    拿到接口1的代理对象后   调用代理类的特殊传参(参数为接口2)方法
    int result =proxy.pass(cl);
    得到s端 接口2实现类 实现的方法返回值,也可能没有返回值
S:  无特殊
//接口1
public interface I1 extends Remote {
    String say() throws RemoteException;
    int pass(I2 temp) throws RemoteException;
}
//-----------------------------------------
//接口1实现类(s端特有)
public class I1implenter implements I1{
    @Override
    public String say() throws RemoteException {
        return "hello";
    }
    @Override
    public int pass(I2 temp) throws RemoteException {
        //此时可以自由使用接口2实现类的方法
        //使用完后返回pass方法的返回值给client
        return temp.sum(3,4);
    }
}
//-----------------------------------------
//接口2
public interface I2 extends Remote {
    int sum(int a,int b) throws RemoteException;
}
//-----------------------------------------
//接口2实现类(c端特有)
public class I2implenter implements I2, Serializable {
    @Override
    public int sum(int a, int b) throws RemoteException {
        return a+b;
    }
}
//-----------------------------------------
//client
 Registry registry = LocateRegistry.getRegistry("127.0.0.1", 65532);
 var proxy = (I1) registry.lookup("key");
 System.out.println(proxy.say());
 I2 cl=new I2implenter();
 int result =proxy.pass(cl);
 System.out.println("result="+result);
//server
 var registry=LocateRegistry.createRegistry(65532);
 I1 simply=new I1implenter();
 var proxy=  UnicastRemoteObject.exportObject(simply, 1985);
 registry.bind("key",proxy);

注意事项

接口2实现类必须实现序列化接口