计算机网络协议

详细源码可见:NuyoahCh GitHub

client客户端实现

package com.networkdesign.client;

import com.networkdesign.protocol.FileTransferProtocol;
import com.networkdesign.util.ProtocolUtil;

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 文件传输客户端
 * 处理文件上传和下载
 */
public class FileTransferClient {
    private final String serverAddress;
    private final int serverPort;
    private Socket socket;
    private InputStream in;
    private OutputStream out;

    public FileTransferClient(String serverAddress, int serverPort) {
        this.serverAddress = serverAddress;
        this.serverPort = serverPort;
    }

    /**
     * 上传文件
     */
    public void uploadFile(String filePath, ProgressCallback callback) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            throw new IOException("文件不存在: " + filePath);
        }

        try {
            connect();
            
            // 发送文件信息
            String fileInfo = file.getName() + "|" + file.length();
            FileTransferProtocol.Message infoMessage = new FileTransferProtocol.Message(
                FileTransferProtocol.FILE_INFO,
                fileInfo.getBytes()
            );
            ProtocolUtil.writeMessage(out, infoMessage);

            // 读取服务器响应
            FileTransferProtocol.Message response = ProtocolUtil.readMessage(in);
            if (response.getType() == FileTransferProtocol.ERROR) {
                throw new IOException(new String(response.getPayload()));
            }

            // 发送文件数据
            try (FileInputStream fileIn = new FileInputStream(file)) {
                byte[] buffer = new byte[FileTransferProtocol.MAX_PACKET_SIZE - FileTransferProtocol.HEADER_SIZE];
                int bytesRead;
                long totalBytesSent = 0;

                while ((bytesRead = fileIn.read(buffer)) != -1) {
                    byte[] data = new byte[bytesRead];
                    System.arraycopy(buffer, 0, data, 0, bytesRead);
                    
                    FileTransferProtocol.Message dataMessage = new FileTransferProtocol.Message(
                        FileTransferProtocol.FILE_DATA,
                        data
                    );
                    ProtocolUtil.writeMessage(out, dataMessage);
                    
                    totalBytesSent += bytesRead;
                    callback.onProgress(totalBytesSent, file.length());
                }
            }

            // 发送传输完成消息
            FileTransferProtocol.Message completeMessage = new FileTransferProtocol.Message(
                FileTransferProtocol.TRANSFER_COMPLETE,
                null
            );
            ProtocolUtil.writeMessage(out, completeMessage);

            // 等待服务器确认
            response = ProtocolUtil.readMessage(in);
            if (response.getType() == FileTransferProtocol.ERROR) {
                throw new IOException(new String(response.getPayload()));
            }

            callback.onComplete();
        } finally {
            disconnect();
        }
    }

    /**
     * 获取服务器上的文件列表
     * @return 文件名数组,如果获取失败或没有文件则返回空数组
     */
    public String[] getFileList() throws IOException {
        try {
            connect();

            // 请求文件列表
            FileTransferProtocol.Message request = new FileTransferProtocol.Message(
                FileTransferProtocol.REQUEST_FILE_LIST,
                null
            );
            ProtocolUtil.writeMessage(out, request);

            // 读取文件列表
            FileTransferProtocol.Message response = ProtocolUtil.readMessage(in);
            if (response.getType() == FileTransferProtocol.ERROR) {
                String errorMsg = new String(response.getPayload());
                System.err.println("从服务器获取文件列表时发生错误: " + errorMsg);
                throw new IOException(errorMsg);
            }

            String fileListPayload = new String(response.getPayload());
            System.out.println("从服务器接收到的文件列表原始字符串: " + fileListPayload);
            String[] files = fileListPayload.split("\\|");

            System.out.println("解析后的文件列表数组,长度: " + files.length);
            for (int i = 0; i < files.length; i++) {
                System.out.println("文件 " + i + ": " + files[i]);
            }

            if (files.length == 0 || (files.length == 1 && files[0].isEmpty())) {
                 System.out.println("服务器上没有可用的文件");
                 return new String[0]; // 返回空数组
            }

            return files;
        } finally {
            disconnect();
        }
    }

    /**
     * 下载指定文件
     */
    public void downloadSpecificFile(String fileName, String saveDir, ProgressCallback callback) throws IOException {
         try {
            connect();

             // 请求文件
            System.out.println("向服务器请求下载文件: " + fileName);
            FileTransferProtocol.Message request = new FileTransferProtocol.Message(
                FileTransferProtocol.REQUEST_FILE,
                fileName.getBytes()
            );
            ProtocolUtil.writeMessage(out, request);

            // 读取文件信息
            FileTransferProtocol.Message response = ProtocolUtil.readMessage(in);
            if (response.getType() == FileTransferProtocol.ERROR) {
                String errorMsg = new String(response.getPayload());
                 System.err.println("从服务器获取文件信息时发生错误: " + errorMsg);
                throw new IOException(errorMsg);
            }

            String[] fileInfo = new String(response.getPayload()).split("\\|");
             if (fileInfo.length != 2) {
                 String errorMsg = "无效的文件信息格式从服务器: " + new String(response.getPayload());
                 System.err.println(errorMsg);
                 throw new IOException(errorMsg);
             }
            String receivedFileName = fileInfo[0];
            long fileSize = Long.parseLong(fileInfo[1]);
             System.out.println("接收到的文件信息 - 文件名: " + receivedFileName + ", 大小: " + fileSize + " bytes");

             if (!receivedFileName.equals(fileName)) {
                 String errorMsg = "服务器返回的文件名与请求不匹配: 请求=" + fileName + ", 接收=" + receivedFileName;
                 System.err.println(errorMsg);
                 throw new IOException(errorMsg);
             }


            // 创建保存目录
            Files.createDirectories(Paths.get(saveDir));

            // 下载文件
            Path filePath = Paths.get(saveDir, fileName);
             System.out.println("开始下载文件到: " + filePath.toString());
            try (OutputStream fileOut = Files.newOutputStream(filePath)) {
                long totalBytesReceived = 0;

                while (totalBytesReceived < fileSize) {
                    response = ProtocolUtil.readMessage(in);

                    if (response.getType() == FileTransferProtocol.FILE_DATA) {
                        // 避免因接收到比预期更多的数据而导致的无限循环
                        long bytesToRead = Math.min(response.getPayload().length, fileSize - totalBytesReceived);
                        fileOut.write(response.getPayload(), 0, (int) bytesToRead);
                        totalBytesReceived += bytesToRead;

                         // 如果接收到的数据量不足预期,说明可能传输有问题
                        if (bytesToRead < response.getPayload().length) {
                             System.err.println("警告: 接收到的文件数据包大小超出预期,已截断");
                        }

                        callback.onProgress(totalBytesReceived, fileSize);
                    } else if (response.getType() == FileTransferProtocol.ERROR) {
                        String errorMsg = new String(response.getPayload());
                         System.err.println("接收文件数据时服务器报告错误: " + errorMsg);
                         Files.deleteIfExists(filePath); // 出现错误时删除部分传输的文件
                        throw new IOException(errorMsg);
                    } else {
                        String errorMsg = "接收文件数据时收到意外消息类型: " + response.getType();
                         System.err.println(errorMsg);
                         Files.deleteIfExists(filePath); // 出现错误时删除部分传输的文件
                        throw new IOException(errorMsg);
                    }
                }
                 System.out.println("文件数据接收完成");
            }


             // 接收到传输完成消息
            response = ProtocolUtil.readMessage(in);
            if (response.getType() != FileTransferProtocol.TRANSFER_COMPLETE) {
                 String errorMsg = "未收到服务器的传输完成消息,收到类型: " + response.getType();
                 System.err.println(errorMsg);
                 // 不抛出异常,只是警告,因为文件可能已经完整接收
                 // throw new IOException(errorMsg);
            } else {
                 System.out.println("接收到服务器的传输完成消息");
            }


            callback.onComplete();
             System.out.println("文件下载完成");
         } catch (IOException e) {
             System.err.println("下载指定文件时发生IO错误: " + e.getMessage());
            callback.onError("下载文件失败: " + e.getMessage());
            throw e; // 重新抛出以便上层处理
        } catch (Exception e) {
            System.err.println("下载指定文件时发生意外错误: " + e.getMessage());
            e.printStackTrace();
            callback.onError("下载文件失败: " + e.getMessage());
            throw new IOException(e); // 封装为IOException抛出
        }
         finally {
            disconnect();
             System.out.println("下载连接已断开");
        }
    }

    /**
     * 显示文件选择对话框
     * @param files 文件列表
     * @return 用户选择的文件名,如果取消则返回 null
     */
    private String showFileSelectionDialog(String[] files) throws InterruptedException, InvocationTargetException {
         System.out.println("进入 showFileSelectionDialog 方法");
         System.out.println("文件列表长度: " + files.length);
        for (int i = 0; i < files.length; i++) {
             System.out.println("对话框文件 " + i + ": " + files[i]);
        }

        final String[] selectedFile = {null};
        final JDialog dialog = new JDialog((Frame)null, "选择要下载的文件", true);
        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        dialog.setLayout(new BorderLayout());
        dialog.setSize(300, 200);
        dialog.setLocationRelativeTo(null);

        JList<String> fileList = new JList<>(files);
        fileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        JScrollPane scrollPane = new JScrollPane(fileList);
        dialog.add(scrollPane, BorderLayout.CENTER);

        JPanel buttonPanel = new JPanel();
        JButton downloadButton = new JButton("下载");
        JButton cancelButton = new JButton("取消");

        downloadButton.addActionListener(e -> {
             System.out.println("点击了下载按钮");
            if (!fileList.isSelectionEmpty()) {
                selectedFile[0] = fileList.getSelectedValue();
                 System.out.println("检测到选中项,值为: " + selectedFile[0]);
            } else {
                 System.out.println("没有检测到选中项");
            }
            dialog.dispose();
             System.out.println("对话框已关闭");
        });

        cancelButton.addActionListener(e -> {
             System.out.println("点击了取消按钮");
            dialog.dispose();
             System.out.println("对话框已关闭");
        });

        buttonPanel.add(downloadButton);
        buttonPanel.add(cancelButton);
        dialog.add(buttonPanel, BorderLayout.SOUTH);

        // 在 EDT 中显示对话框并等待关闭
        System.out.println("在 EDT 中显示对话框");
        SwingUtilities.invokeAndWait(() -> {
            dialog.setVisible(true);
        });
         System.out.println("invokeAndWait 返回,对话框已不再可见");

        return selectedFile[0];
    }

    /**
     * 连接到服务器
     */
    private void connect() throws IOException {
        socket = new Socket(serverAddress, serverPort);
        in = socket.getInputStream();
        out = socket.getOutputStream();
         System.out.println("已连接到服务器: " + serverAddress + ":" + serverPort);
    }

    /**
     * 断开与服务器的连接
     */
    public void disconnect() {
        try {
            if (in != null) in.close();
            if (out != null) out.close();
            if (socket != null) socket.close();
             System.out.println("与服务器的连接已断开");
        } catch (IOException e) {
            System.err.println("关闭连接时发生错误: " + e.getMessage());
            // 忽略关闭时的异常
        }
    }

    /**
     * 进度回调接口
     */
    public interface ProgressCallback {
        void onProgress(long bytesTransferred, long totalBytes);
        void onComplete();
        void onError(String error);
    }
} 

