快递驿站(C/S+MVC+集合+持久化)

快递驿站(C/S+MVC+集合+持久化+多线程)

  • C/S:采用客户端与服务器模式的网络编程,客户端只是作为视图层与服务器间纽带,进行数据的传递,由视图层读取的数据交由客户端发送到服务器,服务器端调用dao操作数据增删改查,然后客户端接收服务器回写数据交给视图层展示。
  • MVC:采用三层模式,view视图层只向用户展示和读取用户输入,c主要是逻辑控制,流程控制,接收请求,委托m处理数据并返回数据到v,,m主要是业务逻辑,对数据的操作。
  • 数据持久化:将服务器接收的数据存入集合,将集合序列化到文件
  • 多线程:在服务器端开启一个子线程用于服务器操作数据。
Socket实现一个客户端一个服务器端通信,进行快递信息的传递,服务器将接收快递持久化存入硬盘保存

客户端:1.将控制台快递信息传递给服务器
       2.接收服务器回写信息,交给视图层展示
       3.客户端作为视图层和服务器之间纽带,只负责数据的传递工作,快递增删改查在服务器完成。

视图层:1.(将控制台作为视图层)控制台接收用户输入,交给客户端;
       2.接收客户端返回结果,显示到控制台。
       3.视图层只与客户端通信

服务器:1.接收客户端信息,将信息交给DAO处理
       2.DAO处理信息后(数据增删改查)将结果返回给服务器
       3.服务器将结果发送给客户端

DAO:   1.进行数据的存储,各种增删改查方法
       2.确保单号、取件码、存放位置 不重复
       3.使用接口-实现类方式

实体类: 1.快递信息的封装 快递单号、公司名称、取件码、存放位置

信息处理工具类:
        1.规定的命令(即规定数据增删改查)
        2.收发信息的方法(客户端与服务器端)

提取一个IO操作工具类,负责将集合信息保存到磁盘(使用序列化、高速字符流、Properties都可)

首先写流程控制Main,循环主菜单和子菜单,并编写对应的视图层
然后开始编写子菜单中各个功能的实现

addExpress()添加快递的实现:
        0.客户端先向服务器确认快递柜是否存满
        1.从视图层(控制台)获取数据到客户端
        2.客户端通过socket向服务器发送执行命令并将数据发给服务器
        3.服务器端监听到客户端连接,进入子线程,根据客户端命令执行添加操作,
          在添加方法中先接收客户端传来的数据,然后委托dao去添加数据,并将操作结果返回客户端。
        4.客户端接收服务器回写数据,交给视图层展示
isFull():判满实现:
        1.在主流程中调用客户端isFull方法判满
        2.客户端isFull中向服务器发送判满命令,并等待接收服务器回写的布尔数据返回给主流程。
        3.服务器端接收客户端连接,进入子线程,根据客户端命令执行判满操作(获取集合元素个数与
          MAX比较),并将结果回写给客户端。
removeExpress():删除快递的实现:
        1.在主流程中接收视图层输入的单号,然后调用客户端removeExpress删除快递
        2.客户端removeExpress先向服务器发送删除命令并传递参数单号,然后等待服务器回写
        3.服务器接收客户端连接,进入子线程,根据客户端命令执行删除操作,先接收客户端
          传递来的单号,调用dao根据单号实现数据删除
        4.服务器回写数据给客户端,客户端将数据返回到主流程中,在主流程中将结果传到视图层展示。
修改、查询、取件操作整体上和上面流程差不多,
1.都是从主流程中获取视图层输入的数据,
2.然后让客户端将要操作的指令和参数传递到服务器并等待回写
3.服务器接收到客户端连接,执行相应的指令并回写数据给客户端
4.客户端再将回写数据返给主流程,主流程将数据传给视图层展示。
        

bean包的Express实体类

package com.kdz.bean;

import java.io.Serializable;

/**
 * 快递实体类
 */
public class Express implements Serializable {
    private static final long serialVersionUID = 7859017056410332717L; //实现序列化接口
    private String number;//快递单号
    private String company;//快递公司
    private int code;//取件码
    private int target;//快递位置(0-100),显示时按第几排第几列显示,10排10列

