Java 网络编程 —— RMI 框架

这篇具有很好参考价值的文章主要介绍了Java 网络编程 —— RMI 框架。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述

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

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

Java 网络编程 —— RMI 框架

当客户端调用远程对象的一个方法时,实际上是调用本地存根对象的相应方法。存根对象与远程对象具有同样的接口。存根采用一种与平台无关的编码方式,把方法的参数编码为字节序列,这个编码过程被称为参数编组。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 设置为指定位置,从而实现动态加载文章来源地址https://www.toymoban.com/news/detail-485277.html

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

到了这里,关于Java 网络编程 —— RMI 框架的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C语言网络编程:实现自己的高性能网络框架

    一般生产环境中最耗时的其实是业务逻辑处理。所以,是不是可以将处理业务逻辑的代码给拆出来丢到线程池中去执行。 比如像下面这样: ​我们事先创建好一堆worker线程,主线程accepter拿到一个连接上来的套接字,就从线程池中取出一个线程将这个套接字交给它。这样,我

    2024年02月10日
    浏览(47)
  • 【Linux网络】网络编程套接字 -- 基于socket实现一个简单UDP网络程序

    我们把数据从A主机发送到B主机,是目的吗?不是,真正通信的不是这两个机器!其实是这两台机器上面的软件(人) 数据有 IP(公网) 标识一台唯一的主机 ,用谁来标识各自主机上客户或者服务进程的唯一性呢? 为了更好的表示一台主机上服务进程的唯一性,我们采用 端口号

    2024年02月12日
    浏览(159)
  • 网络编程:编写一个TCP客户端与服务端

    用的系统是Ubuntu。 socket用来创建套接字。这个函数服务端与客户端都要使用。 第一个参数用来制定地址族规范,比如 AF_INET(PF_INET) 表示IPv4地址, AF_INET6(PF_INET6) 表示IPv6地址。 第二个参数用来制定套接字的类型规范,如 SOCK_STREAM 表示面向连接的套接字, SOCK_DGRAM 表示面

    2024年02月01日
    浏览(47)
  • Java网络编程 - 网络编程介绍 - 网络通信三要素

    什么是网络编程 ? 网络编程可以让程序与网络上的其他设备中的程序进行数据交互。 网络编程基本模式 : 常见的通信模式有如下2种形式:Client-Server( CS: 客户端与服务器模式 ) 、 Browser/Server( BS: 浏览器与服务器模式 ) Client-Server(CS)模式 Browser/Server(BS)模式 实现网络编程关键的三

    2024年02月02日
    浏览(55)
  • 【Linux网络编程】高并发服务器框架 线程池介绍+线程池封装

    前言 一、线程池介绍 💻线程池基本概念 💻线程池组成部分 💻线程池工作原理  二、线程池代码封装 🌈main.cpp 🌈ThreadPool.h 🌈ThreadPool.cpp 🌈ChildTask.h  🌈ChildTask.cpp 🌈BaseTask.h 🌈BaseTask.cpp 三、测试效果 四、总结 📌创建线程池的好处 本文主要学习 Linux内核编程 ,结合

    2024年01月16日
    浏览(95)
  • Linux下网络编程(3)——socket编程实战,如何构建一个服务器和客户端连接

            经过前几篇的介绍,本文我们将进行编程实战,实现一个简单地服务器和客户端应用程序。 编写服务器程序          编写服务器应用程序的流程如下:         ①、调用 socket()函数打开套接字,得到套接字描述符;         ②、调用 bind()函数将套接字

    2024年02月03日
    浏览(67)
  • Java 网络编程 —— 非阻塞式编程

    在生活中,最常见的阻塞现象是公路上汽车的堵塞。汽车在公路上快速行驶,如果前方交通受阻,就只好停下来等待,等到公路顺畅,才能恢复行驶。 线程在运行中也会因为某些原因而阻塞。所有处于阻塞状态的线程的共同特征:放弃 CPU,暂停运行,只有等到导致阻塞的原

    2024年02月04日
    浏览(51)
  • 计算机网络技术与JAVA网络编程UDP编程-----JAVA入门基础教程-----计算机网络经典

    import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.*; public class UDP { public static void main(String[] args) { DatagramSocket datagramSocket = null; try { datagramSocket = new DatagramSocket(); InetAddress inetAddress = InetAddress.getByName(\\\"127.0.0.1\\\"); int port = 9090; byte[] byte

    2024年02月15日
    浏览(53)
  • 计算机网络技术与JAVA网络编程URL编程-----JAVA入门基础教程-----计算机网络经典

    import org.junit.jupiter.api.Test; import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class URLTest { public static void main(String[] args) { //URL:统一资源定位符(种子),一个URL就定位着互联网上某个资源的地址 //http:应用层协议,IP地址,端口号,资源地址,参数

    2024年02月15日
    浏览(63)
  • 【网络编程】利用套接字实现一个简单的网络通信(UDP实现聊天室 附上源码)

    源IP地址(Source IP Address): 源IP地址是数据包发送方(或数据流出发点)的唯一标识符。它用于在互联网或本地网络中定位发送数据包的设备或主机。源IP地址是数据包的出发点,即数据从这个地址开始传送,向目的IP地址指示的设备发送。 在TCP/IP协议中,源IP地址通常由发

    2024年02月14日
    浏览(86)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包