server服务器端

package com.networkdesign.server;

import com.networkdesign.protocol.FileTransferProtocol;
import com.networkdesign.util.ProtocolUtil;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 文件传输服务器
 * 处理客户端的文件上传和下载请求
 */
public class FileTransferServer {
    private static final String UPLOAD_DIR = "uploads";  // 文件上传目录
    private final int port;
    private ServerSocket serverSocket;
    private ExecutorService threadPool;
    private volatile boolean running;
    private Thread serverThread;

    public FileTransferServer(int port) {
        this.port = port;
        this.threadPool = Executors.newCachedThreadPool();
        this.running = false;
    }

    /**
     * 启动服务器
     */
    public void start() throws IOException {
        if (running) {
            return; // 服务器已经在运行
        }

        // 创建上传目录
        Files.createDirectories(Paths.get(UPLOAD_DIR));

        try {
            serverSocket = new ServerSocket(port);
            running = true;

            // 在新线程中启动服务器
            serverThread = new Thread(() -> {
                System.out.println("服务器已启动,监听端口: " + port);
                
                // 接受客户端连接
                while (running) {
                    try {
                        Socket clientSocket = serverSocket.accept();
                        System.out.println("接受新的客户端连接: " + clientSocket.getInetAddress());
                        threadPool.execute(() -> handleClient(clientSocket));
                    } catch (IOException e) {
                        if (running) {
                            System.err.println("接受客户端连接时发生错误: " + e.getMessage());
                        }
                    } catch (Exception e) {
                         System.err.println("处理客户端连接时发生意外错误: " + e.getMessage());
                         e.printStackTrace();
                    }
                }
            });
            
            serverThread.start();
        } catch (IOException e) {
            running = false;
            if (serverSocket != null) {
                serverSocket.close();
            }
            throw e;
        } catch (Exception e) {
            System.err.println("启动服务器时发生意外错误: " + e.getMessage());
            running = false;
            if (serverSocket != null) {
                try { serverSocket.close(); } catch (IOException ignored) {}
            }
            throw new IOException(e);
        }
    }

