Java图书管理系统 -- 基于Socket实现客户端服务端拆分

2023-11-12

图书管理系统小Demo又又又升级了!

本图书管理系统已经经历了三个阶段

  • 通过操作数组来实现图书的增删改查方法,用控制台获取用户输入来实现人机交互
  • 通过集合容器存储对象,使用序列化在管理系统开启关闭时,加载,存储数据到本地
  • 使用TCP协议实现客户端服务端分离,客户端实现信息输入,信息展示,服务端接收用户请求,并处理数据,将结果返回给用用户。

最终效果图

第二代图书管理系统

第一代,我们的三层架构实际上是MVC缩水版,service层和controller层其实是一个东西,连接前端的(控制台实现)是直接调用对象.属性的方式来实现的。这次我将跨度直接来一个大升级,直接将客户端和服务端拆分。

项目结构

  • Bean
    • Book 图书对象
  • BookService
    • Book图书管理系统的业务层,里面有操作图书的增删改查,以及读取文件,存储资源到本地,关闭资源等方法
    • 内置了一个TreeSet用于存储Book对象
  • BookView
    • Book图书管理系统的前端,接收用户输入,然后调用BookService的方法,再把结果返回给用户

image-20200823173643465

成果展示

相信大家都写得出来吧!无非一些键盘录入,switch,操作集合容器,IO读写

问题

虽然使用IO流可以轻松的将数据持久化到本地,但是我们的View和Service之间还是以传输对象为主,直接调用Service层的方法,耦合性很高,而且我们这个项目始终是个整体,想要跑一跑这个项目,得把数据文件整个一起打包传输给别人才行,不然你上述代码查看所有图书功能在不给别人数据文件的前提下怎么展示呢?

思考

  • 倘若我们只发送一个客户端,别人直接通过客户端就可以得到我们这以上的结果,但是客户端像知道方法实现的细节以及得到你的数据文件,不好意思,没有。
  • 客户端只能展示数据,却不能得到数据的对象,也就是说客户端仅仅是往界面展示一些所需要的的数据,至于代码的实现逻辑,数据文件的存储过程,等方法的实现细节都无法得到。

改造!

注意事项

  1. 在不大规模修改代码,已实现的功能应该保持原样

  2. 客户端服务端应该是两个不同的项目而不是一个包下面的两个子包!

  3. 客户端不应该有操作数据的方法,服务端不能有操作客户端的方法。

技术难点

  • 如何实现通信?
  • 前后端如何架构?
  • 如何实现信息交互?

信息通信,相信简单的网络编程TCP和UDP的小Demo大家都会写吧,我这里简单罗列一下代码


如何实现通信?

这里我引入其他博主优秀的帖子:网络通信协议,TCP和UDP协议

写两个简单的案例:

UDP

UDP发送端

public class UdpSend{