    public Express(String number, String company) {
        this.number = number;
        this.company = company;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public int getTarget() {
        return target;
    }

    public void setTarget(int target) {
        this.target = target;
    }

    @Override
    public String toString() {
        return "Express{" +
                "number='" + number + '\'' +
                ", company='" + company + '\'' +
                ", code='" + showTarget()+'}';
    }

    /**
     * 展示快递位置(第几排第几列),target存的时候是int,展示的时候是string
     * @return
     */
    public String showTarget() {
        return target/10+"排"+target%10+"列";
    }
}

MsgUtil信息处理工具类

客户端与服务器间的通信工具

package com.kdz.utils;

import com.kdz.bean.Express;

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

/**C/S通信工具类
 * 信息处理工具类:(只负责Client与Server间的信息处理)
 *    1.规定的命令(即规定数据增删改查)
 *    2.提取客户端与服务器收发信息的方法(即提取IO流操作)
 *
 * 客户端服务端都是靠Socket对象进行通信的,都要使用socket中的网络进行传输数据,
 * 所以C/S通信封装的方法参数都少不了socket对象(借助流)对数据的发送和接收。
 */
public class MsgUtil {
    //定义公开的常量命令(增删改查命令),通过命令,让服务器执行对应的方法
    //服务器需要做的事情
    public static final String CMD_ADD="ADD"; //添加命令
    public static final String CMD_REMOVE="REMOVE"; //删除命令
    public static final String CMD_MODIFY="MODIFY";//修改命令
    public static final String CMD_QUERY="QUERY";//查询命令
    public static final String CMD_SIZE="SIZE"; //查询快递柜中快递量
    public static final String CMD_PICK="PICK"; //取快递
    public static final String CMD_CHECKE_FULL = "FULL";//快递柜判空
    public static final String CMD_FIND_BY_NUMBER  ="FIND_BY_NUMBER";//通过单号判断快递存在否


    /**
     * 1.输出快递对象,客户端和服务器端都会用到
     * 客户端发送express,服务器端接收
     * 服务器端发送express,客户端接收
     * @param socket
     * @param express
     */
    public static void sendExpress(Socket socket, Express express) throws IOException {
        //将socket中网络输出流转换成(装饰)序列化输出流
        //序列化流不仅可以文件上的对象传输,还可以网络上的对象传输,下面就是网络中传输对象
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
        oos.writeObject(express);//输出对象
    }

    /**
     * 2.接收快递对象,客户端和服务器都会用到
     * @param socket
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static Express receiveExpress(Socket socket) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        return (Express) ois.readObject();//读取对象并返回
    }

    /**
     * 发送命令--命令是String类型
     * @param socket
     * @param cmd
     */
    public static void sendStr(Socket socket,String cmd) throws IOException {
        //推荐使用数据流(只要传的不是对象),序列化流有坑,先将socket网络流转成数据流
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeUTF(cmd);//输出字符串
    }

    /**
     * 接收命令--命令是String类型
     * @param socket
     * @return
     * @throws IOException
     */
    public static String receiveCmd(Socket socket) throws IOException {
        //将socket网络流转成数据流
        DataInputStream dis = new DataInputStream(socket.getInputStream());
        return dis.readUTF(); //读取字符串并返回
    }

    /**
     * 发送Boolean类型数据(客户端与服务器间)
     * @param socket
     * @param flag
     * @throws IOException
     */
    public static void sendBoolean(Socket socket,Boolean flag) throws IOException {
        //只要不是传对象,可使用数据流,将socket中网络流转成数据流
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeBoolean(flag);//输出布尔数据
    }

    /**
     * 接收Boolean类型数据(客户端与服务器间)
     * @param socket
     * @throws IOException
     */
    public static boolean receiveBoolean(Socket socket) throws IOException {
        //将socket网络输入流转成数据流
        DataInputStream dis = new DataInputStream(socket.getInputStream());
        return dis.readBoolean();//读取布尔类型
    }

    /**
     * 发送Int类型数据(客户端与服务器间)
     * @param socket
     * @param num
     * @throws IOException
     */
    public static void sendInt(Socket socket,int num) throws IOException {
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeInt(num);//输出Int数据
    }

    /**
     * 接收int类型数据(客户端与服务器间)
     * @param socket
     * @return
     * @throws IOException
     */
    public static int receiveInt(Socket socket) throws IOException {
        DataInputStream dis = new DataInputStream(socket.getInputStream());
        return dis.readInt(); //将读取int数据并返回
    }