    /**
     * 停止服务器
     */
    public void stop() {
        if (!running) {
            return; // 服务器已经停止
        }

        running = false;
        
        try {
            if (serverSocket != null) {
                serverSocket.close();
            }
        } catch (IOException e) {
            System.err.println("关闭服务器时发生错误: " + e.getMessage());
        }
        
        // 等待服务器线程结束
        if (serverThread != null && serverThread.isAlive()) {
            try {
                serverThread.join(1000); // 等待最多1秒
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        
        // 关闭线程池
        threadPool.shutdown();
        System.out.println("服务器已停止");
    }

    /**
     * 检查服务器是否正在运行
     */
    public boolean isRunning() {
        return running;
    }

    /**
     * 处理客户端请求
     */
    private void handleClient(Socket clientSocket) {
        try (InputStream in = clientSocket.getInputStream();
             OutputStream out = clientSocket.getOutputStream()) {

            // 读取客户端请求
            FileTransferProtocol.Message request = ProtocolUtil.readMessage(in);
            
            // 根据请求类型处理
            switch (request.getType()) {
                case FileTransferProtocol.REQUEST_FILE_LIST:
                    handleFileListRequest(out);
                    break;
                case FileTransferProtocol.REQUEST_FILE:
                    handleFileRequest(request.getPayload(), out);
                    break;
                case FileTransferProtocol.FILE_INFO:
                    handleFileUpload(request.getPayload(), in, out);
                    break;
                case FileTransferProtocol.TRANSFER_COMPLETE:
                    // 客户端发送的传输完成消息,服务器不需要额外处理,连接会关闭
                    break;
                default:
                    sendError(out, "未知的请求类型");
            }

        } catch (IOException e) {
            System.err.println("处理客户端请求时发生错误: " + e.getMessage());
            // 可以发送错误响应给客户端,如果连接还开着的话
            // try { sendError(clientSocket.getOutputStream(), e.getMessage()); } catch (IOException ignored) {}
        } catch (Exception e) {
            System.err.println("处理客户端请求时发生意外错误: " + e.getMessage());
            e.printStackTrace();
             // 可以发送错误响应给客户端,如果连接还开着的话
            // try { sendError(clientSocket.getOutputStream(), "服务器内部错误: " + e.getMessage()); } catch (IOException ignored) {}
        }
        finally {
            try {
                if (!clientSocket.isClosed()) {
                   clientSocket.close();
                }
            } catch (IOException e) {
                // 忽略关闭时的异常
            }
        }
    }

    /**
     * 处理文件列表请求
     */
    private void handleFileListRequest(OutputStream out) throws IOException {
        List<String> files = new ArrayList<>();
        File uploadDir = new File(UPLOAD_DIR);
        if (uploadDir.exists() && uploadDir.isDirectory()) {
            File[] fileList = uploadDir.listFiles();
            if (fileList != null) {
                for (File file : fileList) {
                    if (file.isFile()) {
                        files.add(file.getName());
                    }
                }
            }
        }

        String fileListStr = String.join("|", files);
        FileTransferProtocol.Message response = new FileTransferProtocol.Message(
            FileTransferProtocol.FILE_LIST,
            fileListStr.getBytes()
        );
        ProtocolUtil.writeMessage(out, response);
    }

    /**
     * 处理文件请求
     */
    private void handleFileRequest(byte[] payload, OutputStream out) throws IOException {
        String fileName = new String(payload);
        Path filePath = Paths.get(UPLOAD_DIR, fileName);

        if (!Files.exists(filePath)) {
            sendError(out, "文件不存在: " + fileName);
            return;
        }

        // 发送文件信息
        String fileInfo = fileName + "|" + Files.size(filePath);
        FileTransferProtocol.Message infoMessage = new FileTransferProtocol.Message(
            FileTransferProtocol.FILE_INFO,
            fileInfo.getBytes()
        );
        ProtocolUtil.writeMessage(out, infoMessage);

        // 发送文件数据
        try (InputStream fileIn = Files.newInputStream(filePath)) {
            byte[] buffer = new byte[FileTransferProtocol.MAX_PACKET_SIZE - FileTransferProtocol.HEADER_SIZE];
            int bytesRead;

            while ((bytesRead = fileIn.read(buffer)) != -1) {
                byte[] data = new byte[bytesRead];
                System.arraycopy(buffer, 0, data, 0, bytesRead);
                
                FileTransferProtocol.Message dataMessage = new FileTransferProtocol.Message(
                    FileTransferProtocol.FILE_DATA,
                    data
                );
                ProtocolUtil.writeMessage(out, dataMessage);
            }
        }

         // 文件数据发送完成,发送传输完成消息
        FileTransferProtocol.Message completeMessage = new FileTransferProtocol.Message(
            FileTransferProtocol.TRANSFER_COMPLETE,
            null
        );
        ProtocolUtil.writeMessage(out, completeMessage);
    }

    /**
     * 处理文件上传
     */
    private void handleFileUpload(byte[] payload, InputStream in, OutputStream out) throws IOException {
        String[] fileInfo = new String(payload).split("\\|");
        if (fileInfo.length != 2) {
             sendError(out, "无效的文件信息格式");
             return;
        }
        String fileName = fileInfo[0];
        long fileSize;
        try {
             fileSize = Long.parseLong(fileInfo[1]);
        } catch (NumberFormatException e) {
             sendError(out, "无效的文件大小格式");
             return;
        }
       

        Path filePath = Paths.get(UPLOAD_DIR, fileName);

        // 检查文件是否已存在,避免覆盖
        if (Files.exists(filePath)) {
             sendError(out, "文件已存在: " + fileName);
             return;
        }

         // 告知客户端服务器已准备好接收数据
        FileTransferProtocol.Message readyMessage = new FileTransferProtocol.Message(
             FileTransferProtocol.TRANSFER_COMPLETE, // 使用TRANSFER_COMPLETE作为准备就绪信号
             null
        );
        ProtocolUtil.writeMessage(out, readyMessage);

        try (OutputStream fileOut = Files.newOutputStream(filePath)) {
            long totalBytesReceived = 0;

            while (totalBytesReceived < fileSize) {
                FileTransferProtocol.Message dataMessage = ProtocolUtil.readMessage(in);
                
                if (dataMessage.getType() == FileTransferProtocol.FILE_DATA) {
                    // 避免因接收到比预期更多的数据而导致的无限循环
                    long bytesToRead = Math.min(dataMessage.getPayload().length, fileSize - totalBytesReceived);
                    fileOut.write(dataMessage.getPayload(), 0, (int) bytesToRead);
                    totalBytesReceived += bytesToRead;

                     // 如果接收到的数据量不足预期,说明可能传输有问题
                    if (bytesToRead < dataMessage.getPayload().length) {
                         System.err.println("警告: 接收到的文件数据包大小超出预期,已截断");
                    }

                } else if (dataMessage.getType() == FileTransferProtocol.ERROR) {
                    // 客户端发送错误消息,中断接收
                    System.err.println("客户端报告错误: " + new String(dataMessage.getPayload()));
                    Files.deleteIfExists(filePath); // 删除部分传输的文件
                    throw new IOException("客户端传输错误: " + new String(dataMessage.getPayload()));
                } else {
                    // 接收到非数据或错误消息,中断接收
                     System.err.println("警告: 接收到非数据或错误消息 (类型: " + dataMessage.getType() + "),中断文件上传");
                     Files.deleteIfExists(filePath); // 删除部分传输的文件
                     throw new IOException("接收到意外消息类型,中断上传");
                }
            }

             // 接收到传输完成消息(可选,取决于客户端是否发送)
            // FileTransferProtocol.Message completeMessage = ProtocolUtil.readMessage(in);
            // if (completeMessage.getType() != FileTransferProtocol.TRANSFER_COMPLETE) {
            //      System.err.println("警告: 未收到客户端的传输完成消息");
            // }

        } catch (IOException e) {
             Files.deleteIfExists(filePath); // 出现异常时删除部分传输的文件
             throw e; // 重新抛出异常以便上层处理和记录
        }
    }

    /**
     * 发送错误消息
     */
    private void sendError(OutputStream out, String errorMessage) throws IOException {
        FileTransferProtocol.Message errorResponse = new FileTransferProtocol.Message(
            FileTransferProtocol.ERROR,
            errorMessage.getBytes()
        );
        ProtocolUtil.writeMessage(out, errorResponse);
    }
} 

GUI界面

package com.networkdesign.gui;

import com.networkdesign.client.FileTransferClient;
import com.networkdesign.server.FileTransferServer;

import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files; // 导入 Files 类
import java.nio.file.Paths; // 导入 Paths 类

/**
 * 文件传输GUI主窗口
 * 提供文件上传和下载的图形界面
 */
public class FileTransferGUI extends JFrame {
    private final JTextField serverAddressField;
    private final JTextField serverPortField;
    private final JButton uploadButton;
    private final JButton downloadButton;
    private final JButton serverButton;
    private final JTextArea logArea;
    private FileTransferServer server; // GUI 持有服务器实例,用于启动/停止

    public FileTransferGUI() {
        // 设置窗口属性
        setTitle("文件传输系统");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(600, 400);
        setLocationRelativeTo(null);

        // 创建主面板
        JPanel mainPanel = new JPanel(new BorderLayout());

        // 创建控制面板
        JPanel controlPanel = new JPanel(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(5, 5, 5, 5);
        gbc.fill = GridBagConstraints.HORIZONTAL;

        // 服务器地址输入
        gbc.gridx = 0;
        gbc.gridy = 0;
        controlPanel.add(new JLabel("服务器地址:"), gbc);

        serverAddressField = new JTextField("localhost", 15);
        gbc.gridx = 1;
        controlPanel.add(serverAddressField, gbc);

        // 服务器端口输入
        gbc.gridx = 0;
        gbc.gridy = 1;
        controlPanel.add(new JLabel("服务器端口:"), gbc);

        serverPortField = new JTextField("8888", 15);
        gbc.gridx = 1;
        controlPanel.add(serverPortField, gbc);

        // 上传按钮
        uploadButton = new JButton("上传文件");
        uploadButton.addActionListener(e -> uploadFile());
        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.gridwidth = 2;
        controlPanel.add(uploadButton, gbc);

        // 下载按钮
        downloadButton = new JButton("下载文件");
        downloadButton.addActionListener(e -> downloadFile());
        gbc.gridy = 3;
        controlPanel.add(downloadButton, gbc);

        // 启动/停止服务器按钮
        serverButton = new JButton("启动服务器");
        serverButton.addActionListener(e -> toggleServer());
        gbc.gridy = 4;
        controlPanel.add(serverButton, gbc);

        // 创建日志区域
        logArea = new JTextArea();
        logArea.setEditable(false);
        JScrollPane scrollPane = new JScrollPane(logArea);

        // 添加组件到主面板
        mainPanel.add(controlPanel, BorderLayout.WEST);
        mainPanel.add(scrollPane, BorderLayout.CENTER);

        // 设置主面板
        add(mainPanel);
    }

    /**
     * 上传文件
     */
    private void uploadFile() {
        // 上传前先检查服务器是否运行
        if (server == null || !server.isRunning()) {
            log("错误:请先启动服务器!");
            JOptionPane.showMessageDialog(this, "错误:请先启动服务器!", "错误", JOptionPane.ERROR_MESSAGE);
            return;
        }

        JFileChooser fileChooser = new JFileChooser();
        if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
            File file = fileChooser.getSelectedFile();
            String serverAddress = serverAddressField.getText();
            int serverPort = Integer.parseInt(serverPortField.getText());

            // 创建进度对话框
            TransferProgressDialog progressDialog = new TransferProgressDialog(
                    this, "上传文件", file.getName());

            // 在新线程中执行上传
            new Thread(() -> {
                FileTransferClient client = null; // 线程内部创建客户端
                try {
                    client = new FileTransferClient(serverAddress, serverPort);
                    client.uploadFile(file.getAbsolutePath(),
                            new FileTransferClient.ProgressCallback() {
                                @Override
                                public void onProgress(long bytesTransferred, long totalBytes) {
                                    // 在 EDT 中更新 GUI
                                    SwingUtilities.invokeLater(() ->
                                            progressDialog.updateProgress(bytesTransferred, totalBytes));
                                }

                                @Override
                                public void onComplete() {
                                    // 在 EDT 中更新 GUI
                                    SwingUtilities.invokeLater(() -> {
                                        progressDialog.dispose();
                                        log("文件上传完成: " + file.getName());
                                        JOptionPane.showMessageDialog(null, "文件上传完成: " + file.getName(), "提示", JOptionPane.INFORMATION_MESSAGE);
                                    });
                                }

                                @Override
                                public void onError(String error) {
                                    // 在 EDT 中更新 GUI
                                    SwingUtilities.invokeLater(() -> {
                                        progressDialog.dispose();
                                        log("上传错误: " + error);
                                        JOptionPane.showMessageDialog(null, "上传错误: " + error, "错误", JOptionPane.ERROR_MESSAGE);
                                    });
                                }
                            });
                } catch (IOException e) {
                    // 在 EDT 中更新 GUI
                    SwingUtilities.invokeLater(() -> {
                        progressDialog.dispose();
                        log("上传IO错误: " + e.getMessage());
                        JOptionPane.showMessageDialog(null, "上传IO错误: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
                    });
                } catch (Exception e) {
                    // 在 EDT 中更新 GUI
                    SwingUtilities.invokeLater(() -> {
                        progressDialog.dispose();
                        log("上传过程中发生意外错误: " + e.getMessage());
                        e.printStackTrace(); // 打印堆栈跟踪以便调试
                        JOptionPane.showMessageDialog(null, "上传过程中发生意外错误: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
                    });
                } finally {
                    if (client != null) {
                        client.disconnect(); // 确保连接关闭
                    }
                }
            }).start();

            progressDialog.setVisible(true); // 显示进度对话框
        }
    }

    /**
     * 下载文件
     */
    private void downloadFile() {
        // 下载前先检查服务器是否运行
        if (server == null || !server.isRunning()) {
            log("错误:请先启动服务器!");
            JOptionPane.showMessageDialog(this, "错误:请先启动服务器!", "错误", JOptionPane.ERROR_MESSAGE);
            return;
        }

        String serverAddress = serverAddressField.getText();
        int serverPort = Integer.parseInt(serverPortField.getText());

        // 创建进度对话框 (先显示,表示正在获取文件列表)
        TransferProgressDialog progressDialog = new TransferProgressDialog(
                this, "下载文件", "正在获取文件列表...");
        // progressDialog.setVisible(true); // 先不显示,获取到列表再显示

        // 在新线程中执行下载流程(包括获取文件列表和文件选择)
        new Thread(() -> {
            File saveDir = null;
            String selectedFile = null;
            FileTransferClient client = null; // 线程内部创建客户端

            try {
                client = new FileTransferClient(serverAddress, serverPort);

                // 1. 获取服务器文件列表
                String[] files = client.getFileList();
                client.disconnect(); // 获取完列表后立即断开连接

                if (files == null || files.length == 0) {
                    SwingUtilities.invokeLater(() -> {
                        JOptionPane.showMessageDialog(this, "服务器上没有可用的文件", "提示", JOptionPane.INFORMATION_MESSAGE);
                        // 如果进度对话框已经显示了,需要确保它被关闭
                        if(progressDialog.isVisible()) {
                            progressDialog.dispose();
                        }
                        log("服务器上没有可用的文件"); // 在对话框显示后再更新日志
                    });
                    return; // 没有文件可下载
                }

                // 2. 在 EDT 中显示文件选择对话框让用户选择文件
                // 注意:showFileSelectionDialogOnEDT 是阻塞的,当前线程会在这里等待用户操作对话框
                selectedFile = showFileSelectionDialogOnEDT(files);

                if (selectedFile == null) {
                    // 用户取消了文件选择
                    SwingUtilities.invokeLater(() -> {
                        // 如果进度对话框已经显示了,需要确保它被关闭
                        if(progressDialog.isVisible()) {
                            progressDialog.dispose();
                        }
                        log("用户取消了文件选择");
                    });
                    return;
                }

                // 3. 在 EDT 中显示保存目录选择对话框
                // 注意:showSaveDirectoryDialogOnEDT 也是阻塞的
                saveDir = showSaveDirectoryDialogOnEDT();

                if (saveDir == null) {
                    // 用户取消了保存目录选择
                    SwingUtilities.invokeLater(() -> {
                        // 如果进度对话框已经显示了,需要确保它被关闭
                        if(progressDialog.isVisible()) {
                            progressDialog.dispose();
                        }
                        log("用户取消了保存位置选择");
                    });
                    return;
                }

                // 4. 更新并显示进度对话框 (现在才显示,因为用户已经选择了文件和保存位置)
                String finalSelectedFile = selectedFile; // 需要一个 final 变量在 lambda 中使用
                SwingUtilities.invokeLater(() -> {
                    progressDialog.setTitle("下载文件: " + finalSelectedFile);
                    progressDialog.setFileName(finalSelectedFile);
                    progressDialog.setVisible(true); // 现在显示进度对话框
                });

                // 5. 重新创建客户端连接并开始下载选定的文件
                // 这里需要一个新的客户端实例来进行实际的文件数据传输
                FileTransferClient downloadClient = null;
                try {
                    downloadClient = new FileTransferClient(serverAddress, serverPort);
                    downloadClient.downloadSpecificFile(selectedFile, saveDir.getAbsolutePath(), // 调用下载指定文件的方法
                            new FileTransferClient.ProgressCallback() {
                                @Override
                                public void onProgress(long bytesTransferred, long totalBytes) {
                                    // 在 EDT 中更新 GUI
                                    SwingUtilities.invokeLater(() ->
                                            progressDialog.updateProgress(bytesTransferred, totalBytes));
                                }

                                @Override
                                public void onComplete() {
                                    // 在 EDT 中更新 GUI
                                    SwingUtilities.invokeLater(() -> {
                                        progressDialog.dispose();
                                        log("文件下载完成: " + finalSelectedFile); // 使用 final 变量
                                        JOptionPane.showMessageDialog(null, "文件下载完成: " + finalSelectedFile, "提示", JOptionPane.INFORMATION_MESSAGE); // 使用 final 变量
                                    });
                                }

                                @Override
                                public void onError(String error) {
                                    // 在 EDT 中更新 GUI
                                    SwingUtilities.invokeLater(() -> {
                                        progressDialog.dispose();
                                        log("下载错误: " + error);
                                        JOptionPane.showMessageDialog(null, "下载错误: " + error, "错误", JOptionPane.ERROR_MESSAGE);
                                    });
                                }
                            });
                } finally {
                    if (downloadClient != null) {
                        downloadClient.disconnect(); // 确保下载连接关闭
                    }
                }


            } catch (IOException e) {
                SwingUtilities.invokeLater(() -> {
                    if(progressDialog.isVisible()) {
                        progressDialog.dispose();
                    }
                    log("下载IO错误: " + e.getMessage());
                    JOptionPane.showMessageDialog(null, "下载IO错误: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
                });
            } catch (Exception e) {
                SwingUtilities.invokeLater(() -> {
                    if(progressDialog.isVisible()) {
                        progressDialog.dispose();
                    }
                    log("下载过程中发生意外错误: " + e.getMessage());
                    e.printStackTrace(); // 打印堆栈跟踪以便调试
                    JOptionPane.showMessageDialog(null, "下载过程中发生意外错误: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
                });
            }
            // Note: The initial 'client' used for getFileList is disconnected in its finally block.
            // The 'downloadClient' is disconnected in its finally block.
            // No need for a top-level finally here unless there's other shared cleanup.
        }).start();
        // progressDialog.setVisible(true); // 不在这里设置可见,在获取到文件列表并用户选择后才显示
    }

    /**
     * 切换服务器状态
     */
    private void toggleServer() {
        if (server == null || !server.isRunning()) {
            try {
                int port = Integer.parseInt(serverPortField.getText());
                // 在新线程中启动服务器以避免阻塞 GUI
                new Thread(() -> {
                    try {
                        server = new FileTransferServer(port);
                        server.start();
                        SwingUtilities.invokeLater(() -> {
                            serverButton.setText("停止服务器");
                            log("服务器已启动,监听端口: " + port);
                        });
                    } catch (IOException e) {
                        SwingUtilities.invokeLater(() -> {
                            log("启动服务器失败: " + e.getMessage());
                            JOptionPane.showMessageDialog(this, "启动服务器失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
                        });
                    }
                }).start();
            } catch (NumberFormatException e) {
                log("错误:端口号格式无效!");
                JOptionPane.showMessageDialog(this, "错误:端口号格式无效!", "错误", JOptionPane.ERROR_MESSAGE);
            } catch (Exception e) {
                log("启动服务器发生意外错误: " + e.getMessage());
                e.printStackTrace();
                JOptionPane.showMessageDialog(this, "启动服务器发生意外错误: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
            }
        } else {
            // 在新线程中停止服务器
            new Thread(() -> {
                if (server != null) {
                    server.stop();
                    SwingUtilities.invokeLater(() -> {
                        serverButton.setText("启动服务器");
                        log("服务器已停止");
                    });
                }
            }).start();
        }
    }

    /**
     * 添加日志信息
     */
    private void log(String message) {
        // 在 EDT 中更新日志区域
        SwingUtilities.invokeLater(() -> {
            logArea.append(message + "\n");
            // 自动滚动到最新日志
            logArea.setCaretPosition(logArea.getDocument().getLength());
        });
    }

    /**
     * 在 EDT 中显示文件选择对话框
     * @param files 文件列表
     * @return 用户选择的文件名,如果取消则返回 null
     */
    private String showFileSelectionDialogOnEDT(String[] files) throws InterruptedException, InvocationTargetException {
        final String[] selected = {null};
        // 使用 invokeAndWait 确保对话框在 EDT 中创建和显示,并且当前线程会等待对话框关闭
        SwingUtilities.invokeAndWait(() -> {
            JDialog dialog = new JDialog(this, "选择要下载的文件", true); // Modality makes it block input to other windows
            dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            dialog.setLayout(new BorderLayout());
            dialog.setSize(300, 200);
            dialog.setLocationRelativeTo(this); // Center relative to the main frame

            JList<String> fileList = new JList<>(files);
            fileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            JScrollPane scrollPane = new JScrollPane(fileList);
            dialog.add(scrollPane, BorderLayout.CENTER);

            JPanel buttonPanel = new JPanel();
            JButton downloadButton = new JButton("下载");
            JButton cancelButton = new JButton("取消");

            downloadButton.addActionListener(e -> {
                if (!fileList.isSelectionEmpty()) {
                    selected[0] = fileList.getSelectedValue();
                }
                dialog.dispose(); // Close the dialog
            });

            cancelButton.addActionListener(e -> {
                dialog.dispose(); // Close the dialog
            });

            buttonPanel.add(downloadButton);
            buttonPanel.add(cancelButton);
            dialog.add(buttonPanel, BorderLayout.SOUTH);

            dialog.setVisible(true); // Show the dialog and block until disposed
        });
        return selected[0];
    }

    /**
     * 在 EDT 中显示保存目录选择对话框
     * @return 用户选择的目录 File 对象,如果取消则返回 null
     */
    private File showSaveDirectoryDialogOnEDT() throws InterruptedException, InvocationTargetException {
        final File[] selectedDir = {null};
        // 使用 invokeAndWait 确保对话框在 EDT 中创建和显示,并且当前线程会等待对话框关闭
        SwingUtilities.invokeAndWait(() -> {
            JFileChooser fileChooser = new JFileChooser();
            fileChooser.setDialogTitle("选择保存下载文件的目录"); // 设置对话框标题
            fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); // 只允许选择目录
            // 可以设置默认目录,例如用户的下载目录
            // fileChooser.setCurrentDirectory(new File(System.getProperty("user.home") + "/Downloads"));

            int userSelection = fileChooser.showSaveDialog(this); // 显示保存对话框

            if (userSelection == JFileChooser.APPROVE_OPTION) {
                selectedDir[0] = fileChooser.getSelectedFile();
            }
        });
        return selectedDir[0];
    }


    public static void main(String[] args) {
        // 在 EDT 中运行 GUI
        SwingUtilities.invokeLater(() -> {
            FileTransferGUI gui = new FileTransferGUI();
            gui.setVisible(true);
        });
        System.out.println("服务器准备启动就绪~");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值