项目学习自哔哩哔哩的韩顺平老师的课程,课程地址
该项目主要对Java网络编程和多线程进行一个熟悉与锻炼;项目仅供学习使用
系统涉及的知识:多线程、网络通讯、数据库、集合
这里采用的交流格式采用对象流
采用 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 采用对象字节流进行写入的时候会写入头部,然后读出的时候读出头部,但是不是每一次都写,就很奇怪,文章链接
解决方式就是自己重写Object输入输出的两个方法,具体原因还没有查明
链接:https://pan.baidu.com/s/12UygVcYiXreJ4Qy7mPImGQ
提取码:0925
报错的文件及复现项目
参考文章
项目地址:
链接:https://pan.baidu.com/s/1pEyWgLlu-gU3tMMSjLg96Q
提取码:0925
给出的建议:如果你使用socket,并通过对象输入/输出流来处理的话,并且已经对某个socket调用了一次getInputStream时,但又需要把这个socket的相关信息作为参数传递给别的对象时,应注意:不用直接把socket传过去,应该把对应的ObjectInputStream或ObjectOutputStream对象传递过去
经测试发现文件流,写入读出多个同一类型的对象是没问题的,多个不同对象也是没问题的,不管顺序是否打乱
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);
}
}
}
复现了,确实是由于调用了两次 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
对文件传输过程中,采用字符串存储字节数组,再转回来的字节数发生变化问题解决
需要采用 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
遇到的一些问题就是多线程时代码过长不易维护,考虑到后期可以改为采用设计模式进行改进;以及多线程下对线程所属性认识不全,最后是字节流对象传输的对象设计的并不是十分完善,可以考虑设计的完善一些