    /**
     * 接收list集合(c/s之间通信)
     * 数据是对象,使用序列化流,不使用数据流
     * @param socket
     * @return
     */
    public static ArrayList<Express> receiveList(Socket socket) throws IOException, ClassNotFoundException {
        //socket中网络流转成序列化流
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        ArrayList<Express> list = (ArrayList<Express>) ois.readObject();//读取数据返回list
        return list;
    }

    /**
     * 发送list类型数据
     * @param socket
     * @param list
     * @return
     */
    public static void sendList(Socket socket,ArrayList<Express>list) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
        oos.writeObject(list);//发送数据,若C发送则S接收,反之若S发送则C接收
    }
}

IOUtil数据持久化工具类

package com.kdz.utils;

import com.kdz.bean.Express;

import java.io.*;
import java.util.ArrayList;

/**
 * 数据持久化工具类
 * 1.读取文件-->将数据转成集合
 * 2.将集合-->写入文件
 * 传递的是对象,使用序列化流最方便,对象的序列化操作。
 */
public class IOUtil {
    private static File file=new File("快递信息.txt");//相对路径
    /**
     * 将数据(集合)写入文件
     * 使用序列化流,写到文件的是对象的二进制数据,在文本中是乱码
     * @param list
     */
    public static void writeToFile(ArrayList<Express>list) {
        //将文件流转成序列化流,使用JDK9的异常处理,自动释放资源
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)){
            oos.writeObject(list);//将整个集合序列化到文件中
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 从文件读取数据,使用反序列化流,将二进制读成集合对象
     * 要预防文件不存在时的异常,保证可以拿到一个集合(即使是空的)
     * @return
     */
    public static ArrayList<Express> readFromFile(){
        if (!file.exists()||file.length()==0) return new ArrayList<>();
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))){
            return (ArrayList<Express>) ois.readObject();//读成一个集合并返回
        } catch (IOException |ClassNotFoundException e) {
            e.printStackTrace();
        }
        return new ArrayList<>();
    }
}

Views视图层

package com.kdz.view;
import com.kdz.bean.Express;
import java.util.ArrayList;
import java.util.Scanner;
public class Views {

    private Scanner sc=new Scanner(System.in);
    /**
     * 1.欢迎
     */
    public void welcome(){
        System.out.println("欢迎使用快递驿站");
    }

    /**
     * 3.输入功能选项
     * @param max
     * @return
     */
    public int inputKey(int max){
        System.out.println("请输入选择:");
        String s = sc.nextLine();
        int num=-1;
        try {
            num = Integer.parseInt(s); //转换可能出现异常
        }catch (Exception e){

        }
        if (num<0||num>max){ //输入不合理
            System.out.println("输入错误,请重新输入");
            num=inputKey(max); //输入选项不符合则继续调用方法
        }
        return num;
    }

    /**
     * 2.展示主菜单
     */
    public void showMainMenu(){
        System.out.println("请选择用户类型:");
        System.out.println("1.快递员 2.用户 3.退出程序");
    }

    /**
     * 4.展示管理员子菜单
     */
    public void showAdminMenu() {
        System.out.println("请选择操作:");
        System.out.println("1.增加快递 2.删除快递 3.修改快递 4.查看所有 5.返回主菜单");
    }

    /**
     * 5.展示用户子菜单
     */
    public void showUserMenu() {
        System.out.println("请选择操作:");
        System.out.println("1.取件 2.返回上级菜单");
    }

    /**
     * 6.添加快递信息操作
     * @return
     */
    public Express inputExpress() {
        String number = inputNumber(); //输入快递单号
        String company = inputCompany();//输入快递公司
        return new Express(number,company);

    }
    public String inputNumber() {
        return inputStr("请输入快递单号:");
    }

    private String inputCompany() {
        return inputStr("请输入快递公司:");
    }

    /**
     * 7.封装方法专门在控制台接收用户输入的字符串
     * 输入提示,接收一个字符串
     * @param msg
     * @return
     */
    private String inputStr(String msg){
        System.out.println(msg);
        return sc.nextLine();
    }

    /**
     * 8.视图层显示 客户端向服务器添加快递后服务器的回写信息(传回的是快递)
     * @param add
     */
    public void printResultAdd(Express add) {
        System.out.println(add==null?"添加失败":"添加成功,存储位置:"+add.showTarget()+",取件码:"+add.getCode());
    }

