Skip to main content

🏹Java学习之RMI远程调用

·345 words·2 mins
Yalois
Author
Yalois

在学习序列化的时候发现了一个东西叫RMI,来学习一下

概念
#

远程调用 RMI(Remtoe Method Invocation) 顾名思义就是本地JVM通过网络远程调用另一个JVM上的某个方法。采用客户端/服务器通信方式(C/S)。需要在服务器上部署提供服务的远程对象,然后在客户端请求访问服务器上远程对象的方法。

RMI框架采用代理(Proxy)来负责客户端和服务端之间通信的细节。RMI框架分别生成了客户端代理和服务端代理。位于客户端的代理被称为存根(Stub),位于服务端的代理类被称为骨架(Skeleton)。

存根(Stub): 客户端调用远程对象时,实际上是通过本地代理对象(Stub)来进行的。Stub负责将方法调用转发到远程对象。

骨架(Skeleton): 位于服务端的代理对象,用于接受来自客户端的请求,并将请求转发给实际的远程对象。

使用流程
#

1.创建远程接口
#

远程接口,需要直接或者间接的继承Remote(java.rmi.Remote)接口,远程接口声明了可以被客户端访问的远程方法。而且接口中的方法都要抛出RemoteException(java.rmi.RemoteException)异常

这里实现了一个求和方法。

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

interface HelloServer extends Remote {
    public int Sum(int a,int b) throws RemoteException;
}

2.创建远程类
#

远程类用于实现远程接口。为了使远程类的实例能够提供服务,还需要把它导出为远程对象。有下面两种途径:

image-20240614213830554

  1. 子类化UnicastRemoteObject
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

class HelloServerImpl extends UnicastRemoteObject  implements HelloServer{
    //远程类的构构方法必声明抛出RemoteException异常
    public HelloServerImpl() throws RemoteException{
    }
    @Override
    public int Sum(int a, int b) throws RemoteException 	{
        return a+b;
    }
} 
  1. 调用exportObject方法(如果远程类已经继承了其他类,无法继承UnicastRemoteObject。)
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

class HelloServerImpl [extends OtherClass] implements HelloServer{ 
    public HelloServerImpl() throws RemoteException
    {
        //端口0代表监听任意一个匿名端口
        UnicastRemoteObject.exportObject(this, 0);
    }
    @Override
    public int Sum(int a, int b) throws RemoteException 	{
        return a+b;
    }
} 

3.创建服务器程序
#

RMI有一个注册表,注册表将一个名称映射到远程对象。 服务器使用注册表注册其远程对象,以便可以查找它们。 客户端想要调用远程对象上的方法时,它必须首先使用其名称查找远程对象。

启动RMI注册表有两种方式,一种是直接运行rmiregistry.exe程序,一般在jdk的bin目录下,默认绑定1099端口。运行之后再把远程对象绑定过去就行。

常用的是下面的方式,

方式一:创建注册表直接绑定

        Registry registry= null;
        try {
            //创建注册表并使用1099端口
            registry = LocateRegistry.createRegistry(1099);
            //创建远程对象
            HelloServer service=new HelloServerImpl();
            //为远程对象绑定一个Name,客户端通过name找到该远程对象。
            registry.rebind("HelloServer", service);
        } catch (RemoteException e) {
            e.printStackTrace();
        }

方式二:使用java.rmi.Naming绑定

 Registry registry=null;
        try {
            //创建注册表并使用1099端口
            registry= LocateRegistry.createRegistry(1099);
            //创建远程对象
            HelloServer service=new HelloServerImpl();
            //调用命名类服务来注册
            Naming.rebind("HelloServer",service);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

方式三:调用 JNDI API 的 javax.naming.Context 接口

       Registry registry=null;
        try {
            registry= LocateRegistry.createRegistry(1099);
            HelloServer service=new HelloServerImpl();
            Context namingContext = new InitialContext();
            namingContext.rebind("rmi:HelloServer", service);
        } catch (NamingException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }

rebind()方法会直接覆盖已经存在的名称的远程对象,而bind()方法遇到已经存在的名称的远程对象会抛出异常AlreadyBoundException。

这里用方式一写个server程序:

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class server{
    public static void main(String[] args) {
        Registry registry= null;
        try {
            //创建注册表并使用1099端口
            registry = LocateRegistry.createRegistry(1099);
            //创建远程对象
            HelloServer service=new HelloServerImpl();
            //为远程对象绑定一个Name,客户端通过name找到该远程对象。
            registry.rebind("HelloServer", service);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

4.创建客户端程序
#

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class client {
    public static void main(String[] args) throws RemoteException{
        try {
            //获取指定主机的注册表实例
            Registry registry = LocateRegistry.getRegistry("localhost", 1099);
            //通过远程注册表查找名为 "HelloServer" 的远程对象。返回远程对象的引用。
            HelloServer helloServer = (HelloServer) registry.lookup("HelloServer");
            System.out.println(helloServer.Sum(1,6));
        } catch (NotBoundException e) {
            e.printStackTrace();
        }
    }
}

执行程序返回结果7.

RMI中的参数和返回值
#

只有基本数据类型远程对象以及可序列化的对象才可以被作为参数或者返回值进行传递。

  • 传递远程对象时,实际上传递的是存根。存根充当本地代理,负责处理远程调用的细节。
  • 传递可序列化的对象,传递的是对象的序列化数据。
  • 像int、double、boolean等基本数据类型可以直接作为参数或返回值传递,因为它们在Java中是原生支持的。

JRMP
#

JRMP是Java远程方法调用(RMI)中使用的通信协议,用于在不同Java虚拟机之间传输远程调用信息。它负责将客户端的方法调用请求序列化并通过网络发送到服务器端,然后服务器端执行相应方法并将结果返回给客户端。JRMP是基于Java对象序列化的协议,适用于Java虚拟机之间的通信。

扩展
#

  • RPC是什么

参考文章
#

Java的RMI介绍及使用方法详解 | w3cschool笔记

java RMI 技术介绍和实践_rmi请求-CSDN博客

Java——RMI详解-CSDN博客

Overview (Java SE 11 & JDK 11 ) (runoob.com)

Java RMI学习与解读(一) - Zh1z3ven - 博客园 (cnblogs.com)

UnicastRemoteObject (Java SE 11 & JDK 11 ) (runoob.com)

Java 网络编程 —— RMI 框架 - 低吟不作语 - 博客园 (cnblogs.com)

编辑日期:2024.06.14

如果内容有错误的地方请联系我。