首页 > 编程语言 >Java 网络编程 —— RMI 框架

Java 网络编程 —— RMI 框架

时间:2023-06-15 17:01:12浏览次数:68  
标签:rmi Java 对象 编程 java RMI 远程 客户端

概述

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/

标签:rmi,Java,对象,编程,java,RMI,远程,客户端
From: https://www.cnblogs.com/Yee-Q/p/17483415.html

相关文章

  • 编程不懂正则表达式,不如回家种红薯
    编程不懂正则表达式,将有被淘汰的危险编程的大量工作都是在处理字符串,如验证输入、查找子串替换、解析HTML等,而正则表达式是一个极为强大的工具,它使我们需要很多行重复啰嗦的代码才能完成的编程任务,一个表达式就可以搞定,既节省时间又节省精力。但是学习它并不是一件轻松的事情,......
  • 面向对象编程-实例属性和类属性
    在实例中绑定属性,和直接在类种绑定属性是不一样的,实例中绑定的只能在实例中用如果Student类本身需要绑定一个属性,所有实例都可以访问,那么可以这么定义classStudent(object):name='Student'点击查看代码classStudent(object):score=100#定义类属性def__in......
  • 上传自己java项目到maven中央仓库pom
    前提首先的你项目需要在Gitee或者Github上有仓库我这里以Gitee是的yhchat-sdk-core仓库为例开始在sonatype上创建问题访问sonatype注册并登录创建一个问题概要填仓库名称描述随意写写GroupId填写自己的域名,如果没有域名的话,可以自行百度使用Gitee、Github的域名需......
  • 面向对象编程-获取对象信息
    获取对象类型使用type()type(123)<class'int'>type('str')<class'str'>type(None)<type(None)'NoneType'>type(abs)<class'builtin_function_or_method'>type(a)<class'......
  • 结对编程神器:tmux
    tmux的确比Screen帅气啊!mark:https://github.com/aqua7regia/tmux-Productive-Mouse-Free-Development_zh/blob/master/book-content/Chapter5.md结对编程要求两人都能输入,如果是教学,希望client端不能写,只需要attach-session-r即可目前的疑问:tmux我设置`(反引号)为控制符,搞得我没法......
  • 视频 资料 编程 基础
    这几门CS经典课程,起飞!原创编程指北编程指北2021-05-2417:33今天给大家推荐几门,数据结构、计网、数据库、计算机组成原理、操作系统方面非常好的课程。我在大学期间,主要就是靠MOOC、B站,网易云公开课刷视频,还有就是看书,这次我把看过的觉得非常好的课程推荐给大家。话不多说,直......
  • 50基于java的智能停车场管理系统设计与实现
    ​>本章节给大家带来一个基于java的智能停车场管理系统设计与实现,可适用于java车辆管理,java停车场信息管理平台,小区停车管理平台,小区停车,物业停车管理,智慧停车场管理系统,智慧小区停车场平台,车辆AI识别,车辆识别。项目背景近年来,随着我国经济的快速发展,人们生活水平的不断提高,物......
  • JavaScript开发工具-WebStorm 2023 mac/win版
    WebStorm是一款由JetBrains开发的JavaScript开发工具,是专为JavaScript开发者设计的集成开发环境(IDE)。它提供了强大的功能和工具,能够帮助开发者更高效地编写、调试和维护JavaScript代码。→→↓↓载WebStorm2023mac/win版 WebStorm拥有一套丰富的功能,使得它成为JavaScript开......
  • 创建项目-空项目、java项目、web项目
    空项目(EmptyProject):空项目是一个完全空白的项目,没有任何默认的目录结构或配置文件。你可以根据需要自定义项目的结构和配置,适用于从头开始构建自己的项目。Java项目(JavaProject):Java项目是一个标准的Java应用程序项目,具有默认的目录结构和配置。它包含了源代码目......
  • JAVA面试题解惑系列(八)——聊聊基本类型(内置类型)
    关键字:java面试题基本类型intlongbooleanfloatdoublechar作者:臧圩人(zangweiren)基本类型,或者叫做内置类型,是JAVA中不同于类的特殊类型。它们是我们编程中使用最频繁的类型,因此面试题中也总少不了它们的身影,在这篇文章中我们将从面试中常考的几个方面来回顾一......