第65天:Java中的RMI(远程方法调用)的故事
#100daysofcode #java #distributedsystems #rmi

远程方法调用(RMI)和远程过程调用(RPC)是Java中的两个重要概念,可以进行分布式计算。 RMI和RPC均使软件组件能够通过网络相互通信,从而允许在不同的机器上执行代码,而无需手动干预。在这篇博客文章中,我们将讨论RMI和RPC是什么,它们彼此之间有何不同,他们的优势/缺点以及Java中每个的示例。

什么是RMI

在其核心,远程方法调用是一种使用Object Request Broker Architecture (ORB)通过网络连接上远程对象的方法的方式。它为开发人员提供了远程执行代码的能力,而无需对其运行的何处或如何运行 - 您需要的只是接口定义文件(.IDL文件),该文件描述了您的对象的方法签名和返回类型以及一些与安全等有关的其他信息,然后您可以像其他任何本地对象一样使用它!这里的主要好处在于它的灵活性。由于同时跨多个系统进行更改时,不需要手动编码或编译 - 而是要更新一个集中式IDL文件。

远程调用并不是什么新鲜事。多年来,C程序员使用远程过程调用(RPC)在远程主机上执行C函数并返回结果。 RPC和RMI之间的主要区别在于,RPC是C语言的分支,主要与数据结构有关。包装数据并将其运送到周围相对容易,但是对于Java来说,这还不够。在Java中,我们不仅可以使用数据结构。我们与对象一起工作,这些对象包含数据和方法用于数据。我们不仅必须能够通过电线运输对象(数据)的状态,而且收件人也必须能够在接收到它后与对象(使用其方法)交互。

现在让我们首先了解有关RMI及其工作原理的一些概念。首先,让我们对什么是远程和非远程对象

远程和非远程OBJ

在可以与RMI一起使用对象之前,必须序列化。但这还不够。 RMI中的远程对象是真实的分布式对象。顾名思义,远程对象可以是其他机器上的对象。它也可以是本地主机上的对象。一词远程表示该对象是通过可以通过网络传递的特殊对象引用来使用的。像普通的Java对象一样,远程对象通过引用传递。无论使用参考的位置,方法调用都发生在原始对象,该对象仍然存在于其原始主机上。如果远程主机将其对象之一的引用返回给您。然后,您将该对象的方法称为;实际方法调用将发生在对象所在的远程主机上。

非远程对象更简单。它们只是正常的序列化对象。 (您可以通过网络传递这些)。捕获的是,当您通过网络将非远程对象传递到简单地复制时。因此,对一个主机上对象的引用与远程主机上的对象不同。非远程对象通过副本传递(而不是引用)。

存根

存根用于实现远程对象。当您在远程对象上调用方法(可以在其他主机上)时,您实际上是在调用一些本地代码,这些代码可作为该对象的代理。这是存根。 (这被称为存根,因为它就像对象的截短占位符。)

客户端计算机上的存根对象构建了一个信息块并将此信息发送到服务器。

该块由

组成
  • 要使用的远程对象的标识符
  • 要调用的方法名称
  • 远程JVM的参数

骨骼

骨骼是另一个与原始主机上的真实物体生活在一起的代理。它从存根接收远程方法调用并将其传递到对象。

骨架对象将请求从存根对象传递到远程对象。它执行以下任务

  • 它调用服务器上存在的真实对象上的所需方法。
  • 它将从存根对象接收到的参数转发到方法。

在造物素存根和骨骼之前,我们需要创建远程对象并了解其工作原理

远程对象

首先要做的是创建一个接口,该接口将提供远程客户端可以调用的方法的描述。该接口应扩展远程接口,并且接口内的方法原型应抛出Remoteexception。

远程对象是实现特殊远程接口的对象,该对象可以远程调用该对象方法的哪些方法。远程接口必须扩展java.rmi.remote接口。您的远程对象将实现其远程接口;和自动生成的存根对象一样。然后,在代码的其余部分中,您应该将远程对象称为远程接口的实例,而不是作为其实际类的实例。因为真实对象和存根都实现了远程接口,所以就我们而言,它们是等效的(用于方法调用);在本地,我们永远不必担心我们是否有对存根或实际对象的引用。这种类型的等效性意味着我们可以使用普通语言功能,例如用远程对象铸造。当然,远程对象的公共字段(变量)无法通过接口访问,因此,如果要操纵远程对象字段,则必须制作访问者方法。

远程接口中的所有方法都必须声明它们可以抛出异常java.rmi.remoteexception。当发生任何类型的网络错误时,此例外(实际上,是Remoteexception的许多子类之一)会抛出可用。

