JavaRMI入门详细

2023-11-19

Java RMI入门

定义:

**RMI: **

远程方法调用(Remote Method Invocation),它支持存储在不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。

Java RMI:

1、用于不同虚拟机之间的通信

2、这些虚拟机可以在不同的主机上、也可以在同一个主机上;

3、一个虚拟机中的对象调用另一个虚拟上中的对象的方法,只不过是允许被远程调用的对象要通过一些标志加以标识

  • 优点:避免重复造轮子;

  • 缺点:调用过程很慢,而且该过程是不可靠的,容易发生不可预料的错误,比如网络错误等;

从方法调用角度来看,RMI要解决的问题

是让客户端对远程方法的调用可以相当于对本地方法的调用而屏蔽其中关于远程通信的内容,即使在远程上,也和在本地上是一样的。

从客户端-服务器模型来看,客户端程序直接调用服务端,两者之间是通过JRMP( Java Remote Method Protocol)协议通信,这个协议类似于HTTP协议,规定了客户端和服务端通信要满足的规范。

实际上,客户端只与代表远程主机中对象的Stub对象进行通信,丝毫不知道Server的存在。

客户端只是调用Stub对象中的本地方法,Stub对象是一个本地对象,它实现了远程对象向外暴露的接口,也即它的方法和远程对象暴露的方法的签名是相同的。

客户端认为它是在调用远程对象的方法,实际上是调用Stub对象中的方法。可以理解为Stub对象是远程对象在本地的一个代理,当客户端调用方法的时候,Stub对象会将调用通过网络传递给远程对象。

java 1.2之前,与Stub对象直接对话的是Skeleton对象,在Stub对象将调用传递给Skeleton的过程中,其实这个过程是通过JRMP协议实现转化的,通过这个协议将调用从一个虚拟机转到另一个虚拟机。

java 1.2之后,与Stub对象直接对话的是Server程序,不再是Skeleton对象了。

所以从逻辑上来看,数据是在Client和Server之间横向流动的,但是实际上是从Client到Stub,然后从Skeleton到Server这样纵向流动的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UfWEQVoB-1640164643251)(Java网络复习.assets/SouthEast)]

RMI远程调用步骤

RMI交互图

在这里插入图片描述

存根和骨干网的具体通信过程

在这里插入图片描述