    /**
     * 9.当服务器通过客户端告知主流程快递柜满了,视图层给出提示
     */
    public void full() {
        System.out.println("快递柜已满");
    }

    /**
     * 10.视图层给出删除后提示
     * @param remove
     */
    public void printResultRemove(Boolean remove) {
        System.out.println(remove?"删除成功":"删除失败");
    }

    /**
     * 11.未查到单号
     */
    public void printNone() {
        System.out.println("无此单号信息");
    }

    /**
     * 12.修改快递后的结果
     */
    public void printResultModify(Express modify) {
        System.out.println(modify==null?"修改失败":"修改成功:"+modify);
    }

    /**
     * 13.显示所有快递信息
     * @param list
     */
    public void showList(ArrayList<Express> list) {
        if(list.size()==0){
            showEmpty();
        }else {
            showTitle();
            for (Express express:list){
                System.out.printf("%-9s",express.getNumber());
                System.out.printf("%-9s",express.getCompany());
                System.out.printf("%-9s",express.getCode());
                System.out.printf("%-9s",express.getTarget());
            }
            System.out.println();
        }
    }

    /**
     * 标题:快递字段
     */
    private void showTitle() {
        System.out.printf("%-6s","快递单号");
        System.out.printf("%-6s","公司名称");
        System.out.printf("%-6s","取件码");
        System.out.printf("%-6s","存储位置");
        System.out.println();
    }

    /**
     * 列表中没有快递
     */
    private void showEmpty() {
        System.out.println("暂无任何快递信息");
    }

    /**
     * 输入取件码
     * @return
     */
    public int inputCode() {
        System.out.println("请输入取件码:");
        String s = sc.nextLine();
        int num=-1;
        try {
            num = Integer.parseInt(s); //转换可能出现异常
        }catch (Exception e){

        }
        if (num<100000||num>999999){ //输入不合理
            System.out.println("输入错误,请重新输入");
            num=inputCode(); //输入取件码不符合则继续调用方法
        }
        return num;
    }

    public void printResultPick(Express express) {
        System.out.println(express==null?"取件失败":"取件成功,存储位置:"+express.getTarget());
    }
}

主流程Main

package com.kdz;

import com.kdz.bean.Express;
import com.kdz.client.ExpressClient;
import com.kdz.view.Views;

import java.io.IOException;
import java.util.ArrayList;

