Java多用户即时通讯系统

项目学习说明

项目学习自哔哩哔哩的韩顺平老师的课程,Java网络多线程专题 - TCP UDP Socket编程 多线程 并发处理 文件传输 新闻推送 Java

该项目主要对Java网络编程和多线程进行一个熟悉与锻炼;项目仅供学习使用

系统涉及知识说明

系统涉及的知识:多线程、网络通讯、数据库、集合

这里的交流格式采用对象流

采用Scanner输入对空格问题的解决

采用 scanner.useDelimiter(“\n”); 设置为换行即可
工具类如下

package chat.base;

import java.util.Scanner;

/**
 * @author bbyh
 * @date 2022/11/9 0009 23:41
 * @description
 */
public class ScannerUtil {
    private static final Scanner SCANNER = new Scanner(System.in);

    public static String read(int size) {
        SCANNER.useDelimiter("\n");
        String next = SCANNER.next();
        if (next.length() <= size) {
            return next;
        }
        return next.substring(0, size);
    }
}

遇到的对象字节流问题

正常写入读出对象,但是报错 java.io.StreamCorruptedException: invalid type code: AC
查看一些文章表示是 Socket 采用对象字节流进行写入的时候会写入头部,然后读出的时候读出头部,但是不是每一次都写,就很奇怪
参考文章:java.io.StreamCorruptedException: invalid type code: AC错误解决

解决方式就是自己重写Object输入输出的两个方法,具体原因还没有查明

目前的实现了私聊的简单版本

项目链接:https://pan.baidu.com/s/12UygVcYiXreJ4Qy7mPImGQ
提取码:0925

关于对象字节流报错问题解决

报错的文件及复现项目:StreamCorruptedException异常的产生和应注意的点
项目地址:https://pan.baidu.com/s/1pEyWgLlu-gU3tMMSjLg96Q
提取码:0925

给出的建议:如果你使用socket,并通过对象输入/输出流来处理的话,并且已经对某个socket调用了一次getInputStream时,但又需要把这个socket的相关信息作为参数传递给别的对象时,应注意:不用直接把socket传过去,应该把对应的ObjectInputStream或ObjectOutputStream对象传递过去

对于文件及一般的Socket调用对象字节流的测试

经测试发现文件流,写入读出多个同一类型的对象是没问题的,多个不同对象也是没问题的,不管顺序是否打乱

package chat.test;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * @author bbyh
 * @date 2022/11/9 0009 19:17
 * @description
 */
public class Test {
    public static void main(String[] args) throws Exception {
        String filePath = "D:/test1.txt";
        Path path = Paths.get(filePath);

        ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(path));
        oos.writeObject(new Person("Hello1", 20));
        oos.writeObject(new Person("Hello2", 22));

        ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(path));
        Object o1 = ois.readObject();
        Object o2 = ois.readObject();

        System.out.println(o1);
        System.out.println(o2);
    }
}

class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

经测试发现,Socket在连续发送对象且只有单向通信时也并不会出现无法解析的情况

package chat.test;

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

import static chat.test.Client.PORT;

/**
 * @author bbyh
 * @date 2022/11/9 0009 19:32
 * @description
 */
public class Server {
    public static void main(String[] args) throws Exception {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            Socket accept = serverSocket.accept();
            ObjectInputStream ois = new ObjectInputStream(accept.getInputStream());
            Object o1 = ois.readObject();
            Object o2 = ois.readObject();

            System.out.println(o1);
            System.out.println(o2);
        }
    }
}


package chat.test;

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

/**
 * @author bbyh
 * @date 2022/11/9 0009 19:17
 * @description
 */
public class Client {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9090;

    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket(IP, PORT)) {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(new Person("Hello1", 20));
            oos.writeObject(new Man("Hello2", 21));
        }
    }
}


package chat.test;

import java.io.*;

/**
 * @author bbyh
 * @date 2022/11/9 0009 19:17
 * @description
 */
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

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


package chat.test;

import java.io.*;

/**
 * @author bbyh
 * @date 2022/11/9 0009 19:17
 * @description
 */
public class Man implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String name;
    private final int age;

    public Man(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

就算是双向通信一次也并没有出现解析出错的情况

package chat.test;

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

import static chat.test.Client.PORT;

/**
 * @author bbyh
 * @date 2022/11/9 0009 19:32
 * @description
 */
public class Server {
    public static void main(String[] args) throws Exception {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            Socket accept = serverSocket.accept();
            ObjectInputStream ois = new ObjectInputStream(accept.getInputStream());
            Object o1 = ois.readObject();
            Object o2 = ois.readObject();

            System.out.println(o1);
            System.out.println(o2);

            ObjectOutputStream oos = new ObjectOutputStream(accept.getOutputStream());
            oos.writeObject(new Person("Hello3", 22));
            oos.writeObject(new Man("Hello4", 23));
            accept.shutdownOutput();
        }
    }
}


package chat.test;

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

/**
 * @author bbyh
 * @date 2022/11/9 0009 19:17
 * @description
 */
