TCP多人聊天室实现 JSON文件解析

TCP多人聊天室实现 JSON文件解析

一,TCP多人聊天室实现

1.1 客户端

1.1.1 客户发送数据线程

package com.qfedu.a_charoom.client;

import com.qfedu.a_charoom.util.CloseUtil;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 * @author Anonymous
 * @description 发送端
 * @date 2020/3/9 10:01
 *
 * 这里需要输出流
 *      输出流对象需要通过Socket获取,在当前客户端连接到服务器之后,Socket对象存在
 *      的情况下才可以启动的
 * 成员变量:
 *      输出流
 *          DataOutputStream
 *      需要一个从键盘上输入数据使用的输入流,获取用户输入信息
 *          BufferedReader
 *      标记是否连接
 * 构造方法:
 *      需要Socket作为当前构造的参数,第一次访问服务器需要带有用户名,用于注册
 * 成员方法:
 *      发送数据给服务器
 *      从键盘上获取用户的数据
 */
public class ClientSend implements Runnable {

    /**
     * 基于Socket获取的输出流对象,用于发送数据给服务器
     */
    private DataOutputStream outputStream;

    /**
     * 从键盘上获取用户输入的BufferedReader字符缓冲输入流
     */
    private BufferedReader console;

    /**
     * 是否连接
     */
    private boolean connection;

    /**
     * 使用客户端和服务器连接使用的Socket对象,和用户指定的用户名创建
     * ClientSend线程对象,同时初始化输出流和键盘录入输入流对象
     *
     * @param socket   客户端连接服务器对应的Socket对象
     * @param userName 用户指定的用户名,用于服务器注册
     */
    public ClientSend(Socket socket, String userName) {
        // 初始化输出流和输出流
        try {
            outputStream = new DataOutputStream(socket.getOutputStream());
            console = new BufferedReader(new InputStreamReader(System.in));

            // 发送用户名给服务器注册 需要完成一个send方法
            send(userName);
            connection = true;
        } catch (IOException e) {
            e.printStackTrace();
            // 连接标记关闭,同时处理对应的输入流和键盘录入流
            connection = false;
            CloseUtil.closeAll(outputStream, console);
        }

    }

    /**
     * 从键盘上获取用户输入的数据
     *
     * @return 用户输入的数据字符串形式
     */
    public String getMsgFromConsole() {
        String msg = null;

        try {
            // 从键盘上读取一行数据
            msg = console.readLine();
        } catch (IOException e) {
            e.printStackTrace();
            /*
            发生异常:
                1. connection连接标记改成false
                2. 不是null需要关闭资源
                    outputStream, console
             */
            connection = false;
            CloseUtil.closeAll(outputStream, console);
        }

        return msg;
    }

    /**
     * 发送数据给服务器
     *
     * @param msg 需要发送给服务器的数据
     */
    public void send(String msg) {
        // 如果这里数据为null,或者"" 不发送
        try {
            if (msg != null && !"".equals(msg)) {

                outputStream.writeUTF(msg);
                outputStream.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
            /*
            发生异常:
                1. connection连接标记改成false
                2. 不是null需要关闭资源
                    outputStream, console
             */
            connection = false;
            CloseUtil.closeAll(outputStream, console);
        }
    }

    /**
     * 线程代码,只要当前connection是连接状态,一直执行send和getMsgFromConsole
     */
    @Override
    public void run() {
        while (connection) {
            send(getMsgFromConsole());
        }
    }
}

1.1.2 客户端接收数据线程

package com.qfedu.a_charoom.client;

import com.qfedu.a_charoom.util.CloseUtil;

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

/**
 * @author Anonymous
 * @description
 * @date 2020/3/9 10:01
 *
 * 客户端接收数据线程
 *
 * 这里需要输入流
 *      输入流是通过Socket对象获取的,也就是在客户端连接服务器之后才可以获取到输入流
 * 成员变量:
 *      输入流
 *          DataInputStream
 *      标记是否连接
 * 构造方法:
 *      需要Socket作为当前构造的参数
 * 成员方法:
 *      从服务器接收数据,展示
 */
public class ClientReceive implements Runnable {

    /**
     * 用于接收数据的输入流对象
     */
    private DataInputStream inputStream;

    /**
     * 是否连接状态标记
     */
    private boolean connection;

