HYLOVEYOURSELF

小洋宝的进步史

导航

分布式计算编程项目二

利用RPC技术实现一个学生信息管理系统

一、具体要求

1.客户端实现用户交互,服务器端实现学生信息存储和管理。客户端与服务器端利用RPC机制进行协作。中间件任选。

2.服务器端至少暴漏如下RPC接口:

  • bool add(Student stu) 添加一条学生信息
  • Student queryByID(int stuID) 查询指定ID号的学生信息
  • Student queryByName(String name) 按姓名查询符合条件的学生信息
  • bool delete (int stuID) 删除指定ID号的学生信息

二、相关理论

理论迭代

1.单机结构

一个系统业务量很小的时候,所有的代码都放在一个项目中,然后这个项目部署在一台服务器上。整个项目所有的服务都由这台服务器提供。这就是单机结构。 它的缺点是显而易见的,因为单机的处理能力毕竟是有限的,当你的业务增长到一定程度的时候,单机的硬件资源将无法满足你的业务需求。此时便出现了集群模式。

2.集群结构

单机处理到达瓶颈的时候,把单机复制几份,这样就构成了一个“集群”。集群中每台服务器就叫做这个集群的一个"节点",所有节点构成了一个集群。每个节点都提供相同的服务,那么这样系统的处理能力就相当于提升了好几倍(有几个节点就相当于提升了这么多倍)。

但问题是用户的请求究竟由哪个节点来处理呢?最好能够让此时此刻负载较小的节点来处理,这样使得每个节点的压力都比较平均。要实现这个功能,就需要在所有节点之前增加一个"调度者"的角色,用户的所有请求都先交给它,然后它根据当前所有节点的负载情况,决定将这个请求交给哪个节点处理。这个“调度者”有个名字——负载均衡服务器

集群结构的好处就是系统扩展非常容易。如果随着系统业务的发展,当前的系统又支撑不住了,那么给这个集群再增加节点就行了。但是,当你的业务发展到一定程度的时候,你会发现一个问题——无论怎么增加节点,整个集群性能的提升效果并不明显了。这时候,就需要使用微服务结构了。

3.微服务结构

单机结构集群结构,代码基本无需要作任何修改,要做的仅仅是多部署几台服务器,每台服务器上运行相同的代码就行了。但是,当要从集群结构演进到微服务结构的时候,之前的那套代码就需要发生较大的改动了。所以对于新系统建议系统设计之初就采用微服务架构,这样后期运维的成本更低。因为如果一套老系统需要升级成微服务结构的话,那就得对代码大动干戈了。

微服务介绍

微服务就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在微服务结构中,每个子系统就被称为"服务"。这些子系统能够独立运行在web容器中,它们之间通过RPC方式通信。

容器:当客户请求服务器调用某不能独立运行(没有main函数)的JAVA程序的功能时,服务器会把此请求交给部署此程序的容器,容器再来调用此JAVA程序的方法。

web容器:支持发布web程序的软件。服务器的每个端口一般都有一个提供相应服务的程序(处理从客户端发出的请求)。程序与容器的关系如上所述,一个服务器可以包含多个容器。

web容器用于给处于其中的应用程序组件(ASP、JSP)提供一个环境,是中间件的一个组成部分,它实现了对动态语言的解析。

b/s结构下,以常见的自来水供水系统为例来说,整个web系统好比完整的供水系统,用户只需要有水龙头(Broswer),水管相当于计算机网络(network),自来水公司的供水系统(即Server),其中的压力水罐就是容器(Container)了。

假设需要开发一个在线商城———按照微服务的思想,我们需要按照功能模块拆分成多个独立的服务,如:用户服务、产品服务、订单服务、后台管理服务、数据分析服务等等。这一个个服务都是一个个独立的项目,可以独立运行。如果服务之间有依赖关系,那么通过RPC方式调用。