//主流程控制,先给出各个功能方法,写好整个流程后再实现各个功能
public class Main {
    static Views v=new Views(); //视图层对象
    static ExpressClient client=new ExpressClient();//客户端对象
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        v.welcome();
        boolean flag=true;//循环开关
        while(flag){
            v.showMainMenu();//显示主菜单
            int key=v.inputKey(3);//用户输入功能号
            switch(key){ //执行对应的功能
                case 1:
                    adminLoop(); //进入管理员菜单循环
                    break;
                case 2:
                    userLoop();//进入用户菜单循环
                    break;
                case 3:
                    flag=false; //关闭循环开关,退出程序
                    client.close();//客户端关闭socket
                    break;
            }

        }
    }



    /**
     * 1.进入管理员子菜单循环
     */
    private static void adminLoop() throws IOException, ClassNotFoundException {
        boolean flag = true;//循环开关
        while (flag) {
            v.showAdminMenu();//显示管理员子菜单
            int key = v.inputKey(5);//用户输入功能号
            //System.out.println("key:"+key);
            switch (key) { //执行对应的功能(case退出switch)
                case 1:
                    addExpress();
                    break;
                case 2:
                    removeExpress();
                    break;
                case 3:
                    modifyExpress();
                    break;
                case 4:
                    findAllExpresses();
                    break;
                case 5:
                    flag=false; //关闭循环开关,退出子菜单
                    break;
            }
        }

    }

    private static void findAllExpresses() throws IOException, ClassNotFoundException {
        ArrayList<Express>list=client.findAll();//客户端向服务器要数据并传递过来
        v.showList(list);//视图层来展示数据
    }

    /**
     * 修改快递信息
     * 需要通过客户端将单号传递给服务器去查有没有这个单号
     * 服务器将查询结果返回客户端再返回到主流程这里
     */
    private static void modifyExpress() throws IOException, ClassNotFoundException {
        String number = v.inputNumber();//在视图层输入单号
        if (client.findByNumber(number)==-1){
            v.printNone();//视图层给出提示
            return;//结束该次修改操作
        }
        //单号存在
        Express express = v.inputExpress();
        Express modify=client.modifyExpress(new Express(number,null),express);//服务器返回修改后对象
        v.printResultModify(modify);//在客户端显示是否修改成功
    }

    /**
     * 删除快递信息
     * 也是通过客户端将待删除信息传递给服务器去删除
     */
    private static void removeExpress() throws IOException {
        String number=v.inputNumber(); //视图层输入待删除快递单号
        Boolean remove=client.removeExpress(number);//客户端将单号传递给服务器删除并接收回写
        v.printResultRemove(remove);
    }

    /**
     * addExpress()添加快递的实现:
     *   0.先去服务器查看快递柜是否满了,不满才能添加
     *   1.从视图层(控制台)获取数据到客户端
     *     获取快递单号,公司名称。(取件号和存储位置由程序负责)
     *   2.客户端通过客户端对象将数据发给服务器
     *   3.接收服务器回写数据,交给视图层展示
     */
    private static void addExpress() throws IOException, ClassNotFoundException {
        //添加前向服务器确认是否满了
        if (client.isFull()){ //快递柜满了
            v.full();  //在视图层给出提示
            return;
        }
        Express express=v.inputExpress();//视图层来添加快递信息
        Express add=client.addExpress(express);//客户端对象将快递发送给服务器
        v.printResultAdd(add); //接收服务器回写数据(回写的是快递对象),交给视图层展示
    }

    /**
     * 2.进入用户子菜单循环
     */
    private static void userLoop() throws IOException, ClassNotFoundException {
        boolean flag=true;//循环开关
        while(flag){
            v.showUserMenu(); //展示用户子菜单
            int key = v.inputKey(2);//用户输入功能号
            switch (key){ //switch执行相应功能
                case 1:
                    pickExpress();
                    break;
                case 2:
                    flag=false;//关闭循环开关,退出子菜单
                    break;
            }
        }
    }

    /**
     * 用户取快递
     */
    private static void pickExpress() throws IOException, ClassNotFoundException {
        int code=v.inputCode();//在视图层输入取件码
        Express express=client.pickExpress(code);//客户端命令服务器取出快递并返回
        v.printResultPick(express);//视图层显示取件结果
    }
}

客户端

package com.kdz.client;

import com.kdz.bean.Express;
import com.kdz.utils.MsgUtil;

import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;

/**
 * 快递的客户端
 * 作为视图层(控制台)和服务器的纽带,只负责数据的传递
 * 客户端与服务器端通信使用MsgUtil工具类即信息处理工具类负责两端通信
 */
public class ExpressClient {
    private Socket socket;