public class Client {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9090;

    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket(IP, PORT)) {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(new Person("Hello1", 20));
            oos.writeObject(new Man("Hello2", 21));
            socket.shutdownOutput();

            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Object o1 = ois.readObject();
            Object o2 = ois.readObject();

            System.out.println(o1);
            System.out.println(o2);
        }
    }
}

对Socket调用了getInputStream或getOutputStream,并再次作为参数传递的测试

复现了,确实是由于调用了两次 getInputStream 导致的不匹配问题
对于直接写入文件的对象字节流 ,是可以发现写入的时候并没有都加 开头的 AC ED 00 05
于是再次进行获取 getInputStream,则报出错误,这实际上是反序列化漏洞,反序列化漏洞整理

package chat.test;

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

import static chat.test.Client.PORT;

/**
 * @author bbyh
 * @date 2022/11/9 0009 19:32
 * @description
 */
public class Server {
    public static void main(String[] args) throws Exception {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            while (true) {
                Socket accept = serverSocket.accept();
                ObjectInputStream ois = new ObjectInputStream(accept.getInputStream());
                Object o1 = ois.readObject();
                Object o2 = ois.readObject();

                System.out.println(o1);
                System.out.println(o2);

                ObjectOutputStream oos = new ObjectOutputStream(accept.getOutputStream());
                oos.writeObject(new Person("Hello3", 22));
                oos.writeObject(new Man("Hello4", 23));
                accept.shutdownOutput();
                accept.close();
            }
        }
    }
}


package chat.test;

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

/**
 * @author bbyh
 * @date 2022/11/9 0009 19:17
 * @description
 */
public class Client {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9090;

    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket(IP, PORT)) {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(new Person("Hello1", 20));
            oos.writeObject(new Man("Hello2", 21));
            socket.shutdownOutput();

            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Object o1 = ois.readObject();
            System.out.println(o1);

            test(socket);
        }
    }

    public static void test(Socket socket) throws Exception {
        try (ObjectInputStream ois = new ObjectInputStream(socket.getInputStream())) {
            Object o1 = ois.readObject();
            System.out.println(o1);
        }
    }
}

同样的,两次调用 getOutputStream,也会导致解析出错

package chat.test;

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

import static chat.test.Client.PORT;

/**
 * @author bbyh
 * @date 2022/11/9 0009 19:32
 * @description
 */
public class Server {
    public static void main(String[] args) throws Exception {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            while (true) {
                Socket accept = serverSocket.accept();
                ObjectInputStream ois = new ObjectInputStream(accept.getInputStream());
                Object o1 = ois.readObject();
                System.out.println(o1);

                Object o2 = ois.readObject();
                System.out.println(o2);

                ObjectOutputStream oos = new ObjectOutputStream(accept.getOutputStream());
                oos.writeObject(new Person("Hello3", 22));
                accept.shutdownOutput();
                accept.close();
            }
        }
    }
}


package chat.test;

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

/**
 * @author bbyh
 * @date 2022/11/9 0009 19:17
 * @description
 */
public class Client {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9090;

    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket(IP, PORT)) {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(new Person("Hello1", 20));

            test(socket);

            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Object o1 = ois.readObject();
            System.out.println(o1);
        }
    }

    public static void test(Socket socket) throws Exception {
        try (ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream())) {
            oos.writeObject(new Man("Hello2", 21));
            socket.shutdownOutput();
        }
    }
}

解决聊天软件中出现的对象读出问题

采用上面的重写字节流那两个类的两个方法也可以;或者采取传递转化好后的字节流,而不是传递socket到方法;或者防止对socket进行两次获取 getInputStream、getOutputStream

对比之下,我个人感觉,直接传递 转化好的字节流是最好的方式,简洁安全高效

项目链接:https://pan.baidu.com/s/15g9L6uDuYCFD_s7DbMuQ9w
提取码:0925

byte与String转化问题

对文件传输过程中,采用字符串存储字节数组,再转回来的字节数发生变化问题解决

需要采用 StandardCharsets.ISO_8859_1 这种单字节编码才不会让字符串转化回来的字节数组变长

package chat.test;

import java.io.BufferedInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;

import static chat.base.Config.FILE_MAX_SIZE;

/**
 * @author bbyh
 * @date 2022/11/10 0010 0:37
 * @description
 */
public class Test {
    public static void main(String[] args) throws Exception {
        String filePath = "src/image/1.jpg";
        byte[] buf = new byte[FILE_MAX_SIZE];

        BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(Paths.get(filePath)));
        int read = bis.read(buf);
        System.out.println(read);

        String str = new String(buf, 0, read, StandardCharsets.ISO_8859_1);
        System.out.println(str.getBytes(StandardCharsets.ISO_8859_1).length);
    }
}

最终版(强制下线、群聊、发送文件、服务端推送、离线发送)

项目链接:https://pan.baidu.com/s/1J7f343QblcZxznRxfdXoVQ
提取码:0925

遇到的一些问题就是多线程代码过长时不易维护,考虑到后期可以改为采用设计模式进行改进;以及多线程下对线程的属性认识不全,最后是字节流对象传输的对象设计的并不是十分完善,可以考虑设计的完善一些

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值