    /**
     * 根据客户端连接服务器对应的Socket对象获取输入流对象
     *
     * @param socket 客户端连接服务器对应的Socket
     */
    public ClientReceive(Socket socket) {
        try {
            inputStream = new DataInputStream(socket.getInputStream());
            connection = true;
        } catch (IOException e) {
            e.printStackTrace();
            connection = false;
        }
    }

    /**
     * 接收数据并且展示
     */
    public void receive() {
        String msg = null;

        try {
            msg = inputStream.readUTF();
        } catch (IOException e) {
            e.printStackTrace();
            /*
            接收数据出现异常:
                连接标记修改
                不是null关闭资源
             */
            connection = false;
            CloseUtil.closeAll(inputStream);
        }

        System.out.println(msg);
    }

    /**
     * 线程核心方法,只要连接状态OK,始终保存接收状态
     */
    @Override
    public void run() {
        while (connection) {
            receive();
        }
    }
}

1.1.3 客户端

package com.qfedu.a_charoom.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author Anonymous
 * @description
 * @date 2020/3/9 10:01
 *
 * 完成内容
 *      1. 连接服务器
 *      2. 启动接收端线程和发送端线程
 * 需要注意:
 *      提供给服务器一个用户名,这里是在连接Socket获取之后,第一次发送数据
 *      给服务器时需要提供的,也就是第一次启动Send发送线程完成的
 */
public class Client {
    public static void main(String[] args) {
        // 从键盘上获取用户输入的数据
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        Socket socket = null;
        String name = null;
        try {
            System.out.println("请输入你的用户名:");
            name = br.readLine();

            if ("".equals(name)) {
                return;
            }

            // 存在连接异常情况,考虑捕获异常处理
            socket = new Socket("192.168.31.154", 8888);
        } catch (IOException e) {
            System.err.println("连接失败!!!");
            try {
                br.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            System.exit(0);
        }


        // 使用线程池启动两个线程,一个是发送一个接受
        ExecutorService pool = Executors.newFixedThreadPool(2);

        pool.submit(new ClientSend(socket, name));
        pool.submit(new ClientReceive(socket));
    }
}

1.2 服务端

package com.qfedu.a_charoom.server;

import com.qfedu.a_charoom.util.CloseUtil;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;

/**
 * @author Anonymous
 * @description
 * @date 2020/3/9 11:12
 *
 * 服务器需要转发数据
 *      1. 数据转发
 *             群聊
 *             私聊
 *      2. 用户注册
 *          用户 ==> class
 *
 * 私聊,群聊属于用户功能
 *      用户这里看做是一个类
 *      成员变量
 *          输入流
 *          输出流
 *          用户ID
 *          用户名
 *          连接状态标记
 *      成员方法:
 *          接收方法
 *              利用客户端连接服务器对应的Socket得到输入流接收用户发送的数据
 *          发送方法
 *              群聊
 *                  遍历整个有效用户
 *              私聊
 *                  找到对应用户
 *              利用客户端连接服务器对应的Socket得到输出流发送数据
 * 【成员内部类】
 *      用户做出一个成员内部类
 *      作为Server服务器类的一个成员变量内部类
 *
 *  用户注册流程
 *       1. ServerSocket Accept客户端连接,获取对应Socket对象
 *       2. 记录在线人数,创建一个新的UserSocket
 *       3. Map中映射对应的UserSocket,Key 为ID, Value是UserSocket
 *
 */
public class Server {

    /**
     * 用户ID和UserSocket映射
     */
    private HashMap<Integer, UserSocket> userMap;

    /**
     * 累加访客人数
     */
    private static int count = 0;

    /**
     * Server构造方法,用于初始化底层保存数据的HashMap双边队列
     */
    public Server() {
        userMap = new HashMap<>();
    }

    /**
     * 服务器启动方法
     *
     * @throws IOException IO异常
     */
    public void start() throws IOException {
        // 启动服务器,同时监听8888端口
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务器启动");

        // 服务器始终处于一个保存连接的状态
        while (true) {
            // 接收客户端请求,得到一个Socket对象
            Socket socket = serverSocket.accept();

            // 创建UserSocket用于注册,并且保存到userMap当中
            count += 1;
            UserSocket userSocket = new UserSocket(count, socket);
            userMap.put(count, userSocket);

            // 启动当前UserSocket服务
            new Thread(userSocket).start();
        }
    }

    /**
     * @author Anonymous
     * @description 用户Socket类,需要完成绑定操作,并且是一个线程类
     * @date 2020/3/9 11:23
     */
    class UserSocket implements Runnable {