    /**
     * 搭建客户端对象,占用端口号12345,服务器端是自身ip
     * 当创建Socket对象时,就已经启动了客户端
     */
    public ExpressClient(){
        try {
            socket = new Socket("127.0.0.1", 12345);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭流 客户端socket
     */
    public void close(){
        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端咨询服务器快递柜是否满了
     * @return
     */
    public boolean isFull() throws IOException {
        //调用C/S间信息处理工具类,告诉服务器执行CMD_CHECKE_FULL命令表示的方法
        MsgUtil.sendStr(socket,MsgUtil.CMD_CHECKE_FULL);
        return MsgUtil.receiveBoolean(socket);//客户端接收服务器回写信息
    }

    /**客户端添加操作
     * 客户端并不直接添加,而是向服务器发送指令,让服务器调用DAO添加快递信息。
     * 1.发命令
     * 2.发需要的参数
     * 3.收结果
     * @param express
     * @return
     */
    public Express addExpress(Express express)throws IOException,ClassNotFoundException {
        //客户端向服务器发送命令,使用信息处理工具类
        MsgUtil.sendStr(socket,MsgUtil.CMD_ADD); //告诉服务器要添加操作
        //向服务器传递要添加的快递
        MsgUtil.sendExpress(socket,express);
        //服务器添加完成,客户端接收回写数据(是对象)
        Express add = MsgUtil.receiveExpress(socket);
        return add;
    }

    /**
     * 客户端删除操作
     * 向服务器发送命令和参数,等待服务器接收并执行命令后回写数据,客户端再接收
     * @param number
     * @return
     * @throws IOException
     */
    public Boolean removeExpress(String number) throws IOException {
        MsgUtil.sendStr(socket,MsgUtil.CMD_REMOVE); //向服务器发送命令
        MsgUtil.sendStr(socket,number);//向服务器发送参数
        boolean remove = MsgUtil.receiveBoolean(socket);//接收服务器回写数据
        return remove;
    }

    /**
     * 客户端根据单号查找快递是否存在
     * 向服务器发送查找命令并传递参数number,并等待服务器端响应数据回来
     * @param number
     * @return
     */
    public int findByNumber(String number) throws IOException {
        MsgUtil.sendStr(socket,MsgUtil.CMD_FIND_BY_NUMBER);//向服务器发送查找命令
        MsgUtil.sendStr(socket,number);//向服务器发送需要的数据
        int num=MsgUtil.receiveInt(socket);//客户端等待接收服务器响应的数据,不存在则返回-1
        return num;
    }

    /**修改快递信息
     * 发送命令
     * 传递数据(对象)
     * 接收服务器响应数据
     * @param oldExpress
     * @param newExpress
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Express modifyExpress(Express oldExpress, Express newExpress) throws IOException, ClassNotFoundException {
        MsgUtil.sendStr(socket,MsgUtil.CMD_MODIFY);//客户端向服务器发送修改命令
        MsgUtil.sendExpress(socket,oldExpress);//向服务器传递旧对象
        MsgUtil.sendExpress(socket,newExpress);//向服务器传递新对象
        return MsgUtil.receiveExpress(socket);//等待接收服务器响应的数据(修改后的快递)
    }

    public ArrayList<Express> findAll() throws IOException, ClassNotFoundException {
        MsgUtil.sendStr(socket,MsgUtil.CMD_QUERY);//客户端向服务器发送查询命令去查询
        ArrayList<Express>list=MsgUtil.receiveList(socket);
        //客户端等待接收服务器传回的查询信息,接收后返回
        return list;
    }

    /**
     * 取快递(根据取件码删除快递)
     * 向服务器发送命令去执行取快递操作
     * 向服务器发送需要的数据socket和code
     * 然后等待接收服务器返回的数据
     * @param code
     * @return
     */
    public Express pickExpress(int code) throws IOException, ClassNotFoundException {
        MsgUtil.sendStr(socket,MsgUtil.CMD_PICK);//发送命令
        MsgUtil.sendInt(socket,code);//发送数据
        Express express = MsgUtil.receiveExpress(socket);//接收数据(取出的快递)
        return express;
    }
}

服务器端

package com.kdz.server;

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

/**
 * 服务器端
 * 使用客户端socket连接进行通信
 * 使用多线程技术,一个服务器接收多个客户端
 */
public class ExpressServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(12345);//搭建服务器并启动
        System.out.println("服务器启动成功");
        while(true){ //时刻监听客户端连接
            Socket accept = ss.accept(); //获取客户端连接(即socket)
            new Thread(new ExpressRunnable(accept)).start();//启动线程
        }
    }
}

服务器端子线程任务

package com.kdz.server;

import com.kdz.bean.Express;
import com.kdz.dao.ExpressDao;
import com.kdz.dao.impI.ExpressDaoImpI;
import com.kdz.utils.MsgUtil;

import java.io.IOException;
import java.net.Socket;

/**
 * 服务器端线程任务,实现快递增删改查,实际上是调用Dao进行具体实现
 */
public class ExpressRunnable implements Runnable {
    private Socket socket; //声明
    private ExpressDao dao=new ExpressDaoImpI();

    /**
     * 构造器,在服务器端获取客户端连接socket,从而可获取客户端传递来的信息
     * @param accept
     */
    public ExpressRunnable(Socket accept) {
        this.socket = accept;
    }