方法调用从客户对象-经-存根(stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向下,传递给主机,然后再次经传输层,向上穿过远程调用层和骨干网(Skeleton),到达服务器对象。

存根扮演着远程服务器对象代理的角色,使该对象可被客户激活。

远程调用层处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。
传输层管理实际的连接,并且追踪可以接受方法调用的远程对象。
骨干网完成对服务器对象实际的方法调用,并获取返回值。
返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,存根获得返回值。

RMI由3个部分构成

第一个是rmiregistry(JDK提供的一个可以独立运行的程序,在bin目录下)

第二个是server端的程序,对外提供远程对象

第三个是client端的程序,想要调用远程对象的方法。

步骤

首先,先启动rmiregistry服务,启动时可以指定服务监听的端口,也可以使用默认的端口(1099)。

其次,server端在本地先实例化一个提供服务的实现类,然后通过RMI提供的Naming/Context/Registry(下面实例用的Registry)等类的bind或rebind方法将刚才实例化好的实现类注册到rmiregistry上并对外暴露一个名称。

最后,client端通过本地的接口和一个已知的名称(即rmiregistry暴露出的名称)再使用RMI提供的Naming/Context/Registry等类的lookup方法从RMIService那拿到实现类。这样虽然本地没有这个类的实现类,但所有的方法都在接口里了,便可以实现远程调用对象的方法了。

数据传递

Java程序中引用类型(不包括基本类型)的参数传递是按引用传递的,对于在同一个虚拟机中的传递时是没有问题的,因为的参数的引用对应的是同一个内存空间,在分布式系统中,由于对象不存在于同一个内存空间,虚拟机A的对象引用对于虚拟机B没有任何意义,那么怎么解决这个问题呢?

第一种

将引用传递更改为值传递也就是将对象序列化为字节,然后使用该字节的副本在客户端和服务器之间传递,而且一个虚拟机中对该值的修改不会影响到其他主机中的数据;

但是对象的序列化也有一个问题,就是对象的嵌套引用就会造成序列化的嵌套,这必然会导致数据量的激增,因此我们需要有选择进行序列化。

在Java中一个对象如果能够被序列化,需要满足下面两个条件之一:
–是Java的基本类型;
–实现java.io.Serializable接口(String类即实现了该接口);

对于容器类,如果其中的对象是可以序列化的,那么该容器也是可以序列化的;
可序列化的子类也是可以序列化的;

第二种:

使用引用传递,每当远程主机调用本地主机方法时,该调用还要通过本地主机查询该引用对应的对象,在任何一台机器上的改变都会影响原始主机上的数据,因为这个对象是共享的;

RMI中的参数传递和结果返回可以使用的三种机制(取决于数据类型):

​ 简单类型:按值传递,直接传递数据拷贝;
​ 远程对象引用(实现了Remote接口):以远程对象的引用传递;
​ 远程对象引用(未实现Remote接口):按值传递,通过序列化对象传递副本,本身不允许序列化的对象不允许传递给远程方法;

在调用远程对象的方法之前需要一个远程对象的引用,如何获得这个远程对象的引用在RMI中是一个关键的问题,如果将远程对象的发现类比于IP地址的发现可能比较好理解一些。

平常我们上网是通过域名来定位一个网站,实际上网络是通过IP地址来定位网站,因此其中就存在一个映射的过程,域名系统(DNS)就是为了这个目的出现的,在域名系统中通过域名来查找对应的IP地址来访问对应的服务器。

对应的,IP地址在这里就相当于远程对象的引用,而DNS则相当于一个注册表(Registry)

而域名在RMI中就相当于远程对象的标识符,客户端通过提供远程对象的标识符访问注册表,来得到远程对象的引用。这个标识符是类似URL地址格式的,它要满足的规范如下:

该名称是URL形式的,类似于http的URL,schema是rmi;
格式类似于rmi://host:port/name,host指明注册表运行的注解,port表明接收调用的端口,name是一个标识该对象的简单名称
主机和端口都是可选的,如果省略主机,则默认运行在本地;如果端口也省略,则默认端口是1099;

入门实战

基本

实现RMI所需的API几乎都在:

java.rmi:提供客户端需要的类、接口和异常;
java.rmi.server:提供服务端需要的类、接口和异常;
java.rmi.registry:提供注册表的创建以及查找和命名远程对象的类、接口和异常;

其实在RMI中的客户端和服务端并没有绝对的界限,与Web应用中的客户端和服务器还是有区别的。这两者其实是平等的,客户端可以为服务端提供远程调用的方法,这时候,原来的客户端就是服务器端。

实战:客户端调用服务器端的对象更新User的信息
1、entity

该对象必须实现Serializable接口,否则在调用过程中,会抛出NotSerializableException异常

public class User implements Serializable {
    // 服务端 客户端的serialVersionUID字段数据要保持一致
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    ... 
2、远程调用对象

远程接口

/**
 * Author:甲粒子
 * Date: 2021/12/22
 * Description:远程接口,该接口需要继承Remote接口,并且接口中的方法全都要抛出RemoteException异常
 */
public interface IUpdateUser extends Remote {
    User updateUser(User u) throws RemoteException;
}

接口实现

import com.zbz.rmi.entity.User;
import com.zbz.rmi.interfaceImpl.IUpdateUser;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
 * Author:甲粒子
 * Date: 2021/12/22
 * Description:远程接口实现类,必须继承UnicastRemoteObject
 * (继承RemoteServer->继承RemoteObject->实现Remote,Serializable),
 * 只有继承UnicastRemoteObject类,才表明其可以作为远程对象,被注册到注册表中供客户端远程调用
 * (补充:客户端lookup找到的对象,只是该远程对象的Stub(存根对象),
 * 而服务端的对象有一个对应的骨架Skeleton(用于接收客户端stub的请求,以及调用真实的对象)对应,
 * Stub是远程对象的客户端代理,Skeleton是远程对象的服务端代理,
 * 他们之间协作完成客户端与服务器之间的方法调用时的通信。)
 */
public class UpdateUserImpl extends UnicastRemoteObject implements IUpdateUser {
    private static final long serialVersionUID = 1L;

    /**
     * 因为UnicastRemoteObject的构造方法抛出了RemoteException异常,因此这里默认的构造方法必须写,
     * 也必须声明抛出RemoteException异常
     *
     * @throws RemoteException
     */

    public UpdateUserImpl() throws RemoteException {
    }

    @Override
    public User updateUser(User u) throws RemoteException {
        System.out.println("-----客户端发送的user-----"+u.toString());
        //更新u
        u.setName(u.getName()+"_更新");
        u.setAge(u.getAge()*2);
        return u;
    }
}
3、Server
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

/**
 * Author:甲粒子
 * Date: 2021/12/22
 * Description:服务端启动类,用于创建远程对象注册表以及注册远程对象
 */
public class Server {
    public static void main(String[] args) throws Exception {
        try{
            // 本地主机上的远程对象注册表Registry的实例,
            // 并指定端口为8888,这一步必不可少(Java默认端口是1099)
            LocateRegistry.createRegistry(8888);
            //创建一个远程对象
            IUpdateUser rUpdateUser=new UpdateUserImpl();
            //把远程对象注册到RMI注册服务器上,命名为 rUpdate
            //绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
            // Naming.bind("//localhost:8888/rUpdate",rUpdateUser);
            Naming.bind("rmi://localhost:8888/rUpdate",rUpdateUser);
            System.out.println("------------远程对象IUpdateUser注册成功,等待客户端调用...");
        }catch (RemoteException e) {
            System.out.println("创建远程对象发生异常!");
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            System.out.println("发生重复绑定对象异常!");
            e.printStackTrace();
        } catch (MalformedURLException e) {
            System.out.println("发生URL畸形异常!");
            e.printStackTrace();
        }

    }
}
4、Client
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

/**
 * Author:甲粒子
 * Date: 2021/12/22
 * Description:该类为客户端启动类,用于在注册表中查找远程对象实现远程方法调用,
 */
public class Client {
    public static void main(String[] args) {
        try{
            // 在RMI服务注册表中查找名称为rUpdate的对象,并调用其上的方法
            IUpdateUser rUpdateUser = (IUpdateUser) Naming.lookup("rmi://localhost:8888/rUpdate");
            //构造User对象,测试远程对象传输
            User user = new User("甲粒子",20);
            System.out.println("-------------- 服务端返回的的user为" + rUpdateUser.updateUser(user).toString());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (NotBoundException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}
5、运行结果

在这里插入图片描述

在这里插入图片描述

说明:

Java 1.4及 以前的版本中需要手动建立Stub对象,通过运行rmic命令来生成远程对象实现类的Stub对象,但是在Java 1.5之后可以通过动态代理来完成,不再需要这个过程了。

核心代码

Naming.bind("rmi://localhost:8888/rUpdate",rUpdateUser); ,通过一个名称rUpdate映射到该远程对象的引用,客户端通过该名称获取该远程对象的引用。

Naming.lookup(...)获取该远程对象的引用。这个方法通过一个指定的名称来获取,该名称必须与远程对象服务器绑定的名称一致。可以通过Naming.list(...)方法列出所有可用的远程对象。

客户端的引用类型的serialVersionUID字段要与服务器端的对象保持一致

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaRMI入门详细 的相关文章

  • 按下按钮并在java中的新窗口中打开文件

    我创建了一个 JFrame 并放置了一个文本字段和按钮 在文本字段中我放置了从文本文件读取的名称 我知道我想单击按钮并打开一个已知窗口 我想在其中放置名称 其他信息来自同一个文件 这是我的代码 这是我的主框架 package Fronten
  • TreeMap 删除所有大于某个键的键

    在项目中 我需要删除键值大于某个键的所有对象 键类型为Date 如果重要的话 据我所知TreeMapJava中实现的是红黑树 它是一种二叉搜索树 所以我应该得到O n 删除子树时 但除了制作尾部视图并一一删除之外 我找不到任何方法可以做到这
  • java inputstream 打印控制台内容

    sock new Socket www google com 80 out new BufferedOutputStream sock getOutputStream in new BufferedInputStream sock getI
  • 如何在 Spring 中使 @PropertyResource 优先于任何其他 application.properties ?

    我正在尝试在类路径之外添加外部配置属性资源 它应该覆盖任何现有的属性 但以下方法不起作用 SpringBootApplication PropertySource d app properties public class MyClass
  • Sun 在 EDT 之外做 GUI 工作的演示?

    我正在看SplashDemo java http download oracle com javase tutorial uiswing examples misc SplashDemoProject src misc SplashDemo
  • 将人类日期(当地时间 GMT)转​​换为日期

    我正在服务器上工作 服务器正在向我发送 GMT 本地日期的日期 例如Fri Jun 22 09 29 29 NPT 2018在字符串格式上 我将其转换为日期 如下所示 SimpleDateFormat simpleDateFormat ne
  • 如何使用 JMagick 转换色彩空间?

    如何使用 JMagick API 转换色彩空间 例如 CMYK gt RGB 和 RGB gt CMYK None
  • 如何在.NET中使用java.util.zip.Deflater解压缩放气流?

    之后我有一个转储java util zip Deflater 可以确认它是有效的 因为 Java 的Inflater打开它很好 并且需要在 NET中打开它 byte content ReadSample sampleName var inp
  • 提高 PostgreSQL 1 亿数据左连接查询性能

    我在用Postgresql 9 2 version Windows 7 64 bit RAM 6GB 这是一个Java企业项目 我必须在我的页面中显示订单相关信息 有三个表通过左连接连接在一起 Tables TV HD 389772 行 T
  • 在Java中运行bat文件并等待

    您可能会认为从 Java 启动 bat 文件是一项简单的任务 但事实并非如此 我有一个 bat 文件 它对从文本文件读取的值循环执行一些 sql 命令 它或多或少是这样的 FOR F x in CD listOfThings txt do
  • Java继承,扩展类如何影响实际类

    我正在查看 Sun 认证学习指南 其中有一段描述了最终修饰符 它说 如果程序员可以自由地扩展我们所知的 String 类文明 它可能会崩溃 他什么意思 如果可以扩展 String 类 我是否不会有一个名为 MyString 的类继承所有 S
  • 使用 Elastic Beanstalk 进行 Logback

    我在使用 Elastic Beanstalk 记录应用程序日志时遇到问题 我正在 AWS Elastic Beanstalk 上的 Tomcat 8 5 with Corretto 11 running on 64bit Amazon Li
  • Java Swing - 如何禁用 JPanel?

    我有一些JComponents on a JPanel我想在按下 开始 按钮时禁用所有这些组件 目前 我通过以下方式显式禁用所有组件 component1 setEnabled false 但是有什么办法可以一次性禁用所有组件吗 我尝试禁用
  • Spring @Cacheable 和 @Async 注解

    我需要缓存一些异步计算的结果 具体来说 为了克服这个问题 我尝试使用 Spring 4 3 缓存和异步计算功能 作为示例 我们采用以下代码 Service class AsyncService Async Cacheable users C
  • 手动设置Android Studio的JDK路径

    如何为 Android Studio 使用自定义 JDK 路径 我不想弄乱 PATH 因为我没有管理员权限 是否有某个配置设置文件允许我进行设置 如果您查看项目设置 您可以从那里访问 jdk 在标准 Windows 键盘映射上 您可以在项目
  • Hibernate 本机查询 - char(3) 列

    我在 Oracle 中有一个表 其中列 SC CUR CODE 是 CHAR 3 当我做 Query q2 em createNativeQuery select sc cur code sc amount from sector cost
  • 在java中以原子方式获取多个锁

    我有以下代码 注意 为了可读性 我尽可能简化了代码 如果我忘记了任何关键部分 请告诉我 public class User private Relations relations public User relations new Rela
  • Java 正则表达式中的逻辑 AND

    是否可以在 Java Regex 中实现逻辑 AND 如果答案是肯定的 那么如何实现呢 正则表达式中的逻辑 AND 由一系列堆叠的先行断言组成 例如 foo bar glarch 将匹配包含所有三个 foo bar 和 glarch 的任何
  • Java 11 - 将 Spring @PostConstruct 替换为 afterPropertiesSet 或使用 initMethod

    我正在使用 spring 应用程序 有时会使用 PostConstruct用于代码和测试中的设置 看来注释将被排除在外Java 11 https www baeldung com spring postconstruct predestro
  • Java 和/C++ 在多线程方面的差异

    我读过一些提示 多线程实现很大程度上取决于您正在使用的目标操作系统 操作系统最终提供了多线程能力 比如Linux有POSIX标准实现 而windows32有另一种方式 但我想知道编程语言水平的主要不同 C似乎为同步提供了更多选择 例如互斥锁

随机推荐

  • Ubuntu/Win10双系统安全删除Ubuntu的方法

    为什么要删除Ubuntu 现在 许多筒子喜欢在电脑上安装双系统 Windows Linux Linux系统中最受个人用户用户青睐的当属Ubuntu了 我们常常在Ubuntu上写程序 调代码 做开发 然而 有些时候我们因为各种各样的原因 不得
  • vscode插件开发踩坑

    vscode插件开发踩坑 q npm总是提示连接错误并且切换源也没用 a 卸载重装 卸载干净 usr 下的lib和bin有关node moudle的全删掉 q npm使用sudo时提示错误 a 首先npm不能和sudo一起用 然后因为npm
  • iOS 17 Simulator Failed with HTTP status 400:bad request

    升级 xcode 15 要 ios17 的 sdk 才能运行 但是更新这个 sdk 400 错误了 解决方案 直接去官网下载开发者后台下载dmg文件 使用命令行快速安装即可 https developer apple com documen
  • OCaml简介

    OCaml简介 函数式编程 产生于 优点 ref https zhuanlan zhihu com p 591818090 函数式编程 传统的编程语言 是面向过程 面向对象的 产生于 20世纪80 90年代 产生于法国巴黎高等师范学院 起源
  • 如何在Unity中使用AR Foundation和ARCore创建一个项目并编译到Android 11手机设备中

    最近又开始学如何使用Unity进行AR开发 因为Unity开发的AR Foundation在各种设备 例如Android iOS HoloLens 的原生AR SDK 例如ARCore ARKit Windows 10 SDK 上进行了封装
  • 服务端收发登录注冊流程

    client发包给服务分为主次id struct TCP Command WORD wMainCmdID 主命令码 WORD wSubCmdID 子命令码 一 注冊 1 当在client输入游戏帐号或游戏昵称换行时 进行验证 CS 1 1
  • 《基于spyglass同步设计分析和静态验证》阅读笔记

    常见的CDC问题 亚稳态 data hold数据保持的时间问题 常见的两级触发器同步 多bit信号采用简单的两级触发器同步 CDC中复杂的同步设计 亚稳态总会有概率的存在 单bit信号的CDC同步设计 慢时钟域到快时钟域的同步情况 快时钟域
  • uni-app 运行到MuMu模拟器

    文章目录 1 前言 2 实现流程 2 1 下载MuMu模拟器 2 2 配置全局 adb 2 3 运行到模拟器 2 4 模拟器调为手机版 1 前言 本文使用的模拟器为MuMu模拟器 使用逍遥模拟器会一直卡在 同步手机端程序文件完成 DClou
  • Vue+ElementUI实现从后台动态填充下拉框

    1 首先编写前端代码 将elementUI中的标签写到 vue界面中
  • STM32在休眠模式(Stop/Standby)模式下的关闭看门狗问题的解决

    长期以来一直都认为Stm32启用了IWDG看门狗以后 就不能再使用休眠进入低功耗模式 由于看门狗启动后就不能停止 给很多人带来了困扰 还有很多人放弃了使用看门狗 从而给产品带来一定的不确定性 其实有一个简单的方法可以实现在有看门狗的情况下可
  • Python3:我只用1行代码就下载全网视频,我被我的才华和颜值征服了!!

    you get库使用 1 引言 2 代码实战 2 1 you get介绍 2 2 you get安装 2 3 you get下载视频 2 3 1 指定存储和重命名 2 3 2 查看视频信息 2 3 3 选择需要下载清晰度和格式 2 4 yo
  • 关于华硕飞行堡垒安装Ubuntu时的卡死和 grub引导问题的解决办法

    今天给同学装双系统时发现网上好多博客都不能用 然后结合前辈的一些优点和自己使用deepin和Ubuntu时的感悟 解决了他们两个堡垒关于grub引导和Ubuntu启动时卡死的问题 总结如下 1 关于U盘安装时进不去引导时的解决办法 进入gr
  • 《0基础学安卓逆向》第1集:什么是安卓逆向及环境搭建

    1 安卓逆向是什么 安卓逆向是什么 目前百度知道都没有收录安卓逆向这个词条 大部分能搜索到的资料都是技术人员写的偏技术类的文章 往往充斥着代码和各类工具集合 非技术人员一看就比较懵逼 简单地来说 安卓逆向是对已经打包好的APP进行反编译 源
  • psycopg2中copy_to /copy_from/copy_expert的使用方法

    psycopg2是一个用于在Python中连接和操作PostgreSQL数据库的库 它提供了三个方法copy from copy to和copy expert用于在PostgreSQL数据库中执行COPY操作 copy from方法用于从文
  • Matrix calculus(矩阵微积分)(前四节)

    原文地址 https en wikipedia org wiki Matrix calculus 注 不要把它和几何运算或者是向量运算混淆 前言 在数学中 矩阵微积分是进行多变量微积分的一种特殊符号 特别是在矩阵的空间上 它将关于许多变量的
  • C语言初识

    include
  • 2012.11.23

    刚体变换基本上告一段落 现在开始封装这一部分 以便于重用
  • 成功解决 vscode远程调试python

    welcome to my blog 微软新出的插件 非常方便远程调试 不需要改动代码 简单9步 配置远程调试环境 第一步 按ctrl shift x 输入remote development 安装 第二步 按ctrl shift p 输入
  • MFC进程退出

    MFC软件关闭 进程退出 BOOL CMainFrame DestroyWindow TODO 在此添加专用代码和 或调用基类 m bRunThreadStart 0 while m bRunThreadState m bReadyStat
  • JavaRMI入门详细

    Java RMI入门 定义 RMI 远程方法调用 Remote Method Invocation 它支持存储在不同地址空间的程序级对象之间彼此进行通信 实现远程对象之间的无缝远程调用 Java RMI 1 用于不同虚拟机之间的通信 2 这