网络编程:传输 Student 对象与多线程服务器实战

发布于:2025-08-29 ⋅ 阅读:(15) ⋅ 点赞:(0)

温馨提示:阅读本文前请先观看:【保姆级教程】[特殊字符]Java 网络编程从入门到实战:TCP/UDP 核心原理 + 完整案例-CSDN博客

一、传输 Student 对象(需序列化)

1. 序列化核心规则(📚知识卡片)

  • 定义:序列化是将对象转为字节流(便于网络传输 / 本地存储),反序列化则是字节流转回对象。
  • 必须条件
    1. 类实现Serializable接口(空接口,仅作可序列化标记);
    2. 显式声明serialVersionUID(固定值,避免类结构微调导致反序列化失败)。
  • 注意事项transient修饰的成员变量不参与序列化(如敏感信息密码)。

2. Student 实体类(序列化实现)

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. 客户端:读文件转对象并发送

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. 服务器:接收对象并遍历输出

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,内容示例:
001.zs.21
002.lucy.19
003.jack.20
004.tom.18
  • 类路径一致:客户端与服务器的Student类必须在相同包下(如com.briup.chap12),否则报ClassNotFoundException
  • 资源释放:务必通过try-with-resources或手动关闭流 / 连接,避免服务器资源泄漏。

二、多线程服务器(支持多客户端)

1. 设计思路(📚知识卡片)

  • 主线程职责:循环调用serverSocket.accept()监听客户端连接,不处理具体通信。
  • 子线程职责:每接收一个客户端连接,创建子线程专门处理该客户端的 IO 交互,避免单线程阻塞(单线程时,一个客户端未断开会导致其他客户端无法连接)。
  • 核心优势:支持多客户端同时连接,各客户端通信独立,提升服务器并发能力。

2. 多线程服务器代码实现

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管理线程,示例:
// 初始化线程池(核心线程数5,最大线程数10)
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 提交任务到线程池,替代new Thread()
threadPool.submit(() -> { /* 子线程逻辑 */ });
  • 避坑要点
    1. 子线程必须关闭clientSocket,否则客户端断开后连接资源持续占用;
    2. 若需发送消息,可在子线程中添加ObjectOutputStream,但需同步处理 IO 流,避免并发异常;
    3. 测试时可启动多个ObjectClient,验证服务器是否能同时接收多客户端数据。


网站公告

今日签到

点亮在社区的每一天
去签到