Java 网络编程 —— RMI 框架

news2025/1/10 23:28:48

概述

RMI 是 Java 提供的一个完善的简单易用的远程方法调用框架,采用客户/服务器通信方式,在服务器上部署了提供各种服务的远程对象,客户端请求访问服务器上远程对象的方法,它要求客户端与服务器端都是 Java 程序

RMI 框架采用代理来负责客户与远程对象之间通过 Socket 进行通信的细节。RMI 框架为远程对象分别生成了客户端代理和服务器端代理。位于客户端的代理必被称为存根(Stub),位于服务器端的代理类被称为骨架(Skeleton)

在这里插入图片描述

当客户端调用远程对象的一个方法时,实际上是调用本地存根对象的相应方法。存根对象与远程对象具有同样的接口。存根采用一种与平台无关的编码方式,把方法的参数编码为字节序列,这个编码过程被称为参数编组。RMI 主要采用Java 序列化机制进行参数编组。存根把以下请求信息发送给服务器:

  • 被访问的远程对象的名字
  • 被调用的方法的描述
  • 编组后的参数的字节序列

服务器端接收到客户端的请求信息,然后由相应的骨架对象来处理这一请求信息,骨架对象执行以下操作:

  • 反编组参数,即把参数的字节序列反编码为参数
  • 定位要访问的远程对象
  • 调用远程对象的相应方法
  • 获取方法调用产生的返回值或者异常,然后对它进行编组
  • 把编组后的返回值或者异常发送给客户

客户端的存根接收到服务器发送过来的编组后的返回值或者异常,再对它进行反编组,就得到调用远程方法的返回结果

JDK5.0 之后,RMI 框架会在运行时自动为运程对象生成动态代理类(包括存根和骨架类),从而更彻底地封装了 RMI 框架的实现细节,简化了 RMI 框架的使用方式


创建 RMI 应用

创建一个 RMI 应用包括以下步骤:

  • 创建远程接口:继承 java.rmi.Remote 接口
  • 创建远程类:实现远程接口
  • 创建服务器程序:负责在 RMI 注册器中注册远程对象
  • 创建客户程序:负贵定位远程对象,并且调用远程对象的方法

1. 创建远程接口

远程接口中声明了可以被客户程序访问的远程方法,并直接或间接继承 java.rmi.Remote 接口

import java.rmi.*;

public interface HelloService extends Remote {
    public String echo(String msg) throws RemoteException;
}

2. 创建远程类

远程类必须实现一个远程接口,此外,为了使远程类的实例变成能为远程客户提供服务的远程对象,可通过以下两种途径之一把它导出为远程对象:

  • 使远程类继承 java.rmi.server.UnicastRemoteObjcct 类,并且远程类的构构方法必声明抛出 RemoteException

    import java.rmi.*;
    import java.rmi.server.UnicastRemoteObjoct;
    
    public class HelloServlceImpl extends UnicagtRemoteObject implements HelloService {
        
        private String name;
        
        public HelloServicelmpl(String name) throws RemoteException {
            this.name = name;
        }
        
        public String echo(String msg) throws RemoteException {
            System.out.println(name + ":测用echo()方法");
            return "echo;" + msg + " from" + name;
        }
    }
    
  • 如果一个远程类已经继承了其他类,无法再继承 UnicastRemoteObiect 类,那么可以在构造方法中调用 UnicastRemoteObject 类的静态 expotObject 方法,同样,远程类的构造方法也必须声明抛出 RemoteException

    public class HelloServlceImpl extends OtherClass implements HelloService {
        
        private String name;
        
        public HelloServicelmpl(String name) throws RemoteException {
            this.name = name;
            //参数 port 指定监听的端口,如果取值为0,就表示监听任意一个匿名端口
            UnicagtRemoteObject.exportobject(this, 0);
        }
        
        public String echo(String msg) throws RemoteException {
            System.out.println(name + ":测用echo()方法");
            return "echo;" + msg + " from" + name;
        }
    }
    

3. 创建服务器程序

RMI 采用一种命名服务机制来使得客户程序可以找到服务器上的一个远程对象,RMI注册器提供这种命名服务。好比电话查询系统,那些希望对外公开联系方式的单位先到查询系统登记,当客户想知道某个单位的联系方式时,只需向查询系统提供单位的名字,查询系统就会返回该单位的联系方式