    public static void main(String[] args) throws SocketException {
        DatagramSocket datagramSocket = new DatagramSocket();
        while (true) {
            //控制台发送信息
            Scanner scanner = new Scanner(System.in);
            String message = scanner.nextLine();
            byte[] bytes = message.getBytes();
            //发送数据包
            try {
                DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("127.0.0.1"), 10001);
                datagramSocket.send(datagramPacket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

UDP接收端

public static void main(String[] args) throws SocketException {
        DatagramSocket datagramSocket=new DatagramSocket(10000);
        while (true){
            //新建一个数据包,用于存储数据
            byte[] bytes = new byte[1024];
            DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
            //通过服务的receive方法将受到的数据存入数据包中
            try {
                datagramSocket.receive(datagramPacket);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //获取连接的属性(ip 端口 文件内容)
            String ip=datagramPacket.getAddress().getHostAddress();
            String data = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
            int port = datagramPacket.getPort();
            System.out.println(" ip : "+ip+", data : "+data+" , port : "+port);
        }
    }

玩一玩

UDP大概就是数据加进数据包,然后发送一个数据包,至于是否发送到别人手上,我不管!


TCP

细节就不说了,TCP就是一种稳定的协议,知道要传输给谁,而且保证能传输。

TCP客户端

import java.io.*;
import java.net.Socket;

public class TcpClient {

    public static void main(String[] args) throws IOException {

        //创建一个客户端对象
        Socket socket=new Socket("192.168.1.3", 10000);
        //用于接收键盘录入到流中
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        //用于接收服务端返回结果
        BufferedReader receive = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        //用于发送请求到服务端
        BufferedWriter send = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        //读取键盘录入
        String request = null;

        while ((request=bufferedReader.readLine())!=null){
            if ("exit".equals(request)){
                break;
            }
            //发送信息
            send.write(request);
            send.newLine();
            send.flush();
            //等待服务器范返回
            String response=receive.readLine();
            System.out.println(response);
        }
        //关闭资源
        bufferedReader.close();
        receive.close();
        send.close();
        socket.close();
    }
}

TCP服务端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {

    public static void main(String[] args) throws IOException {
        //建立服务端对象,监听10000端口
        ServerSocket server=new ServerSocket(10000);
        //接收一个客户端对象
        Socket socket=server.accept();

        //用于接收客户端信息
        BufferedReader receive = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        //用于信息发送客户端
        BufferedWriter send = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        String line = null;
        //接收客户端发出的信息
        while ((line=receive.readLine())!=null){
            //将请求字符串的大写格式返回
            send.write(line.toUpperCase());
            send.newLine();
            send.flush();
        }
        //关闭资源
        socket.close();
        server.close();
        receive.close();
        send.close();
    }
}

玩一玩

简单的说就是发送完一个字符串,服务器接收到然后把字符串大写的结果返回给客户端

那么要更新我们的图书管理系统,我们应该用哪个协议呢?

我们想象一下:

**UDP:**客户端把一个请求写为/login/root/123456打包成数据包,发送到服务端,服务端解析字符串,把/loginResut/true发送给客户端,客户端解析字符串,得到结果==》登录成功

**TCP:**服务端客户端建立连接,客户端将/login/root/123456打包成字符串写进socket流,服务端读取到流,解析字符串,将"true"字符串写进流,客户端读取到流,字符串转布尔值==》登录成功

看上去差别不大,UDP每次重新打包,而且如果有多个客户端,你是不是不知道应该是哪个客户端发来的数据包,而且服务端发送数据包也要注名是发给谁,这样的操作每次都要进行,既然如此那不如干脆我们两个先建立连接再通过流告诉的读写。

根据以上分析,我们决定用稳定的TCP协议来连接前后端。


前后端如何架构?

首先,我们不能大规模改代码,我们应该在不怎么修改代码的前提下,完成业务拆分

我们看一下我们原来的项目架构。

  • BookService存放增删改查图书,文件读取,存储,这些功能保持现状。
  • BookView用于获取用户输入,调用bookSservice的方法,返回结果再展示给用户。
  • BookUtils有操作数据的方法,比如排序,匹配字符串,也有操作前端的方法,比如获取键盘录入的合法性匹配

我们发现:

  • 将BookView调用bookService的方法的过程进行拆分是我们实现前后端分离的关键,但是又不能大规模改代码

image-20200823185312416

我们提出以下架构

  • 首先我们新增了一个管理中心,也就是Socket服务,也就是TCP,我们前后端交互都是通过对管理中心进行交互数据来实现通信的。

  • 新增一个BookHandler类,用于解析socket流传过来的东西,然后再调用BookService方法,将返回值再通过TCP通讯返还给BookView前端界面。

为什么有两个BookService类?两个BookService冲突吗?**

  • 前端的每一个方法都是依赖于调用BookService.方法名实现的,如果我们不希望页面飘红,我们就保留BookService这个类,但是我们的对方法体要进行修改,不能像以前一样。

  • 不冲突!因为我们的架构设计,前后端已经分离了,为了体现分离,我们可以新建两个module,分别写前端和后端,这样即便方法重名也互不干涉!

image-20200823190115033


第三代,前后端分离架构

真正意义的分离

image-20200823191037310

  • Book_Client 客户端

    • BookClient 连接控制中心,即建立TCP通信
    • BookUtils 只有一些前端必须的方法,比如获得键盘录入,没有操作数据的方法
    • BookService 存放一些发送请求以及解析请求结果的方法。
    • BookView 前端,存放一些获取键盘录入的条件判断的一些方法

    其中BookUtils对象是抽取出前端才需要的方法,没有更改代码,BookView也没有更改任何代码

image-20200823191127731

  • Book_Server 服务端

    • Book 图书对象
    • BookHandler 处理请求,并返回结果
    • BookServer 建立通讯,建立TCP链接
    • utils 处理数据要用的方法,比如排列,匹配
    • BookService 处理图书对象

    其中 Book,BookService,util对象没有进行任何的更改。

image-20200823191417699


核心代码

先看简单的前端的BookService

我们看到我们把参数都封装成一个类似于get请求的字符串形式,再通过BookClient的request方法发送出去,方法有个String类型的返回值,也就是response,我们发现我们都是把字符串转换成布尔值,我们看看其他方法。

image-20200823191932085

这里有返回结果为void,返回结果为String的方法,可以看到,不同的方法,我们都进行不同的处理,返回的都是我们想要的字符串。原因是我们客户端服务器是固定的,每个方法发出的请求字符串的前缀就是方法名也是唯一的,参数也是给定的,所以我们前端发送这些请求就可以被后端正确的解析并且返回想要的结果。

image-20200823192134794

由于BookService的方法体变了,但是方法返回值,参数等信息都是没有变化的,所以我们的前端不会飘红。

接下来上一点难的代码

这是BookClient的request方法,简单的说就是如果不终止,那么就输入流里写请求,然后输出流拿结果。当然结果是readline是字符串,比如返回结果是"true",我们用Boolean.parseBoolean()来转换

可以看到,客户端由于几乎没有处理数据的方法,只是简单的字符串类型转换,和发送request字符串,还是很好实现的,没有大规模改动BookView的代码。

image-20200823192750768

接下来看服务端

先从简单的开始

服务端除了BookHandler和BookServer以外,其他代码都是原封不动的

image-20200823193123754

我们先看一下建立通讯的BookServer

这里没什么好说的,我们看BookHandler的handler方法

实现细节我发红色字了,大概就是TCP简单Demo多一些条件判断而已

image-20200823193503777

模拟请求解析过程

image-20200823193804240

我们发现,实际上就是知道是哪个方法然后用类加载机制通过方法名得到方法Method对象,然后传入参数,将结果返回,由于response都是String类型的,而String.valueOf方法可以将其他类型转化成String类型。

image-20200823193905332

流程图

image-20200823194810442

流程叙述

  1. BookView调用BookService的方法,BookService将方法名和参数信息拼接为一条为一条字符串,比如login/username/123456交给BookClient的request方法。

  2. BookClient将请求字符串写入socket的输入流中,然后BookServer的readline方法读取到socket输出流中前端传递过来的字符串,将字符串交给BookHandler方法。

  3. BookHandler的handler方法解析请求字符串为String类型的数组。比如login/username/123456,解析为String{login,username,password}。通过数组的第一个元素来选择要调用的方法的方法名,使用类加载机制获取到BookService对应的方法对象Method,然后用Method.invoke(对象,参数…)来调用方法。返回结果再转换为字符串,然后返回给BookHandler。

  4. BookHandle将返回结果写进socket的输入流中,然后前端的BookClient接收到socket输出流中的response字符串,交给BookService。

  5. BookService根据需求决定是将字符串转换为布尔值,还是直接返回,还是打印等各种操作。

结果演示

由于已经是分布式了,所以我们可以分别打包成jar包,通过jar包的方式来运行java程序,这样更加的直观。

由于篇幅问题,一些项目的细节以及采坑记录我会单独的整理,要源码的可以私信我。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java图书管理系统 -- 基于Socket实现客户端服务端拆分 的相关文章

随机推荐