        /*
         * 对应当前客户端连接服务器对应Socket生成输入流和输出流
         */
        private DataInputStream inputStream;
        private DataOutputStream outputStream;

        /**
         * 用户ID号,是当前用户的唯一表示,不可以重复
         */
        private int userId;

        /**
         * 用户名,用于注册,同时在发送数据时给予其他用户标记
         */
        private String userName;

        /**
         * 是否连接状态标记
         */
        private boolean connetion;

        /**
         * 创建UserSocket对象,需要的参数使用userID,和对应的Socket对象
         *
         * @param userId 当前用户的ID号
         * @param socket 客户端连接服务器对应的Socket
         */
        public UserSocket(int userId, Socket socket) {
            this.userId = userId;

            try {
                inputStream = new DataInputStream(socket.getInputStream());
                outputStream = new DataOutputStream(socket.getOutputStream());
                connetion = true;
            } catch (IOException e) {
                e.printStackTrace();
                connetion = false;
            }

            try {
                 // 用户在创建Send线程时,首先会将用户的名字发送给服务器
                assert inputStream != null;
                this.userName = inputStream.readUTF();

                // 广播告知所有人,ID:XXX 姓名: XXX 上线 【群聊,系统广播】
                sendOther("ID:" + this.userId + " " + this.userName + "来到直播间", true);
                // 服务器告诉当前客户端,你已经进入聊天室
                send("欢迎来到聊天室");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 接收客户端发送的数据,用于转发操作
         *
         * @return 用户发送的数据
         */
        public String receive() {
            String msg = null;

            try {
                // 接收用户发送到服务器需要服务器转发的数据
                msg = inputStream.readUTF();
            } catch (IOException e) {
                e.printStackTrace();
                /*
                发生异常:
                    1. 连接状态修改
                    2. 关闭资源
                    3. 删除在userMap中对应的数据【留下】

                    对号是书签 快捷键 F11
                    查看当前项目所有的书签 ALT + 2
                 */
                connetion = false;
                CloseUtil.closeAll(inputStream, outputStream);
            }

            return msg;
        }

        /**
         * 发送数据到客户端
         *
         * @param msg 需要发送的数据
         */
        public void send(String msg) {
            try {
                outputStream.writeUTF(msg);
                outputStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
                /*
                发生异常:
                    1. 连接状态修改
                    2. 关闭资源
                    3. 删除在userMap中对应的数据
                 */
                connetion = false;
                CloseUtil.closeAll(inputStream, outputStream);
            }
        }

        /*
        这里需要一个方法
            1. 私聊判断
            2. 群聊
        这里需要根据msg进行判断
            1. 如果是@数字: 前缀
                私聊
                    通过HashMap --> ID --> UserSocket --> 转发消息
            2. 非@数字:开头群聊
                a. 系统播报
                b. 私人发送
                这里需要做一个标记
                    获取所有在线用户,判断除自己之外,其他人转发消息
         */

        /**
         * 转发数据判断方法,msg需要处理,选择对应的私聊和群发,同时要判断是否是系统发送消息
         *
         * @param msg 需要转发的消息
         * @param sys 系统标记
         */
        public void sendOther(String msg, boolean sys) {
            if (msg.startsWith("@") && msg.contains(":")) {
                // 私聊
                // @1:XXXXXX
                Integer id = Integer.parseInt(msg.substring(1, msg.indexOf(":")));
                String newMsg = msg.substring(msg.indexOf(":"));

                UserSocket userSocket = userMap.get(id);
                // 如果没有对应的UserSocket用户存在,无法发送消息
                if (userSocket != null) {
                    // ID:1 小磊磊悄悄的对你说:XXXX
                    userSocket.send("ID:" + this.userId + " " + this.userName + "悄悄的对你说" + msg);
                }

            } else {
                // 群聊
                // 从userMap中获取对应的所有value,也就是所有UserSocket对象Collection集合
                Collection<UserSocket> values = userMap.values();
                for (UserSocket userSocket : values) {
                    // 不需要将消息发送给自己
                    if (userSocket != this) {
                        // 判断是不是系统消息
                        if (sys) {
                            userSocket.send("系统公告:" + msg);
                        } else {
                            userSocket.send("ID:" + this.userId + " " + this.userName + msg);
                        }
                    }
                }
            }
        }

        /**
         * 线程代码
         */
        @Override
        public void run() {
            while (connetion) {
                // 使用receive收到的消息作为参数,同时标记非系统消息,调用sendOther
                sendOther(receive(), false);
            }
        }
    }

    public static void main(String[] args) {

        // 启动服务器!!!
        Server server = new Server();

        try {
            server.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

1.3 工具类

package com.qfedu.a_charoom.util;

import java.io.Closeable;
import java.io.IOException;

/**
 * @author Anonymous
 * @description 关闭资源的工具类
 * @date 2020/3/9 15:02
 */
public class CloseUtil {
    /**
     * 关闭代码中所需资源的close工具类方法
     *
     * @param closeable 要求为符合Closeable接口的实现类对象,为不定长参数
     */
    public static void closeAll(Closeable... closeable) {
        /*
        不定长参数可以按照数组的形式来处理
         */
        try {
            for (Closeable source : closeable) {
                if (source != null) {
                    source.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

二,JSON文件解析

2.1 数据格式

2.1.1 JSON对象

{
"ID":001,
"name":"骚磊",
"age":16
}
特征:
1. 数据形式键值对形式
"键":值
2. 数据支持 字符串,数字,true false
3. {} 大括号以内的数据

2.1.2 JSON对象数组

[
{
"ID":1,
"name":"骚磊",
"age":16
},
{
"ID":2,
"name":"骚杰",
"age":66
},
{
"ID":3,
2.2.3 JSON数据验证
JSON格式验证
2.3 解析JSON格式工具
2.3.1 FastJson内容
"name":"康康",
"age":15
}
]
特征:
1. 数据使用[]包含
2. 在[]都是JSON格式对象
3. 每一个对象之间使用逗号隔开,同时最后一个元素不
需要逗号 

2.2 FastJson内容

JSON核心类
JSON核心类提供解析和转化方法,用于解析JSON数据格
式,同时用于转换类对象到JSON格式,该类对象需要符合
JavaBean规范
--| JSONArray
存在按照键值对方式解析获取数据,同时存在一定
的List方法
--| JSONObject
获取对应的类对象,指定键值对对应数据的方法

2.3 解析演示

2.3.1 student类代码

package com.qfedu.b_json;

/*
完成一个符合JavaBean规范的类
*/
public class Student {
    private String name;
    private Integer age;
    
    // 这里根据需要完成对应的Setter和getter方法

    public Student() {
    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
        }
	}

2.3.2 FastJson演示

package com.qfedu.b_json;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import jdk.nashorn.internal.ir.CallNode;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Anonymous
 * @description FastJson演示
 * @date 2020/3/9 16:34
 */
public class Demo1 {
    public static void main(String[] args) {
        ArrayList<Student> list = new ArrayList<>();
        Student stu = new Student("骚磊", 16);
        list.add(stu);
        list.add(new Student("骚杰", 66));
        list.add(new Student("老黑", 56));
        list.add(new Student("老付", 36));
        list.add(new Student("老高", 66));

        /*
        JavaBean List ==> Json String
        */
        String s = JSON.toJSONString(list);
        System.out.println(s);

        System.out.println("--------------------------------------");

        /*
        JavaBean Student类对象 ==> Json String
         */
        String s1 = JSON.toJSONString(stu);
        System.out.println(s1);

        /*
        Json String == Java Bean Student
         */
        Student student = JSON.parseObject(s1, Student.class);
        System.out.println(student);


        /*
        Json String ==> Json Object
         */
        JSONObject jsonObject = JSON.parseObject(s1);
        String name = jsonObject.getString("name");
        System.out.println(name);
        System.out.println(jsonObject.getInteger("age"));

        /*
        Json String ==> JsonArray
         */
        JSONArray objects = JSON.parseArray(s);
        System.out.println(objects);

        for (Object object : objects) {
            JSONObject jsonObject1 = (JSONObject) object;

            System.out.println(jsonObject1.getString("name"));
            System.out.println(jsonObject1.getInteger("age"));
        }


        Student object = objects.getObject(2, Student.class);
        System.out.println(object);

        System.out.println("--------------------------------------");
        /*
        JSONArray ==> JavaBean List集合
         */
        List<Student> students = objects.toJavaList(Student.class);
        for (Student student1 : students) {
            System.out.println(student1);
        }

        System.out.println("--------------------------------------");
    }
}
发布了41 篇原创文章 · 获赞 0 · 访问量 1730

猜你喜欢

转载自blog.csdn.net/qq_39544980/article/details/104764016