启动 RMI 注册器有两种方式。一种方式是直接运行 rmiregistry.exe 程序,在 JDK 的安装目录的 bin 子目录下有一个 rmiregistry.exe 程序,它是提供命名服务的注册器程序。尽管 rmiregistry 注册器程序也可以单独运行在一个主机上,但出于安全的原因,通常让 rmiregistry 注册器程序与服务器程序运行在同一个主机上

启动 RMI 注册器的另一种方式是在服务器程序中调用 java.rmiregistry.LocateRegistry 类的静态方法 createRegistry()

//默认的监听路口为1099
Registry registry = LocateRegistry.createRegigtry(1099);

向注册器注册远程对象有三种方式:

//创建远程对象
HelloService service1 = new HelloServiceImpl("service1");

//方式1:调用 java.i.registry.Registy 接口的 bind 或 rebind 方法
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind("HelloService1", service1);

//方式2:调用命名服务类 java.rmi.Naming 的 bind 或 rebind 方法
Naming.rebind("HelloService1", service1);

//方式3:调用 JNDI API 的 javax.naming.Context 接口的 bind 或rebind 方法
Context namingContext = new InitialContext();
namingContext.rebind("rmi:HelloService1", service1);

下例的 SimpleServer 类创建了两个 HelloServicelmpl 远程对象,接着创建并启动 RMI 注册器,然后把两个远程对象注册到 RMI 注册器

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

public class SimpleServer {
    
