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("服务器准备启动就绪~");
}
}