【Java基础】网络编程-RMI远程调用

文章目录

      • RMI远程调用基本概念
      • Java实现RMI远程调用
      • Java实现RMI远程调用2
      • Java的RMI远程调用弊端
      • 小结

RMI远程调用基本概念

  • Java的RMI远程调用是指,一个JVM中的代码可以通过网络实现远程调用另一个JVM的某个方法RMI是Remote Method Invocation的缩写。

  • 提供服务的一方我们称之为服务端,而实现远程调用的一方我们称之为客户端

Java实现RMI远程调用

实现一个最简单的RMI:服务器会提供一个WorldClock服务,允许客户端获取指定时区的时间,即允许客户端调用下面的方法:

	LocalDateTime getLocalDateTime(String zoneId);
  1. 要实现RMI,服务器和客户端必须共同实现同一个接口。且该接口必须继承java.rmi.Remote,并在每个方法声明抛出RemoteException

    要求:

    1. 服务端与客户端必须同时共享继承java.rmi.Remote的接口
    2. 共享接口必须继承java.rmi.Remote
    3. 共享接口所有方法必须声明抛出java.rmi.RemoteException 异常

    我们定义一个WorldClock接口,代码如下:

    public interface WorldClock extends Remote {
    	LocalDateTime getLocalDateTime(String zoneId) throws RemoteException;
    }
    

    Java的RMI规定此接口必须继承java.rmi.Remote,并在每个方法声明抛出RemoteException

  2. 编写服务端的实现类,并将服务通过RMI暴露到网络(注册远程服务)上,因为客户端请求的调用方法getLocalDateTime()最终会通过这个实现类返回结果。

    实现类WorldClockService代码如下:

    public class WorldClockService implements WorldClock {
    	@Override
    	public LocalDateTime getLocalDateTime(String zoneId) throws RemoteException {
        	return LocalDateTime.now(ZoneId.of(zoneId)).withNano(0);
    	}
    }
    

    服务端的服务相关代码就编写完毕后,我们需要通过Java RMI提供的一系列底层支持接口,把上面编写的服务以RMI的形式暴露在网络上,客户端才能调用

    public class Server {
    	public static void main(String[] args) throws RemoteException {
        	System.out.println("create World clock remote service...");
        	// 实例化一个WorldClock:
        	WorldClock worldClock = new WorldClockService();
        	// 将此服务转换为远程服务接口:
        	WorldClock skeleton = (WorldClock) UnicastRemoteObject.exportObject(worldClock, 0);
        	// 将RMI服务注册到1099端口:
        	Registry registry = LocateRegistry.createRegistry(1099);
       		 // 注册此服务,服务名为"WorldClock":
        	registry.rebind("WorldClock", skeleton);
    	}
    }
    

    上面代码是通过RMI提供的相关类,将我们自己的WorldClock实例 注册到RMI服务上RMI的默认端口是1099,最后一步注册服务时通过 rebind() 指定服务名称为"WorldClock"

  3. 编写客户端代码(RMI要求服务器和客户端共享同一个接口)即在客户端必须能够引用共享接口

    public class Client {
    	public static void main(String[] args) throws RemoteException, NotBoundException {
        	// 连接到服务器localhost,端口1099:
        	Registry registry = LocateRegistry.getRegistry("localhost", 1099);
        	// 查找名称为"WorldClock"的服务并强制转型为WorldClock接口:
        	WorldClock worldClock = (WorldClock) registry.lookup("WorldClock");
        	// 正常调用接口方法:
        	LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");
        	// 打印调用结果:
        	System.out.println(now);
    	}
    }
    
  4. 先运行服务端,再运行客户端。客户端只有接口没有实现类,因此客户端获得的接口方法返回值实际上是通过网络从服务器端获取的。

    • 整个过程实际上非常简单,对客户端来说,客户端持有的WorldClock接口实际上对应了一个“实现类”,它是Registry内部动态生成的,并负责把方法调用通过网络传递到服务端
    • 而服务端接收网络调用的服务并不是我们自己编写的WorldClockService而是Registry自动生成的代码
    • 我们把客户端的“实现类”称为stub,而服务器端的网络服务类称为skeleton它会真正调用服务端的WorldClockService获取结果,然后把结果通过网络传递给客户端
    • 整个过程由RMI底层负责实现序列化和反序列化
      【Java基础】网络编程-RMI远程调用_第1张图片