这是定义RemoteObject行为的远程接口的一个简单示例;我们将给它一种可以远程调用的方法

public interface MessageService extends Remote {
    String send(String message) throws RemoteException;
}

当我们实现此对象时,其称为服务器的远程对象。对于客户端,RMI库将动态创建实现存根。

实施

public class MessengerServiceImpl implements MessengerService { 

    @Override 
    public String send(String message) { 
        return "Client Message".equals(message) ? "Server Message" : null;
    }
}

我们的远程对象要投掷remoteexception是很不寻常的,因为RMI库通常保留此例外,以提高向客户端的通信错误。排除在外的好处是保持我们的实施rmi agnostic。另外,在远程对象中定义的任何其他方法,但在接口中未定义,客户端仍然不可见。

创建远程实现后,我们需要将远程对象绑定到RMI注册表。

RMI注册表

注册表是RMI电话簿。您使用注册表查找对另一主机上注册的远程对象的引用。我们已经描述了如何通过远程方法调用可以来回传递远程引用。但是需要注册表来引导该过程:客户端需要某种方法来查找一些初始对象。

注册表由名为Naming的类和一个名为Rmiregistry的应用程序实现。在启动使用注册表的Java程序之前,该应用程序必须在本地主机上运行。然后,您可以创建远程对象的实例并将其绑定到注册表中的特定名称。 (将自己绑定到注册表的远程对象有时为此目的提供一个主()方法。)注册表名称可以是您选择的任何东西;它采用了斜线分离路径的形式。当客户端对象想要找到您的对象时,它将使用RMI:协议,主机名和对象名称构造特殊URL。在客户端上,RMI命名类然后与注册表进行对话并返回远程对象引用。

因此,在RMI注册表注册之前,我们需要创建一个stub

MessengerService server = new MessengerServiceImpl();
MessengerService stub = (MessengerService) UnicastRemoteObject
  .exportObject((MessengerService) server, 0);

我们使用static UnicastRemoteObject.exportObject方法来创建我们的存根实现。该存根通过基础RMI协议与服务器通信。

导出的第一个参数是远程服务器对象。第二个参数是导出将远程对象导出到注册表的端口。

给出零值表示我们不在乎哪种端口导出使用,哪些是典型的,因此动态选择。

UnicastremoteObject

远程对象的实际实现(不是我们之前讨论的接口)通常会扩展java.rmi.server.UnicastRemoteObject。这是相当于熟悉的对象类的RMI。当构建UnicastremoteObject的子类时,RMI运行时系统会自动导出,以开始侦听对象的远程接口(存根)网络连接。像java.lang.Object一样,此超类还提供了equals( )hashcode( )toString( )的实现,对远程对象很有意义。

RMI架构

RMI Architecture

现在让我们创建一个RMI注册表

创建RMI注册表

我们可以启动服务器本地注册表或作为单独的独立服务。在这里,我们将为本地创建

Registry rmiRegistry = LocateRegistry.createRegistry(1099);

这创建了一个注册表,该注册表可以用服务器绑定并由客户发现。另外,我们已经使用了createRegistry方法,因为我们正在为server创建注册表。

默认情况下,RMI注册表在Kude11端口上运行。相反,也可以在CreaterGistry Factory方法中指定其他端口。

但是,在独立的情况下,我们将调用getRegistry,将hostnameport编号作为参数。

现在,我们必须将RMIC生成的存根绑定到注册表

结合存根

RMI注册表是一个命名设施,例如JNDI等。我们可以在这里遵循类似的模式,将我们的存根绑定到唯一的钥匙:这里的MessageService

registry.rebind("MessengerService", stub);

注册RMI注册表中的存根后,我们需要创建一个客户端。

创建客户端

为此,我们首先找到RMI注册表。此外,我们将使用有界的唯一键查找远程对象存根。

最后,我们将调用发送方法:

Registry registry = LocateRegistry.getRegistry();
MessengerService server = (MessengerService) registry
  .lookup("MessengerService"); // looking for the binded stub
String responseMessage = server.send("Client Message");
String expectedMessage = "Server Message";

if (expectedMessage.equals(responseMessage)) {
    System.out.println("Connection Successful");
} else {
    System.out.println("Not able to recognize client");
}

因为我们正在本地计算机和默认端口1099上运行RMI注册表,所以我们不会传递任何参数来regrigistry。实际上,如果注册表在其他主机或不同的端口上,我们可以提供这些参数。一旦我们使用注册表查找存根对象,我们就可以在远程服务器上调用方法。