温馨提示:阅读本文前请先观看:【保姆级教程】[特殊字符]Java 网络编程从入门到实战:TCP/UDP 核心原理 + 完整案例-CSDN博客
一、传输 Student 对象(需序列化)
1. 序列化核心规则(📚知识卡片)
- 定义:序列化是将对象转为字节流(便于网络传输 / 本地存储),反序列化则是字节流转回对象。
- 必须条件 :
- 类实现**
Serializable
**接口(空接口,仅作可序列化标记); - 显式声明**
serialVersionUID
**(固定值,避免类结构微调导致反序列化失败)。
- 类实现**
- 注意事项 :**
transient
**修饰的成员变量不参与序列化(如敏感信息密码)。
2. Student 实体类(序列化实现)
java
import java.io.Serializable;
// 实现Serializable接口,标记类可序列化
public class Student implements Serializable {
// 序列化版本号:固定1L,确保版本一致性
private static final long serialVersionUID = 1L;
private String id;
private String name;
private int age;
// 构造方法:初始化学生信息
public Student(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
// 重写toString:便于打印学生信息
@Override
public String toString() {
return "Student{id='" + id + "', name='" + name + "', age=" + age + "}";
}
}
3. 客户端:读文件转对象并发送
java
import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class ObjectClient {
public static void main(String[] args) {
String serverIp = "127.0.0.1"; // 服务器IP(本地测试用回送地址)
int serverPort = 8989; // 服务器端口
// try-with-resources自动关闭Socket、IO流,避免资源泄漏
try (Socket socket = new Socket(serverIp, serverPort);
// ObjectOutputStream:专门发送序列化对象
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
// 读取本地stud.txt文件(格式:id.姓名.年龄)
BufferedReader br = new BufferedReader(new FileReader("src/stud.txt"))) {
List<Student> studentList = new ArrayList<>();
String line;
// 解析文件:逐行读取并转为Student对象
while ((line = br.readLine()) != null) {
if (line.trim().isEmpty()) continue; // 跳过空行
String[] parts = line.split("\\."); // 按"."分割(正则需转义)
// 封装Student对象并添加到集合
studentList.add(new Student(parts[0], parts[1], Integer.parseInt(parts[2])));
}
// 发送Student集合到服务器
oos.writeObject(studentList);
oos.flush(); // ObjectOutputStream无自动刷新,需手动调用
System.out.println("成功发送" + studentList.size() + "个学生对象");
} catch (IOException e) {
System.out.println("客户端异常:" + e.getMessage());
}
}
}
4. 服务器:接收对象并遍历输出
java
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
public class ObjectServer {
public static void main(String[] args) {
int port = 8989; // 服务器监听端口
// try-with-resources自动关闭ServerSocket
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("对象传输服务器启动,端口:" + port);
// 接收客户端连接(accept()阻塞,直到有客户端连接)
Socket clientSocket = serverSocket.accept();
System.out.println("客户端连接:" + clientSocket.getInetAddress());
// try-with-resources自动关闭ObjectInputStream
try (ObjectInputStream ois = new ObjectInputStream(
clientSocket.getInputStream())) {
// 接收对象并强制转为List<Student>(需确保类路径一致)
List<Student> studentList = (List<Student>) ois.readObject();
System.out.println("收到学生数量:" + studentList.size());
// 遍历输出学生信息
for (Student student : studentList) {
System.out.println(student);
}
} catch (ClassNotFoundException e) {
// 异常:找不到Student类(通常因客户端与服务器类路径不一致)
System.out.println("找不到Student类:" + e.getMessage());
}
} catch (IOException e) {
System.out.println("服务器异常:" + e.getMessage());
}
}
}
5. 实战准备与避坑(📚知识卡片)
- 文件准备 :在**
src
目录创建stud.txt
**,内容示例:
java001.zs.21 002.lucy.19 003.jack.20 004.tom.18
- 类路径一致 :客户端与服务器的**
Student
类必须在相同包下(如com.briup.chap12
),否则报ClassNotFoundException
**。 - 资源释放 :务必通过**
try-with-resources
**或手动关闭流 / 连接,避免服务器资源泄漏。
二、多线程服务器(支持多客户端)
1. 设计思路(📚知识卡片)
- 主线程职责 :循环调用**
serverSocket.accept()
,**监听客户端连接,不处理具体通信。 - 子线程职责:每接收一个客户端连接,创建子线程专门处理该客户端的 IO 交互,避免单线程阻塞(单线程时,一个客户端未断开会导致其他客户端无法连接)。
- 核心优势:支持多客户端同时连接,各客户端通信独立,提升服务器并发能力。
2. 多线程服务器代码实现
java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class MultiThreadServer {
public static void main(String[] args) {
int port = 8989; // 服务器监听端口
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("多线程TCP服务器启动,端口:" + port);
// 主线程无限循环,持续监听客户端连接
while (true) {
Socket clientSocket = serverSocket.accept(); // 阻塞,直到有客户端连接
System.out.println("新客户端连接:" + clientSocket.getInetAddress());
// 为每个客户端创建子线程处理通信
new Thread(() -> {
// 子线程中处理该客户端的消息读取
try (BufferedReader br = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()))) {
String clientMsg;
// 持续读取客户端消息,直到客户端断开(readLine()返回null)
while ((clientMsg = br.readLine()) != null) {
System.out.println("来自" + clientSocket.getInetAddress() + "的消息:" + clientMsg);
}
} catch (IOException e) {
System.out.println("客户端" + clientSocket.getInetAddress() + "断开:" + e.getMessage());
} finally {
// 最终关闭客户端连接,释放资源
try {
if (clientSocket != null) clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start(); // 启动子线程
}
} catch (IOException e) {
System.out.println("服务器启动失败:" + e.getMessage());
}
}
}
3. 进阶优化与避坑(📚知识卡片)
- 线程池优化 :若客户端数量多,"一个客户端一个线程" 会创建大量线程,导致资源耗尽。可使用**
ThreadPoolExecutor
**管理线程,示例:
java
// 初始化线程池(核心线程数5,最大线程数10)
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 提交任务到线程池,替代new Thread()
threadPool.submit(() -> { /* 子线程逻辑 */ });
- 避坑要点 :
- 子线程必须关闭**
clientSocket
**,否则客户端断开后连接资源持续占用; - 若需发送消息,可在子线程中添加**
ObjectOutputStream
**,但需同步处理 IO 流,避免并发异常; - 测试时可启动多个**
ObjectClient
**,验证服务器是否能同时接收多客户端数据。
- 子线程必须关闭**