一、简介

java RMI 指的是远程方法调用(Remote Method Invocation),是java原生支持的远程调用,采用JRMP(Java Remote Messaging Protocol)作为通信协议可以认为是纯java版本的分布式远程调用解决方案,RMI主要用于不同虚拟机之间的通信,这些虚拟机可以在不同的主机上、也可以在同一个主机上,这里的通信可以理解为一个虚拟机上的对象调用另一个虚拟机对象上的方法。

客户端

  • 存根/桩(Stub):远程对象在客户端上的代理
  • 远程引用层(Remote Reference Layer):解析并执行远程引用协议
  • 传输层(Transport):发送调用、传递远程方法参数、结束远程方法执行结果

服务端

  • 骨架(Skeleton):读取客户端传递的方法参数,调用服务器方的实际对象方法,并接收方法执行后的返回值
  • 远程引用层(Remote Reference Layer):处理远程引用后向骨架发送远程方法调用
  • 传输层(Transport):监听客户端的入站连接,接收并转发调用到远程引用层

注册表(Registry)

以URL形式注册远程对象,并向客户端回复对远程对象的引用

远程调用过程

1、客户端从远程服务器的注册表中查询并获取远程对象引用

2、桩对象与远程对象具有相同的接口和方法列表,当客户端调用远程对象时,实际上是由相应的桩对象代理完成的

3、远程引用层在将桩的本地引用转换为服务器上对象的远程引用后,再将调用转递给传输层(Transport),由传输层通过TCP协议发送调用

4、在服务器端,传输层监听入站连接,他一旦接收到客户端远程调用后,就将这个引用转发给其上层的远程引用层

5、服务端的远程引用层将客户端发送的远程引用转换为本地虚拟机的引用后,再将请求传递给骨架(Skeleton)

6、骨架读取参数,又将请求传递给服务器,最后由服务器进行实际的方法调用

结果返回过程

1、如果远程方法调用后有返回值,则服务器将这些结果又沿着骨架 --> 远程引用层 --> 传输层向下传递,

2、客户端的传输层接收到返回值后,又沿着传输层 --> 远程引用层 --> 桩向上传输,然后由桩来反序列化这些返回值,并将最终的结果传递给客户端程序

image-20220505191508031

二、代码实现

服务端:

1、定义Remote子接口,在其内部定义要发布的远程方法,并且这些方法都要Throws RemoteException

2、定义实现远程接口,并且继承: UnicastRemoteObject

3、启动服务器:依次完成注册表的启动和远程对象绑定

1、创建远程接口

import java.rmi.Remote;
import java.rmi.RemoteException;

//定义Remote子接口
//内部定义要发布的远程方法
public interface IHelloService extends Remote {
    public  String sayHello(User user) throws RemoteException;
}

2、实现远程接口

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

//实现远程接口
public class HelloServiceImpl extends UnicastRemoteObject implements IHelloService {
    @Override
    public String sayHello(User user) throws RemoteException {
        System.out.println("this is server , say hello to "+user.getUsername());
        return "success";
    }
    public HelloServiceImpl() throws RemoteException {
        super();
    }
}

3、远程调用的对象

public class User implements Serializable {
    private String username;
    private int age;

    public User() {
    }

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }

  ...getter 和 setter 省略
}

4、服务端启动类

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class RMIServer {
    public static void main(String[] args) throws RemoteException, MalformedURLException, AlreadyBoundException {
        // 创建一个远程对象,同时也会创建stub对象、skeleton对象
        IHelloService service = new HelloServiceImpl();

        // 本地主机上的远程对象注册表Registry的实例,并指定端口为8888,这一步必不可少(Java默认端口是1099),
        // 必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上
        LocateRegistry.createRegistry(8888);

        //对象的绑定
        //bind方法的参数1:   rmi://ip地址:端口/服务名   参数2:绑定的对象
        //绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略)
        Naming.bind("//127.0.0.1:8888/rmiserver",service);
    }
}

客户端:

1、通过符合JRMP规范的URL字符串在注册表中获取并强转成Remote子接口对象

2、调用这个Remote子接口对象中的某个方法就是为一次远程方法调用行为

public class RMIClient {
    public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
        //1.从注册表中获取远程对象 , 强转
        IHelloService service = (IHelloService) Naming.lookup("//127.0.0.1:8888/rmiserver");

        //2.准备参数
        User user = new User("laowang",18);

        //3.调用远程方法sayHello
        String message = service.sayHello(user);
        System.out.println(message);
    }
}

三、结果返回

image-20220505193345049

先启动服务端,然后启动客户端,成功调用远程对象方法

123

image-20220505192020144