    @Override
    public void run() {//线程任务
        while(true){
            try {
                //服务器接收客户端传递来的命令
                String cmd = MsgUtil.receiveCmd(socket);
                if (cmd==null) break;
                switch (cmd){
                    case MsgUtil.CMD_ADD: //添加命令
                        System.out.println("服务器端开始执行添加操作");
                        addExpress();
                        break;
                    case MsgUtil.CMD_CHECKE_FULL: //判满命令
                        checkFull();
                        break;
                    case MsgUtil.CMD_REMOVE://删除命令
                        System.out.println("服务器端开始执行删除命令");
                        removeExpress();
                        break;
                    case MsgUtil.CMD_FIND_BY_NUMBER:
                        System.out.println("服务器开始通过单号查找快递是否存在");
                        findNumber();
                        break;
                    case MsgUtil.CMD_MODIFY:
                        System.out.println("服务器开始修改快递信息");
                        modifyExpress();
                        break;
                    case MsgUtil.CMD_QUERY:
                        System.out.println("服务器开始查询所有快递信息");
                        findAll();
                        break;
                    case MsgUtil.CMD_PICK:
                        System.out.println("服务器开始取快递");
                        pickExpress();
                        break;
                     default:
                         System.out.println("无此指令");
                         break;
                }
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
                try{
                    socket.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
                break;
            }
        }
    }

    /**
     * 服务器取快递
     * 即通过取件码删除数据
     * 先接收客户端传递参数code
     * 然后调用dao去根据code删除
     * 然后响应客户端数据
     */
    private void pickExpress() throws IOException {
        int code = MsgUtil.receiveInt(socket);//接收取件码code
        Express express = dao.removeByCode(code);//取出快递
        MsgUtil.sendExpress(socket,express);//响应客户端,返回被删除数据
    }

    /**
     * 服务器调用Dao查询所有快递信息
     * 客户端ExpressClient中findAll方法中并没有向服务器传递数据,这里不需接收
     * 但客户端在等着接收服务器返回的查询结果,这里要向客户端发送查询结果
     */
    private void findAll() throws IOException {
        MsgUtil.sendList(socket,dao.getAll());
    }

    /**服务器调用dao修改快递信息
     * 客户端ExpressClient中modifyExpress方法向服务器发送修改命令,开始执行下面方法,
     * 并传递两个快递对象,在下面进行接收
     */
    private void modifyExpress() throws IOException, ClassNotFoundException {
        //接收客户端传递的两个快递对象
        Express oldExpress = MsgUtil.receiveExpress(socket);
        Express newExpress = MsgUtil.receiveExpress(socket);
        Express modify = dao.modify(oldExpress, newExpress);//调用dao去操作数据修改
        MsgUtil.sendExpress(socket,modify);//响应客户端,回写数据,将修改后的快递返回
    }

    /**
     * 服务器端调用DAO查询指定单号快递是否存在
     * ExpressClient客户端中findByNumber方法向服务器发送命令和参数,在这里接收参数
     */
    private void findNumber() throws IOException {
        String number = MsgUtil.receiveCmd(socket);
        int index = dao.findByNumber(number);
        MsgUtil.sendInt(socket,index);//服务器将查询数据发送到客户端,客户端一直等待接收
    }

    /**
     * 服务器调用DAO删除指定单号的数据
     */
    private void removeExpress() throws IOException {
        String number = MsgUtil.receiveCmd(socket);
        //客户端发送删除命令,在这里接收命令,并读取传递来的number单号
        boolean remove = dao.removeByNumber(number);//调用DAO删除指定数据,返回布尔
        MsgUtil.sendBoolean(socket,remove);//服务器将布尔结果回写,客户端还在等着接收

    }

    /**
     * 服务器端调用dao判断快递柜是否已满,将结果直接发送到客户端,客户端调用方法接收
     */
    private void checkFull() throws IOException {
        MsgUtil.sendBoolean(socket,dao.checkFull());
    }

    /**
     * 服务器添加数据,调用DAO进行快递添加操作
     */
    private void addExpress() throws IOException, ClassNotFoundException {
        Express express = MsgUtil.receiveExpress(socket);//服务器接收客户端传递的快递
        Express add= dao.add(express);  //调用dao添加数据
        MsgUtil.sendExpress(socket,add);//将添加结果发送给客户端
    }
}

dao接口

package com.kdz.dao;

import com.kdz.bean.Express;

import java.util.ArrayList;

public interface ExpressDao {
    Express add(Express express);//添加快递

    boolean checkFull(); //判满

    Express removeByCode(int code);//通过取件码删除快递

    boolean removeByNumber(String number);//通过快递单号删除快递

    int findByNumber(String number);

    Express modify(Express oldExpress, Express newExpress);//修改快递信息

