概述

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/

Java 网络编程 —— RMI 框架的更多相关文章

  1. java网络编程框架

    虽然写过一些网络编程方面的东西,但还没有深入研究过这方面的内容,直接摘录一些文章,后续整理 原文地址:http://blog.csdn.net/lwuit/article/details/730613 ...

  2. Java网络编程和NIO详解9:基于NIO的网络编程框架Netty

    Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...

  3. 20145208 实验五 Java网络编程

    20145208 实验五 Java网络编程 实验内容 1.用书上的TCP代码,实现服务器与客户端. 2.客户端与服务器连接 3.客户端中输入明文,利用DES算法加密,DES的秘钥用RSA公钥密码中服务 ...

  4. 20145215实验五 Java网络编程及安全

    20145215实验五 Java网络编程及安全 实验内容 掌握Socket程序的编写: 掌握密码技术的使用: 设计安全传输系统. 实验步骤 本次实验我的结对编程对象是20145208蔡野,我负责编写客 ...

  5. 20145220 实验五 Java网络编程

    20145220 实验五 Java网络编程 实验内容 1.用书上的TCP代码,实现服务器与客户端. 2.客户端与服务器连接 3.客户端中输入明文,利用DES算法加密,DES的秘钥用RSA公钥密码中服务 ...

  6. Java 网络编程最佳实践(转载)

    http://yihongwei.com/2015/09/remoting-practice/ Java 网络编程最佳实践 Sep 10, 2015 | [Java, Network] 1. 通信层 ...

  7. Java网络编程和NIO详解开篇:Java网络编程基础

    Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...

  8. 【Android实战】----从Retrofit源代码分析到Java网络编程以及HTTP权威指南想到的

    一.简单介绍 接上一篇[Android实战]----基于Retrofit实现多图片/文件.图文上传中曾说非常想搞明确为什么Retrofit那么屌. 近期也看了一些其源代码分析的文章以及亲自查看了源代码 ...

  9. Java网络编程和NIO详解6:Linux epoll实现原理详解

    Java网络编程和NIO详解6:Linux epoll实现原理详解 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO h ...

  10. Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制

    Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制 JAVA 中原生的 socket 通信机制 摘要:本文属于原创,欢迎转载,转载请保留出处:https://github.co ...

随机推荐

  1. 深入理解 python 虚拟机:字节码灵魂——Code obejct

    深入理解 python 虚拟机:字节码灵魂--Code obejct 在本篇文章当中主要给大家深入介绍在 cpython 当中非常重要的一个数据结构 code object! 在上一篇文章 深入理解 ...

  2. 【CTF】系统调用号查询表

    32位 #ifndef _ASM_X86_UNISTD_32_H #define _ASM_X86_UNISTD_32_H 1 #define __NR_restart_syscall 0 #defi ...

  3. [Excel/Word]常用函数与技巧

    1 Excel case1 同时多列筛选 同时筛选多列: 选中首行(属性行)>筛选>(筛选目标的N列) case2 IF/OR/AND/COUNTIF语句 =IF(condition,co ...

  4. c#快速入门~在java基础上,知道C#和JAVA 的不同即可

    观看下文前提:如果你的主语言是java,现在想再学一门新语言C#,下文是在java基础上,对比和java的不同,快速上手C# C# 学习参考文档和开发工具 微软c#官方文档:https://learn ...

  5. Python property、setter、deleter

    面向对象封装特点之一就是通过实现好的方法来访问,限制对数据的不合理访问,把对象状态私有化,仅供类的内部进行操作 下方示例,Test方法的number属性类实例的时候传递1,number是一个公开属性, ...

  6. Sentinel为什么这么强,我扒了扒背后的实现原理

    大家好,我是三友~~ 最近我在整理代码仓库的时候突然发现了被尘封了接近两年之久的Sentinel源码库 两年前我出于好奇心扒了一下Sentinel的源码,但是由于Sentinel本身源码并不复杂,在简 ...

  7. Appweb配置

    Appweb配置       具体配置网页=> https://www.embedthis.com/appweb/doc/users/configuration.html         具体参 ...

  8. Pyathon If条件测试

    if条件测试 # 案例 cars = ['audi','bmw','subaru','toyota'] for car in cars: if car =='bmw': print(car.upper ...

  9. 基于pip的python包管理工具

    以下是软件下载链接:https://mysecreat.lanzoub.com/i5yvf0swgtne 软件功能:可以对python包进行安装.卸载.升级.换源等操作,不用输入复杂命令 源码: im ...

  10. C#异步有多少种实现方式?

    前言 微信群里的一个提问引发的这个问题,C#异步有多少种实现方式?首先想要知道C#异步有多少中实现方式,首先我们要知道.NET提供的执行异步操作的三种模式,然后再去了解C#异步实现的方式. .NET异 ...