    public static void main( String args[]) {
        try {
            HelloService service1 = new HelloServiceImpl("service1");
            HelloService service2 = new HelloServiceImpl("service2");
            
            //创建并启动注册器
            Registry registry = LocateRegistry.createRegistry(1099);
            //注册远程对象
            regigtry.rebind("HelloService1", service1);
            regigtry.rebind("HelloService2", service2);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

关于向 RMI 注册器注册远程对象,需要注意的是,远程对象即使没有在注册器中注册,也可被远程访问

4. 创建客户程序

下例的 SimpleClient 类先获得远程对象的存根对象,接着调用它的远程方法

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

public class SimpleClient {
    
    public static void main(String args[]) {
        try {
            //返回本地主机的RMI注册器对象,参数port指定RMI注册器监听的端口
            Registry registry = LocateRegistry.getRegistry(1099);
            //查找对象,返回与参数name指定的名字所绑定的对象
            //返回的是一个名为"com.sun.proxy.$Proxy0"的动态代理类的实例
            HelloService service1 = (HelloService) registry.lookup("HelloService1");
            HelloService service2 = (HelloService) registry.lookup("HelloService2");
            
            System.out.println(service1.echo("hello"));
            System.out.println(service2.echo("hello"));
        }
    }
}

远程方法中的参数与返回值传递

当客户端调用服务器端的远程对象的方法时,客户端会向服务器端传递参数,服务器端则会向客户端传递返回值。RMI 规范对参数以及返回值的传递的规定如下所述:

  • 只有基本类型的数据、远程对象以及可序列化的对象才可以被作为参数或者返回值进行传递
  • 如果参数或返回值是一个远程对象,那么把它的存根对象传递到接收方。也就是说接收方得到的是远程对象的存根对象
  • 如果参数或返回值是可序列化对象,那么直接传递该对象的序列化数据。也就是说接收方得到的是发送方的可序列化对象的复制品
  • 如果参数或返回值是基本类型的数据,那么直接传递该数据的序列化数据。也就是说,接收方得到的是发送方的基本类型的数据的复制品

分布式垃圾收集

在 Java 虚拟机中,对于一个本地对象,只要不被本地 Java 虚拟机内的任何变量引用,它就会结束生命周期,可以被垃圾回收器回收。而对于一个远程对象,不仅会被本地 Java 虚拟机内的变量引用,还会被远程引用

服务器端的一个远程对象受到三种引用:

  • 服务器端的一个本地对象持有它的本地引用
  • 这个远程对象已经被注册到 RMI 注册器,可以理解为,RMI 注册器持有它的引用
  • 客户端获得了这个远程对象的存根对象,可以理解为,客户端持有它的远程引用

RMI 框架采用分布式垃圾收集机制来管理远程对象的生命周期,当一个远程对象不受到任何本地引用和远程引用时,这个远程对象才会结束生命周期,并且可以被本地 Java 虚拟机的垃圾回收器回收。

服务器端如何知道客户端持有一个远程对象的远程引用呢?当客户端获得了一个服务器端的远程对象的存根后,就会向服务器发送一条租约通知,告诉服务器自己持有这个远程对象的引用了。客户端对这个远程对象有一个租约期限,默认值为 600000ms。当至达了租约期限的一半时间,客户如果还持有远程引用,就会再次向服务器发送租约通知。客户端不断在给定的时间间隔中向服务器发送租约通知,从而使肠务器知道客户端一直持有远程对象的引用。如果在租约到期后,服务器端没有继续收到客户端的新的租约通知,服务器端就会认为这个客户已经不再持有远程对象的引用了


动态加载

远程对象一般分布在服务器端,当客户端试图调用远程对象的方法时,如果在客户端还不存在远程对象所依赖的类文件,比如远程方法的参数和返回值对应的类文件,客户就会从 java.rmi.server.codebase 系统属性指定的位贸动态加载该类文件

同样,当服务器端访问客户端的远程对象时,如果服务器端不存在相关的类文件,腐务器就会从 java.rmi.server.codebase 属性指定的位置动态加载它们

此外,当服务器向 RMI 注册器注册远程对象时,注册器也会从 java.rmi.server.codebase 属性指定的位置动态加载相关的远程接口的类文件

前面的例子都是在同一个 classpath 下运行服务器程序以及客户程序的,这些程序都能从本地 classpath 中找到相应的类文件,因此无须从 java.rmi.server.codebase 属性指定的位置动态加载类。而在实际应用中,客户程序与服务器程序运行在不同的主机上,因此当客户端调用服务器端的远程对象的方法时,有可能需要从远程文件系统加载类文件。同样,当服务器端调用客户端的远程对象的方法时,也有可能从远程文件系统加载类文件

我们可以且把这些需要被加载的类的文件都集中放在网络上的同一地方,启动时将java.rmi.server.codebase 设置为指定位置,从而实现动态加载

start java -Djava.rmi.server.codebase=http://www.javathinker.net/download/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/650083.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

提升安全性与合规性的关键工具ADAudit Plus

在当今数字化时代,企业对于安全性和合规性的要求越来越高。特别是在Active Directory(AD)域中,作为组织的核心身份验证和访问管理系统,审计活动的重要性变得前所未有。为了满足这一需求,ADAudit Plus成为了…

TwinCAT3 安装和打开项目问题记录

安装的VS版本: cn_visual_studio_professional_2015_with_update_3_x86_x64_dvd_8923256.iso 安装的TwinCAT版本: TC31-FULL-Setup.3.1.4024.29 更多的TwinCAT版本我都放在了百度云盘,有需求可以留言 4022.22 链接:https://pan.baidu.com/s/1D505FdgL7l1DAUANKb-VLg 提取…

【C++】入门基础知识详解(一)

目录 一、C关键字 二、命名空间 1、命名空间的定义 2、命名空间的使用 三、C输入&&输出 四、缺省参数 1、缺省参数的概念 2、缺省参数的分类 2.1 全缺省参数 2.2 半缺省参数 一、C关键字 我们在学习C之前,我相信大家大多数都对C语言多多少少都有所了解…

JMeter之__threadNum妙用:将接口查询结果列表按顺序赋值给各线程

使用JMeter做性能测试会遇到这么一个场景:后面的请求需要根据前面的查询列表结果通过正则表达式提取器取值后赋值,而后面用户的赋值必须是唯一的,此时该如何做? 如果按编程思维来说,这个问题并不难。只需要把前面的结…

Azkaban搭建与使用

下载最新azkaban源文件:https://github.com/azkaban/azkaban/releases 集群模式安装 1.解压 azkaban-db-3.84.4.tar.gz、 azkaban-exec-server-3.84.4.tar.gz 和 azkaban-web-server-3.84.4.tar.gz 到/opt/install/azkaban 目录下 linux>tar -zxvf azkaban-db-3…

算法:静态查找表

查找表(Search table)是由同一类型的数据元素(或记录)构成的集合。关键字(key)是数据元素中某个数据项的值,又称为键值,用它可以表示一个数据元素,也可以标识一个记录的数据项(字段&…

当深度学习撞上高性能计算,科研仿佛坐上了加速器

今天深度学习无处不在,当你打开移动终端的时候,各种APP会推荐到你喜欢的食物、你喜欢的电影,你关注的新闻热点。在生活中更是改变着我们,今天的智能语音让语言障碍破除,在预测疾病基因大数据领域预测疾病来确定药物治疗…

2023年大学生就业怎么样?双一流高校就业率仅15%,到底是咋了?

2023年,大学毕业生就业状况如何,一直是社会关注的焦点。尤其是中国的双一流高校,以其优越的教学与研究背景和实力,被众多年轻人视为就业的理想选择。 然而,在最新的统计数据中,这些一流高校的就业率却惊人…

欧科云链OKLink全新推出Onchain AML服务 助力新金融合规健康发展

据香港大公报报道,为期两天的全球高端经济峰会2023格林威治经济论坛(GreenwichEconomicForum,下称GEF论坛)于6月15日在香港交易所举办,欧科云链控股有限公司(下称“欧科云链”,股票代码:01499.HK&#xff0…

【北邮国院大三下】Logistics and Supply Chain Management 物流与供应链管理 Week4

北邮国院大三电商在读,随课程进行整理知识点。仅整理PPT中相对重要的知识点,内容驳杂并不做期末突击复习用。个人认为相对不重要的细小的知识点不列在其中。如有错误请指出。转载请注明出处,祝您学习愉快。 如需要pdf格式的文件请私信联系或…

VALSE 2023 无锡线下参会个人总结 6月12日-3

VALSE2023 无锡线下参会个人总结 6月12日-3 6月12日会议日程安排Workshop:多模态大模型与提示学习左旺孟:预训练模型和语言增强的零样本视觉学习余宙:知识增强的多模态预训练和提示学习王云鹤:多模态交织:高效模型架构…

STM32中断设置以及中断优先级设置-不含代码例程

本项目使用到的是STM32F030C8型号的MCU,我们可以从官方下载到的标准库文件中的启动汇编文件中,查看到本型号单片机的外部中断向量表。(如下图所示) 首先,我们了解一下NVIC是什么,在core_cm0.h文件中的标准库…

海川润泽“巧克力”DTU强势来袭

1、设备介绍 “巧克力”DTU(型号:HCRZ-DTU200),它可以实现远程通信、安全传输等功能,提高了传输的效率和可靠性。采用全新工业级设计,灵活应用于不同场景。此款DTU体积小(76mm31mm21.8mm)、重量轻(150g),安装简便&…

「任务派发」上线,高效管理跨平台营销任务

作为企业矩阵号相关业务的负责人,您在工作中是否遇到: 下达一个营销任务,需要在几十上百个群里沟通对接?任务发布后,无法掌握各运营者执行情况?账号数据难回收,无法检验任务完成效果&#xff1…

阿里云携手开放原子开源基金会倡议发起云原生工作委员会,两大开源项目达成捐赠意向

6月11日,2023开放原子全球开源峰会正式拉开帷幕。本届峰会以“开源赋能,普惠未来”为主题,设置了开幕式暨高峰论坛、20余场分论坛和开源成果及重点项目展览。在峰会主论坛,开放原子开源基金会理事长孙文龙协各家单位共同倡议发起云…

抖音加码自营电商,拿什么做出差异化?

今年618期间,抖音电商可谓是花尽了心思。不仅推出了补贴活动,上架了单品超值购、商城频道主题日、搜索彩蛋等玩法,而且在售后方面,还推出了“安心购”服务,可提供“七天无理由退货”“极速退款”“运费险”“过敏包退”…

关于QTreeView使用自定义Tree Model时,插入子项目时,扩展箭头不显示的问题

在使用自定义的TreeModel显示数据时,添加子项目后,扩展箭头不显示,但是点击原来扩展箭头在的地方时,树仍能够展开添加的子项目。 经过研究发现,QTreeView有以下代码: 说明如果调用的beginInserRows中的par…

Apikit 自学日记: 如何创建/生成 API 文档

在 API 研发管理产品中,几乎所有的协作工作都是围绕着 API 文档进行的。 我们在接触了大量的客户后发现,采用 文档驱动 的协作模式会比先开发、后维护文档的方式更好,团队协作效率和产品质量都能得到提高。因此我们建议您尝试基于文档来进行工…

vite预渲染 vite-plugin-prerender 大坑记录

本文部分配置转自:vite预渲染怎么实现_猿耳盗铃的博客-CSDN博客 懒得重新写,贴下版本和自己踩的各种坑吧 以下为版本,本文只给vite vue3的建议,不一定适用,因为正常情况能build成功,我昨天中午之前一直没…

JUC高级-0614

5.LockSupport与线程中断 5.1 线程中断 蚂蚁金服面试题:如何中等一个线程,如何停止一个线程什么是中断机制 首先:一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.…