    ArrayList<Express>getAll();//获取装快递的集合
}

dao实现类

package com.kdz.dao.impI;

import com.kdz.bean.Express;
import com.kdz.dao.ExpressDao;
import com.kdz.utils.IOUtil;

import java.util.ArrayList;

/**
 * dao层操作数据增删改查的地方
 */
public class ExpressDaoImpI implements ExpressDao {
    private ArrayList<Express> data;//若增删改多,可以使用linkedlist

    private final int MAX=100; //快递柜最大存储数量

    //使用构造代码块完成集合初始化,构造方法也可
    //当创建Dao层对象时就会进行一个从磁盘读取数据的操作
    {
        data=IOUtil.readFromFile();
    }

    /**
     * //定义快递存放位置,不重复
     * @return
     */
    private int initTarget(){
        int target;
        do {
            target=(int)(Math.random()*MAX);//随机产生[0,100)之间的数
        }while (findTarget(target)!=-1);//
        return target; //返回没有被占用的坑位
    }

    /**
     * //判断指定位置是否已有快递
     * @param target
     * @return
     */
    private int findTarget(int target){
        //遍历集合,判断已有快递的target是否有与当前target一致
        for (int i = 0; i <data.size(); i++) {
            if (data.get(i).getTarget()==target){ //这个坑位被占用了
                return i;
            }
        }
        return -1;//坑位没有被占用
    }

    /**
     * 定义快递取件码,不重复
     * @return
     */
    private int initCode(){
        int code;
        do{
            code= (int)(Math.random() * 900000 + 100000);
        }while (findCode(code)!=-1);
        return code;
    }

    /**
     * 判断取件码是否已被使用
     * @param code
     * @return
     */
    private int findCode(int code) {
        //遍历集合,判断集合中已有快递的取件码与当前取件码是否有相同
        for (int i = 0; i < data.size(); i++) {
            if (data.get(i).getCode()==code){
                return i;
            }
        }
        return -1;
    }

    /**
     * 判断快递柜是否已经满了
     * @return
     */
    @Override
    public boolean checkFull() {
        return data.size()>=MAX; //若集合元素>=MAX则满
    }

    //以上是添加快递前的准备操作

    /**
     *添加快递信息
     * @param express
     * @return
     */
    @Override
    public Express add(Express express) {
        if(checkFull()){//满了
            return null;
        }
        express.setCode(initCode());
        express.setTarget(initTarget());
        data.add(express);//向集合添加快递
        IOUtil.writeToFile(data);//同步到文件中
        return express;
    }


    /**
     * 通过取件号删除快递
     * @param code
     * @return
     */
    @Override
    public Express removeByCode(int code) {
        int index = findCode(code);//先根据code查找
        if (index==-1) return null;//没找到
        Express remove= data.remove(index);//找到,移除
        IOUtil.writeToFile(data);//同步到文件中
        return remove;
    }

    /**
     * 通过单号删除快递
     * @param number
     * @return
     */
    @Override
    public boolean removeByNumber(String number) {
        int index=findByNumber(number);//查找对应单号快递所在索引
        if (index==-1) return false; //没找到,删除失败
        data.remove(index);//找到,并删除
        IOUtil.writeToFile(data);//同步到文件中
        return true;
    }

    /**
     * 通过单号查找快递,返回索引(位置),找不到返回-1
     * @param number
     * @return
     */
    @Override
    public int findByNumber(String number) {
        for (int i = 0; i < data.size(); i++) {
            if (number.equals(data.get(i).getNumber())){
                return i;
            }
        }
        return -1;
    }

    /**
     * 修改快递信息
     * 旧快递只保存了单号信息,通过单号信息找到旧快递位置,
     * 新快递(单号和公司名称由控制台输入),取件码随机生成,然后将新快递放到就快点位置
     * @param oldExpress
     * @param newExpress
     * @return
     */
    @Override
    public Express modify(Express oldExpress, Express newExpress) {
        int index = findByNumber(oldExpress.getNumber());
        if (index!=-1){//通过单号找到待修改快递位置index
            newExpress.setTarget(data.get(index).getTarget());
            newExpress.setCode(initCode());
            data.set(index,newExpress);
            IOUtil.writeToFile(data);//同步到文件中
            return newExpress;
        }
        return null;
    }

    /**
     * 获取所有快递信息
     * @return
     */
    @Override
    public ArrayList<Express> getAll() {
        return data;
    }
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值