相关优点

  1. 系统之间的耦合度大大降低,可以独立开发、独立部署、独立测试,系统与系统之间的边界非常明确,排错也变得相当容易,开发效率大大提升。
  2. 系统之间的耦合度降低,从而系统更易于扩展。我们可以针对性地扩展某些服务。假设这个商城要搞一次大促,下单量可能会大大提升,因此我们可以针对性地提升订单系统、产品系统的节点数量,而对于后台管理系统、数据分析系统而言,节点数量维持原有水平即可。
  3. 服务的复用性更高。比如,当我们将用户系统作为单独的服务后,该公司所有的产品都可以使用该系统作为用户系统,无需重复开发。

RPC介绍

img

RPC 调用过程:

img
先注册

(1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;

(2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);

(3) 客户端通过 sockets 将消息发送到服务端;

(4) 服务端存根(server stub)收到消息后进行解码(将消息对象反序列化);

(5) 服务端存根(server stub)根据解码结果调用本地的服务;

(6) 本地服务执行并将结果返回给服务端存根(server stub);

(7) 服务端存根(server stub)将返回结果打包成消息(将结果消息对象序列化);

(8) 服务端(server)通过 sockets 将消息发送到客户端;

(9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);

(10) 客户端(client)得到最终结果。

  1. 注册中心 :注册中心负责服务地址的注册与查找,相当于目录服务。服务端启动的时候将服务名称及其对应的地址"ip+port"注册到注册中心,客户端根据服务名称找到对应的服务地址。有了服务地址之后,服务消费端就可以通过网络请求服务端了。
    例如:

    • RMI客户端在调用远程方法时先创建Stub。
    • Skeleton处理客户端请求:lookup、rebind、unbind,如果是lookup则查找RMI服务名绑定的接口对象。
  2. 网络传输 :既然要调用远程的方法就要发请求,请求中至少要包含要调用的类名、方法名以及相关参数

    例如下图:

    • RMI客户端的远程引用层通过Socket连接的方式(传输层)传输RemoteCall序列化后的请求信息到RMI服务端的远程引用层
  3. 序列化 :涉及到网络传输就一定会涉及到序列化,这里直接使用 JDK 自带的序列化。当一个 Java 类实现了Serializable接口后,Java 的内置序列化机制就能够将这个类的对象转换为字节流,并在需要时将字节流还原成对象。

    image-20240412135736823

    例如:

    • RemoteCall序列化RMI服务名称、Remote对象。
    • Skeleton调用RemoteCall,反序列化RMI客户端传过来的请求信息。
    • Skeleton处理客户端请求,然后序列化该对象并通过RemoteCall传输到客户端。
    • RMI客户端反序列化服务端结果,获取远程对象的引用。
    • RMI服务端反射调用RMI服务实现类的对应方法,后序列化执行结果返回给客户端。
    • RMI客户端反序列化RMI远程方法调用结果,即为最终结果。
  4. 动态代理 :因为 RPC 的主要目的就是让我们调用远程方法像调用本地方法一样简单,使用动态代理可以屏蔽远程方法调用的细节比如网络传输。也就是说当你调用远程方法的时候,实际会通过代理对象来传输网络请求(不然的话,怎么可能直接就调用到远程方法呢?)
    例如:

    • RMI客户端在调用远程方法时先创建Stub。这个步骤通常涉及动态代理,因为Stub作为客户端和服务端之间的代理,隐藏了远程方法调用的复杂性。
  5. 负载均衡 :如果我们的系统中的某个服务的访问量特别大,我们将这个服务部署在了多台服务器上,当客户端发起请求的时候,多台服务器都可以处理这个请求。那么,如何正确选择处理该请求的服务器就很关键。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题。

    例如:

    • 尽管负载均衡在这个基础的RMI调用流程中没有直接提及,但如果RMI服务部署在多个服务器上,负载均衡可以在客户端调用远程方法时选择合适的服务器实例

使用到的相关包

RMI相关包

import java.rmi.Naming;//这个类提供了在RMI注册表中查找和绑定远程对象的方法。
import java.rmi.registry.LocateRegistry;// 这个类用于获取和创建RMI注册表的实例,注册表是用于注册和查找远程对象的。
import java.rmi.Remote;// 这是一个标记接口,用于标识可以从非本地虚拟机上调用的接口。
import java.rmi.RemoteException;// 这个类用于声明远程方法调用过程中可能出现的异常。
import java.rmi.server.UnicastRemoteObject;// 这个类用于导出一个对象,使其成为远程对象,并使其能够接收远程方法调用。

其他包

import InterFace.*;// 这是一个自定义的包,包含了项目中定义的所有接口。

import java.util.List;// 这个接口代表一个元素的有序集合。
import java.util.ArrayList;// 这个类提供了List接口的可调整大小的数组实现。
import java.util.HashMap;// 这个类提供了基于哈希表的Map接口的实现。
import java.util.stream.Collectors;// 这个工具类提供了基于流操作的归约和汇总操作的实现。
import java.util.Scanner;// 这个类用于解析基本类型和字符串的简单文本扫描器。

import org.slf4j.Logger;// SLF4J的Logger类,用于记录日志。
import org.slf4j.LoggerFactory;// LoggerFactory类用于创建Logger对象。

import java.sql.Connection;// 这个接口定义了与特定数据库的连接,通过它可以发送命令并接收结果。
import java.sql.PreparedStatement;// 这个接口表示预编译的SQL语句,可以用于执行带或不带输入参数的SQL语句。
import java.sql.ResultSet;// 这个接口表示数据库结果集,它是通过执行查询数据库的语句生成的。
import java.sql.SQLException;// 提供关于数据库访问错误或其他错误信息的异常。
import java.sql.DriverManager;// 这个类管理一组JDBC驱动程序的基本服务。

import java.io.Serializable;// 这是一个标记接口,用于启用其序列化功能的类必须实现此接口。

MySQL启动方法

mysql的启动和环境配置

三、代码架构

img
  1. 客户端 (RMIClient):
    • 提供用户界面,通过控制台与用户交互,执行增加、查询、修改、删除和打印学生信息的操作。
    • 使用Java RMI的Naming.lookup方法从RMI注册表获取服务端的远程对象引用,以调用远程方法。
  2. 服务端 (RMIServer):
    • 注册远程对象StudentSystemImpl,使其可通过RMI客户端访问。
    • 创建和管理RMI服务注册表,允许在指定的端口(如9527)上接收请求,最后将RMI服务名绑定至远程方法实现类上。
    • 先定义RMI服务器IP地址、监听端口号、RMI服务名称,再创建注册中心
  3. 远程接口 (StudentSystemInt):
    • 定义了客户端和服务端共用的远程方法接口,如添加、查询、删除、修改学生信息和打印学生列表。
    • 所有方法都声明了抛出RemoteException,以处理远程调用中可能发生的异常
  4. 远程对象实现 (StudentSystemImpl):
    • 其中实现学生信息管理系统的数据结构定义以及数据操纵函数
    • 实现了StudentSystemInt接口,处理实际的业务逻辑。
    • 使用HashMap来存储学生数据,同时通过StudentDaoImpl数据库交互,实现数据的持久化
  5. 数据访问对象 (StudentDao, StudentDaoImpl):
    • 提供对数据库的访问方法,如保存、查询、删除和更新学生信息。
    • 使用JDBC(是一种Java API,用于在Java应用程序中连接数据库和执行查询)。这个API,由一组用Java语言编写的类和接口组成,提供了一种标准方法,让Java程序能够独立于底层数据库访问与操作数据,进行数据库操作,保证数据的持久存储。

四、功能实现+界面展示

基本功能:

  • 增加学生:客户端收集学生ID和姓名,调用服务端的添加方法。服务端检查缓存(HashMap)中是否已存在该学生,若不存在则添加到缓存和数据库。
  • 查询学生:支持通过ID或姓名查询。通过ID查询会先查缓存,未命中再查询数据库。通过姓名查询使用并行流从缓存中筛选,未命中则查询数据库。
  • 删除学生:从缓存和数据库中删除指定ID的学生。
  • 修改学生信息:更新指定学生的姓名信息,在缓存和数据库中都进行更新。
  • 打印学生列表:从数据库中检索所有学生信息,并在客户端打印。

I.为什么HashMap有利于查询?

哈希函数:HashMap使用哈希函数将键(key)映射到数组中的一个位置(称为桶或槽)。通过哈希函数,可以快速定位到对应的桶,减少了查找的范围。

常数时间复杂度:在理想情况下,哈希函数将键均匀地分布到各个桶中,使得每个桶中的元素数量相对较少。因此,查找操作通常只需要计算一次哈希函数,然后在对应的桶中查找目标元素,时间复杂度为O(1)。

冲突处理:当多个键被映射到同一个桶时(即发生哈希冲突),HashMap使用链地址法(链表)或者开放地址法来解决冲突。在使用链地址法时,桶中的每个元素维护一个链表,冲突的键值对以链表的形式存储;在使用开放地址法时,HashMap会根据一定的探查序列找到下一个空闲的位置。

II.HashMap的适用范围

需要快速查找的场景:HashMap特别适用于需要频繁进行查找操作的场景,如缓存系统、符号表、计数器等。

键值对存储:当需要以键值对形式存储和快速访问数据时,HashMap是一个很好的选择。例如,用户信息的存储、配置参数的管理等。

频繁插入和删除:HashMap在插入删除操作方面也具有良好的性能,适合需要频繁更新数据的应用场景。

不需要顺序访问:如果不要求按顺序访问数据,HashMap是一种非常高效的数据结构。它的插入顺序和存储顺序是没有关系的,如果需要顺序访问数据,可能需要使用其他数据结构如TreeMap或者LinkedHashMap。

技术点:

除了Java RMI技术实现网络间的远程方法调用外,还具备以下优势:

1. 查询速度提高

  • 索引:在操作储存在缓存中的数据部分时,考虑使用更高效的数据结构,如HashMap,以学生ID作为键,这样查询的时间复杂度可以从O(n)降低到接近O(1)。

2. 数据模型和封装

  • 封装Student类的属性:最初Student类的studentIDstudentName属性是公开的(public)。后面改进为使用私有属性和公开的getter/setter方法来增强封装性

3. 异常处理

  • 细化异常处理:最初的实现中,异常被捕获后仅仅打印堆栈跟踪。所以我根据不同的异常类型做更细化的处理,例如,对用户输入错误或网络问题给出更友好的提示信息

4. queryByName方法的性能

  • 使用并行流:将数据结构已经从ArrayList切换到了HashMap后,考虑到对于queryByName这样的操作,仍然需要遍历整个集合。为了提高处理大量数据的效率,我使用了Java 8的parallelStream()来并行处理这些数据,将数据分片后,同时查询多个片段,从而减少总体查询时间。

5. 增加日志记录

  • 使用日志框架:在关键操作处(如添加、删除学生信息)增加日志记录。这不仅有助于开发和调试,而且对于生产环境中的问题排查也非常有用。我使用了SLF4J的日志框架。

6. 安全性考虑

  • 参数校验:在处理客户端请求时,对输入参数进行校验,比如检查studentIDstudentName是否为null或空字符串,以避免潜在的错误或安全问题。

7. 数据持久化

  • 持久化学生数据:数据一般保存在缓存中,我将学生信息持久化MySQL数据库中,这样即使程序重启,学生信息也不会丢失。

持久化学生数据MySQL数据库涉及计算机系统中的多个组成部分,这些组成部分的协调工作确保了数据的可靠存储和读取。

1. 持久化的过程

持久化是指将数据从易失性存储(如内存)保存到非易失性存储(如磁盘)的过程。持久化操作确保数据在系统重启后仍然可用。

2. 计算机组成原理中的相关部分

持久化操作涉及以下主要的计算机组成部分:

    2.1. 内存(RAM)

  • 作用:内存是程序运行时数据存储的地方,包括变量、数据结构、缓存等。在你保存学生信息之前,信息首先会被加载到内存中进行处理。

  • 特点:内存是易失性的,即断电后数据会丢失

    2.2. 中央处理器(CPU)

  • 作用:CPU执行数据库持久化操作的指令,包括读取学生信息、建立数据库连接、执行SQL语句、处理响应等。

  • 特点:CPU是计算机的核心处理单元,负责计算和逻辑操作

    2.3. 硬盘(或固态硬盘,SSD)

  • 作用:硬盘是非易失性存储设备,数据持久化最终会保存到硬盘上。MySQL数据库文件(包括表、索引等)实际存储在硬盘上。

  • 特点:硬盘的数据在断电后仍然保存,确保了数据的持久性

    2.4. I/O系统

  • 作用:I/O系统负责内存与硬盘之间的数据传输。持久化操作涉及将数据从内存通过I/O系统写入硬盘。

  • 特点:I/O操作速度较,是持久化过程中影响性能的关键部分。

3. 持久化操作的具体过程

  1. 数据准备:学生信息首先保存在内存中。
  2. 建立数据库连接:通过数据库驱动(如JDBC),程序与MySQL数据库建立连接。
  3. 执行SQL语句:CPU执行持久化的指令,包括INSERT或UPDATE SQL语句,将数据写入MySQL数据库。
  4. I/O操作:通过I/O系统,数据从内存写入硬盘上的MySQL数据库文件
  5. 事务处理:MySQL数据库管理系统(DBMS)负责处理事务,确保数据持久化的原子性、一致性、隔离性和持久性(ACID属性)
  6. 数据存储:最终,学生信息被存储在硬盘上的MySQL数据库文件中,即使系统重启,数据也不会丢失。

4. 计算机组成原理的关系

持久化操作展示了计算机各组成部分的协同工作:

  • 内存和CPU:处理和暂存数据。
  • I/O系统:负责数据在内存和硬盘之间的传输。
  • 硬盘:提供非易失性存储,确保数据的持久性。

5. 总结

将学生信息持久化到MySQL数据库涉及计算机组成的多个关键部分,确保数据在断电或系统重启后仍然可用。具体来说,数据最终存储在计算机的硬盘或SSD(Solid State Drive,固态硬盘)中,通过内存、CPU和I/O系统的协同工作,实现数据的安全存储和持久化。

界面展示

1.启动成功的服务端

image-20240412151821033

2.启动成功的客户端

image-20240412151902479

3.为了演示操作,我先把数据库里的东西都掉:

比如说我的数据库原来里面是:

image-20240412152101431

我在客户端进行如下操作:

image-20240412152211536

然后我的服务端可见我的删除日志

image-20240412152324175

然后我刷新数据库

image-20240412152415199

可见数据已经被全部删除;

4.接下来,我们从第一条开始尝试功能:

image-20240412152816644

5.查看我们的日志:

image-20240412152855409

6.我们可以先看缓存里面添加成功没有,输入6,打印学生列表:

image-20240412153229876

7.接下来,我们刷新数据库:

image-20240412153248837

8.然后,我们试试我们的查询功能:

(1)通过ID查询:

image-20240412153528921

我们查看日志:

image-20240412153554449

显示的是通过缓存查到了对应学生,这是因为我设置了先从缓存查,再从数据库查,从缓存查到后就不再查询数据库

我们再试个查不到的:

image-20240412154146654

下面是我们的日志:

image-20240412154209396

(2)通过姓名查询:

image-20240412153835450

下面是我们的日志:

image-20240412153857850

我们再试个查不到的:

image-20240412154025634

下面是我们的日志:

image-20240412154101778

9.我们试试删除功能:

image-20240412154346768

下面是我们的日志:

image-20240412154410099

我们来展示一下我们的学生列表:

image-20240412154446966

可以看到ID==3的红太狼被删了,接下来,我们可以看看数据库:

image-20240412154546857

10.最后,我们试一下改名功能:

image-20240412154744770

我们日志也有正确呈现:

image-20240412154813740

我们看一下数据库:

image-20240412154846814

改名成功!!!

11.我们试试加入ID相同的学生:

image-20240412155017781

我们的日志也有回应:

image-20240412155054544

数据库也没有添加成功

image-20240412155116245

另外,补充一下我们的数据库设置:

image-20240412155249059

12.我们试试把学生都删了再继续删

image-20240412155455665

删后的数据库:

image-20240412155736265

可见程序可以准确识别错误:

image-20240412155538102

13.我们尝试退出程序:

image-20240412155626431

14.我们试试在数据库直接加一个学生:

image-20240412155959093

可以发现查询ID==1可以查到他!

image-20240412160121364

我们查看日志:

image-20240412160202406

演示完毕!成功!!!

五、系统的展望

1.其他序列化协议:因为JDK 自带的序列化效率低并且有安全漏洞。 所以,还要考虑使用哪种序列化协议,比较常用的有hession2、kyro、protostuff

2.多个服务端:现在只有一个服务端,后面希望拓展到多个。后调用服务时,从很多服务地址中根据相应的负载均衡算法选取服务地址。

3.逐步引入服务专用数据库:当条件允许时,为每个服务配置独立的数据库

4.容器化部署:当使用了微服务架构后,我们将一个原本完整的系统,按照业务逻辑拆分成一个个可独立运行的子系统。为了降低系统间的耦合度,我们希望这些子系统能够运行在独立的环境中,这些环境之间能够相互隔离。若使用虚拟机来实现运行环境的相互隔离的话成本较高,虚拟机会消耗较多的计算机硬件/软件资源。所以这里建议使用Docker,Docker不仅能够实现运行环境的隔离,而且能极大程度的节约计算机资源

5.自动化构建:当我们使用微服务架构后,随着业务的逐渐发展,系统之间的依赖关系会日益复杂,而且各个模块的构建顺序都有所讲究。这里可以使用Jenkins,我们只需在Jenkins中配置好代码仓库、各个模块的构建顺序和构建命令,在以后的构建中,只需要点击“立即构建”按钮,Jenkins就会自动到你的代码仓库中拉取最新的代码,然后根据你事先配置的构建命令进行构建,最后发布到指定的容器中运行。

6.安全性与数据管理:考虑引入更复杂的错误处理机制、增强的安全性措施(如使用SSL/TLS加密RMI通信),以及更高级的数据管理策略(如引入ORM框架如Hibernate)。

错误处理机制(Error Handling Mechanism)

错误处理机制是指在程序中捕捉、处理和记录错误的方式。复杂的错误处理机制可以帮助开发者更好地理解程序出现的问题,并采取适当的措施来修复或缓解这些问题。例如,Java中使用try-catch块来捕捉异常,并可以记录错误日志以供分析。

安全性措施(Security Measures)

安全性措施是指为了保护系统和数据免受未经授权访问、使用、披露、破坏、修改或破坏所采取的行动。

  • SSL/TLSSSL(Secure Sockets Layer,安全套接字层)和TLS(Transport Layer Security,传输层安全)是用于在计算机网络上提供安全通信的协议。它们通过加密数据来保护通信内容的机密性和完整性。例如,使用SSL/TLS加密RMI(远程方法调用,Remote Method Invocation)通信可以防止数据在传输过程中被窃取或篡改。

数据管理策略(Data Management Strategy)

数据管理策略是指管理、存储和检索数据的方法和流程。高级的数据管理策略可以提高数据处理的效率、可靠性和安全性。例如:

  • ORM框架(Object-Relational Mapping Framework):ORM框架是一种用于在面向对象编程语言(如Java)中,将对象映射到数据库中的关系数据的工具。它简化了数据库操作,使开发者可以直接使用面向对象的方式操作数据库。一个常见的ORM框架是Hibernate

具体解释:

  • SSL/TLS:一种加密协议,用于保护网络通信的安全性。SSL/TLS加密RMI通信意味着通过这种加密技术,确保远程方法调用过程传输的数据是安全的,不会被未授权方查看或篡改。

  • RMI(Remote Method Invocation,远程方法调用):Java中的一种机制,允许一个Java程序调用另一个Java虚拟机中的方法,通常用于分布式计算。

  • ORM框架(Object-Relational Mapping Framework):一个将程序中的对象模型与关系数据库中的数据模型进行映射的工具。ORM框架使开发者可以通过对象操作数据库,而不需要编写复杂的SQL语句(即无须JDBC(Java Database Connectivity)编程)。Hibernate是一个流行的Java ORM框架,它提供了强大的数据库交互功能,简化了数据管理。

六、运行老师的文件的收获

首先,我的环境是JDK8

我对代码及其结构均进行了调整:

刚好可以在下面这张图里基本都展示出来:

image-20240406221400480

然后,有用的步骤是:

1.分别到server/client所在目录下cmd完成.java文件的编译: javac -encoding utf-8 *.java,然后在targe目录下就会出现一些.class文件(必须的,不然编译时找不到文件;注意修改.java内容后要重新编译);

2.在客户端与服务器端.class端的大目录里面建立一个jar包:

jar cvf shared.jar rmiserver\MyCalc.class
image-20240406221950283

3.先清理占用1099端口的进程:

(1)也是到客户端与服务器端.class端的大目录,cmd一下;

(2)清理进程

netstat   -ano|findstr 1099

image-20240406222234724

tasklist|findstr 99232

(这步可有可无)

taskkill /pid 99232 -t -f

image-20240406222518371

(3)引入注册中心

start /b java -classpath "D:\Grade 3\分布式计算\_0406tri\rmi_teacher\target\classes;D:\Grade 3\分布式计算\_0406tri\rmi_teacher\target\classes\shared.jar" sun.rmi.registry.RegistryImpl 1099

image-20240406222804901

4.在IDEA上先运行服务端,再运行客户端~

大功告成!!!

正确结果

服务端先:

image-20240406223249966

客户端后:

image-20240406223324926

然后返回服务端:

image-20240406223408934

整体界面展示:

image-20240406223558625

错误回忆

1.这个的造成一方面是因为我现在的JDK版本会限制接口MyCalc必须是唯一的,另一方面是因为我的代码当时没有在客户端把这个接口删掉+申明用服务端编译好的MyCalc,还有一方面是运行目录也不对;

image-20240406223707759

2.修改代码后,原来对应的.class文件最好删掉,重新编译!

另外补充

命令行运行方式(前提:编译好class文件+进入正确目录):

服务器端:

java -classpath "target\classes;target\classes\shared.jar" rmiserver.MyRMIServer

image-20240406224513155

客户端:

java -classpath "target\classes;target\classes\shared.jar" rmiclient.MyRMIClient

image-20240406224849242

(那时代码还没改hhh,所以有一丢丢问题,代码改好就正确啦!)

PS:错误的根本原因是因为客户端尝试将远程对象转换为 rmiclient.MyCalc,而实际上它应该转换为远程接口 rmiserver.MyCalc即使两个接口的源代码完全相同,如果它们的包路径不同,Java 也会将它们视为完全不同的类型

七、参考链接

我手写了一个RPC框架。成功帮助读者斩获字节、阿里等大厂offer。 - 知乎 (zhihu.com) 基于 Netty+Kyro+Zookeeper 实现的 RPC 框架(非常优秀!)

分布式计算_grpc_书籍信息管理系统(不配叫系统)_分布式计算实现书籍管理-CSDN博客

【JAVA】基于RMI的书籍信息管理系统_bool add(book b) 添加一个书籍对象。 book querybyid(int book-CSDN博客

CourseDesigned: 课程设计 - Gitee.com

Java的RMI远程方法调用实现和应用-阿里云开发者社区 (aliyun.com)

Distributed-Computing/exp3-rpcsystem at main · silence-tang/Distributed-Computing (github.com)

八、未来的学习方向

1.(微服务讲得特别浅显易懂,我有时间一定实现一下)

如何零基础搭建一套微服务框架(Spring Boot + Dubbo + Docker + Jenkins) - 知乎 (zhihu.com)

2.注册中心:Nacos 网关:Gateway 后端基础框架:ssm 前端:Vue + SPA Axios(request.js)

SpringCloud微服务之学生管理_基于微服务的学生管理系统-CSDN博客

3.基于 MySQL + springcloud 微服务框架的作业管理系统_基于微服务的作业管理系统-CSDN博客

4.文章详细,可视化良好

在idea中搭建微服务项目(22版),详细教程_idea创建微服务项目-CSDN博客

5.作为补充吧

JDK 1.8 IEDA 2019(推荐)Maven 3.5.x(推荐) nacos 1.1.3(推荐)

手把手教你搭建第一个微服务(框架) - 知乎 (zhihu.com)

6.发现的宝藏!Markdown进阶(更改字体、颜色、大小,设置文字背景色,调整图片大小设置居中)

posted on 2024-05-24 22:44  岁月月宝贝  阅读(34)  评论(2编辑  收藏  举报