Java实现RMI远程调用2

  • 编写服务端程序
  1. 第一步: 创建远程接口
public interface WorldClock extends Remote {
	LocalDateTime getLocalDateTime(String zoneId) throws RemoteException;
}

要求:

  1. 共享接口必须继承java.rmi.Remote
  2. 所有方法必须抛出java.rmi.RemoteException 异常
  1. 第二步: 创建实现类
public class WorldClockService  extends UnicastRemoteObject  implements WorldClock {
    private static final long serialVersionUID = 1668947611852931187L;

    protected WorldClockService() throws RemoteException { }

    @Override
    public LocalDateTime getLocalDateTime(String zoneId) throws RemoteException {
        return LocalDateTime.now(ZoneId.of(zoneId)).withNano(0);
    }
}

要求:

  • 实现类必须继承java.rmi.server.UnicastRemoteObject
  • 实现类必须声明一个无参受保护的构造方法且方法声明抛出RemoteException
  1. 第三步: 注册远程服务,将服务通过uri 暴露给其它人使用
public class Server2 {
        public static void main(String[] args) throws RemoteException, MalformedURLException {
            //注册通讯端口
            LocateRegistry.createRegistry(1099);

            //注册通讯路径
            Naming.rebind("rmi://192.168.0.101:1099/WorldClock", new WorldClockService());

            System.out.println("启动服务器");
    }
}

注意:

  1. 可以注册多个服务 (可以对外暴露多个服务)
  2. URL的命名规则需要遵循,也就是和上面格式一模一样
  • 编写客户端程序
  1. 第一步: 将接口复制到客户端

    客户端必须也拥有,一个和服务端一样的接口, 并且这个接口所在的路径 ,必须和服务端的接口一模一样
    服务端

  2. 第二步: 使用服务器端暴露的接口获取数据

public class Client2 {
        public static void main(String[] args) throws MalformedURLException, RemoteException, NotBoundException {
            //通过命名空间找到 通讯服务
            WorldClock worldClock = (WorldClock)  Naming.lookup("rmi://192.168.0.101:1099/WorldClock");
            // 正常调用接口方法:
            LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");
            // 打印调用结果:
            System.out.println(now);
        }
}

执行结果:
【Java基础】网络编程-RMI远程调用_第2张图片
在这里插入图片描述

Java的RMI远程调用弊端

  1. Java的RMI严重依赖序列化和反序列化 这可能会造成严重的安全漏洞
  2. 因为Java的序列化和反序列化不但涉及到数据,还涉及到二进制的字节码即使使用白名单机制也很难保证100%排除恶意构造的字节码。
  3. 因此使用RMI时,双方必须是内网互相信任的机器服务端不要把端口暴露在公网上作为对外服务。
  4. 此外,Java的RMI调用机制决定了双方必须是Java程序,其他语言很难调用Java的RMI如果要使用不同语言进行RPC调用,可以选择更通用的协议,例如gRPC(grpc是谷歌的一个开源的rpc(远程服务调用)框架(
  1. grpc是谷歌的一个开源的rpc(远程服务调用)框架,可以让各个语言按照指定的规则通过http2协议相互调用,这个规则是用Protocol Buffer(谷歌的一个数据描述语言)写的一个.proto文件,grpc的目的就是为了让服务调用更方便。
  2. 目前支持的语言有C, C++,C#,Java, Node.js, Python,Go等,大部分语言都是通过插件根据.proto文件生成对应的代码,用生成好的代码,创建或调用grpc服务。
  3. gRPC和restful API都提供了一套通信机制,用于server/client模型通信,而且它们都使用http作为底层的传输协议(严格地说, gRPC使用的http2.0,而restful api则不一定)

小结

  1. Java提供了RMI实现远程方法调用:

  2. RMI通过自动生成stub和skeleton实现网络调用客户端只需要查找服务并获得接口实例,服务器端只需要编写实现类并注册为服务;

  3. RMI的序列化和反序列化可能会造成安全漏洞,因此调用双方必须是内网互相信任的机器,服务端不要把端口暴露在公网上作为对外服务。

你可能感兴趣的:(Java基础)