提问人:Carlos Curcino 提问时间:11/7/2023 更新时间:11/7/2023 访问量:40
Java 套接字多线程文件传输在上传后卡住
Java Socket Multithread File Transfer getting stuck after upload
问:
我已经使用 Java 套接字和多线程实现了一个服务器客户端程序。该程序允许客户端将文件发送到服务器,列出文件并下载该文件。但是我遇到了一个问题,当我的客户端上传文件时,programm/gui 卡住了,客户端无法执行任何操作。无法下载或上传。但文件已正确发送到服务器。有人知道发生了什么吗??
我的客户
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class FileClient {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 12345;
private static Socket socket;
private static DataInputStream in;
private static DataOutputStream out;
private static JList<String> fileList;
private static DefaultListModel<String> listModel;
public static void main(String[] args) {
try {
socket = new Socket(SERVER_HOST, SERVER_PORT);
out = new DataOutputStream(socket.getOutputStream());
in = new DataInputStream(socket.getInputStream());
JFrame frame = new JFrame("File Client");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
JPanel panel = new JPanel();
JButton listButton = new JButton("List Files");
JButton downloadButton = new JButton("Download File");
JButton uploadButton = new JButton("Upload File");
listModel = new DefaultListModel<>();
fileList = new JList<>(listModel);
fileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
JScrollPane scrollPane = new JScrollPane(fileList);
listButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Execute a operação de listagem em uma thread separada
SwingWorker<Void, Void> listWorker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
listFiles();
return null;
}
};
listWorker.execute();
}
});
downloadButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Execute a operação de download em uma thread separada
SwingWorker<Void, Void> downloadWorker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
downloadFile();
return null;
}
};
downloadWorker.execute();
}
});
uploadButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Execute a operação de upload em uma thread separada
SwingWorker<Void, Void> uploadWorker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
uploadFile();
return null;
}
};
uploadWorker.execute();
}
});
panel.add(listButton);
panel.add(downloadButton);
panel.add(uploadButton);
frame.add(panel, BorderLayout.NORTH);
frame.add(scrollPane, BorderLayout.CENTER);
frame.setVisible(true);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void listFiles() {
try {
out.writeUTF("list");
int numFiles = in.readInt();
List<String> files = new ArrayList<>();
for (int i = 0; i < numFiles; i++) {
String fileName = in.readUTF();
files.add(fileName);
}
listModel.clear();
for (String file : files) {
listModel.addElement(file);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void downloadFile() {
String selectedFile = fileList.getSelectedValue();
if (selectedFile != null) {
try {
out.writeUTF("download");
out.writeUTF(selectedFile);
String response = in.readUTF();
if (response.equals("exists")) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setSelectedFile(new File(selectedFile));
int returnVal = fileChooser.showSaveDialog(null);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File savedFile = fileChooser.getSelectedFile();
try (OutputStream fileOut = new FileOutputStream(savedFile)) {
long fileSize = in.readLong();
byte[] buffer = new byte[1024];
int bytesRead;
long totalBytesRead = 0;
while (totalBytesRead < fileSize) {
bytesRead = in.read(buffer, 0, (int) Math.min(1024, fileSize - totalBytesRead));
if (bytesRead == -1) {
break;
}
fileOut.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
}
}
JOptionPane.showMessageDialog(null, "File downloaded successfully.");
}
} else if (response.equals("not_found")) {
JOptionPane.showMessageDialog(null, "File not found on the server.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void uploadFile() {
JFileChooser fileChooser = new JFileChooser();
int returnVal = fileChooser.showOpenDialog(null);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
try {
out.writeUTF("upload");
out.writeUTF(selectedFile.getName());
try (BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream(selectedFile))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fileIn.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
// Indique o término do envio de arquivo
out.writeLong(-1); // -1 para indicar o fim do arquivo
JOptionPane.showMessageDialog(null, "File uploaded successfully.");
// Após o upload bem-sucedido, atualize a lista de arquivos
listFiles();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
我的服务器:
import java.io.*;
import java.net.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FileServer {
private static final int PORT = 12345;
private static final String FILE_DIRECTORY = "C:\\Users\\carlosdaniel\\Desktop\\Code\\Facul\\Redes\\FileTransfer\\Server\\src\\arquivos_servidor\\";
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("Server is running and listening on port " + PORT);
while (true) {
Socket clientSocket = serverSocket.accept();
Runnable clientHandler = new ClientHandler(clientSocket);
executorService.submit(clientHandler);
}
} catch (IOException e) {
e.printStackTrace();
}
}
static class ClientHandler implements Runnable {
private Socket clientSocket;
public ClientHandler(Socket clientSocket) {
this.clientSocket = clientSocket;
}
@Override
public void run() {
try (DataInputStream in = new DataInputStream(clientSocket.getInputStream());
DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream())) {
while (true) {
String command = in.readUTF();
if (command.equals("list")) {
File dir = new File(FILE_DIRECTORY);
String[] files = dir.list();
out.writeInt(files.length);
for (String fileName : files) {
out.writeUTF(fileName);
}
} else if (command.equals("download")) {
String fileName = in.readUTF();
File file = new File(FILE_DIRECTORY + fileName);
if (file.exists()) {
out.writeUTF("exists");
try (InputStream fileIn = new FileInputStream(file)) {
long fileSize = file.length();
out.writeLong(fileSize);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fileIn.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
} else {
out.writeUTF("not_found");
}
} else if (command.equals("upload")) {
String fileName = in.readUTF();
try (OutputStream fileOut = new FileOutputStream(FILE_DIRECTORY + fileName)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
fileOut.write(buffer, 0, bytesRead);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
和我的文件模型:
public class MyFile {
private int id;
private String name;
private byte[] data;
private String fileExtension;
public MyFile(int id, String name, byte[] data, String fileExtension) {
this.id = id;
this.name = name;
this.data = data;
this.fileExtension = fileExtension;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setData(byte[] data) {
this.data = data;
}
public void setFileExtension(String fileExtension) {
this.fileExtension = fileExtension;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public byte[] getData() {
return data;
}
public String getFileExtension() {
return fileExtension;
}
}
答:
所以,你的主要问题就在这里......
} else if (command.equals("upload")) {
String fileName = in.readUTF();
try (OutputStream fileOut = new FileOutputStream(FILE_DIRECTORY + fileName)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
fileOut.write(buffer, 0, bytesRead);
}
}
}
具体说来。除非你打算在另一端关闭流,否则不可能有返回 - 在另一端写入只是意味着服务器将返回到缓冲区。while ((bytesRead = in.read(buffer)) != -1) {
read
-1
-1
-1
虽然有许多可能的解决方案,但最简单的可能也是发送文件大小,然后只读取该数量的字节,例如......
} else if (command.equals("upload")) {
String fileName = in.readUTF();
long totalBytes = in.readLong();
try (OutputStream fileOut = new FileOutputStream(FILE_DIRECTORY + "/" + fileName)) {
byte[] buffer = new byte[1024];
long totalBytesRead = 0;
while (totalBytesRead < totalBytes) {
int bytesRead = in.read(buffer, 0, (int)Math.min(1024, totalBytes - totalBytesRead));
totalBytesRead += bytesRead;
fileOut.write(buffer, 0, bytesRead);
}
}
}
这真的很重要。您不想读取比预期更多的字节,否则您将消耗更多数据,就像下一个命令一样。int bytesRead = in.read(buffer, 0, (int)Math.min(1024, totalBytes - totalBytesRead));
所有“其他”问题
Swing 不是线程安全的,这意味着您不应从事件调度线程的上下文之外更新 UI 或 UI 所依赖的任何状态。
在测试您的代码时,我在列出文件时遇到了许多问题,因为当您在 中执行请求时,您继续从同一后台线程更新。SwingWorker
ListModel
相反,您应该使用 / support 将更新同步回 EDT。publish
process
以下是重新实现,重点是修复线程问题。它使用委派、观察者模式和单一责任概念,因此它利用 API 并尝试将工作流解耦为独立的概念。Action
SwingWorker
它还将大部分客户端套接字工作流封装到一个独立的工作流中,从而进一步解耦代码。
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class FileClient extends JPanel {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 12345;
private ClientSocketManager socketManager;
private JList<String> fileList;
private DefaultListModel<String> listModel;
public static void main(String[] args) {
try {
Socket socket = new Socket(SERVER_HOST, SERVER_PORT);
ClientSocketManager socketManager = new ClientSocketManager(socket);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("File Client");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new FileClient(socketManager));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
public FileClient(ClientSocketManager socketManager) {
this.socketManager = socketManager;
setLayout(new BorderLayout());
listModel = new DefaultListModel<>();
fileList = new JList<>(listModel);
fileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
JScrollPane scrollPane = new JScrollPane(fileList);
JPanel panel = new JPanel();
JButton listButton = new JButton(new ListFilesAction(socketManager, listModel));
JButton downloadButton = new JButton("Download File");
JButton uploadButton = new JButton(new UploadFileAction(socketManager, listModel));
panel.add(listButton);
panel.add(downloadButton);
panel.add(uploadButton);
add(panel, BorderLayout.NORTH);
add(scrollPane, BorderLayout.CENTER);
}
protected class ListFilesAction extends AbstractAction {
private DefaultListModel<String> listModel;
private ClientSocketManager socketManager;
public ListFilesAction(ClientSocketManager socketManager, DefaultListModel<String> listModel) {
putValue(NAME, "List Files");
this.listModel = listModel;
this.socketManager = socketManager;
}
@Override
public void actionPerformed(ActionEvent e) {
ListFilesWorker worker = new ListFilesWorker(socketManager, listModel);
worker.addObserver(new AbstractObservableSwingWorker.DefaultObserver<>() {
@Override
public void didStart(AbstractObservableSwingWorker<Void, String> source) {
listModel.clear();
setEnabled(false);
}
@Override
public void didComplete(AbstractObservableSwingWorker<Void, String> source) {
setEnabled(true);
try {
source.get();
} catch (Exception exp) {
exp.printStackTrace();
if (!(e.getSource() instanceof Component)) {
return;
}
JOptionPane.showMessageDialog((Component) e.getSource(), "Failed to list files from server due to an error", "Error", JOptionPane.ERROR_MESSAGE);
}
}
});
worker.execute();
}
}
protected class UploadFileAction extends AbstractAction {
private ClientSocketManager socketManager;
private DefaultListModel<String> listModel;
public UploadFileAction(ClientSocketManager socketManager, DefaultListModel<String> listModel) {
putValue(NAME, "Upload File");
this.socketManager = socketManager;
this.listModel = listModel;
}
@Override
public void actionPerformed(ActionEvent e) {
JFileChooser fileChooser = new JFileChooser();
Component parent = null;
if (e.getSource() instanceof Component) {
parent = (Component) e.getSource();
}
if (fileChooser.showOpenDialog(parent) == JFileChooser.CANCEL_OPTION) {
return;
}
File selectedFile = fileChooser.getSelectedFile();
if (selectedFile == null) {
return;
}
UploadFileWorker worker = new UploadFileWorker(socketManager, selectedFile, listModel);
worker.addObserver(new AbstractObservableSwingWorker.DefaultObserver<Void, String>() {
@Override
public void didStart(AbstractObservableSwingWorker<Void, String> source) {
setEnabled(false);
}
@Override
public void didComplete(AbstractObservableSwingWorker<Void, String> source) {
setEnabled(true);
Component parent = null;
if (e.getSource() instanceof Component) {
parent = (Component) e.getSource();
}
try {
source.get();
JOptionPane.showMessageDialog(parent, "File uploaded successfully", "Success", JOptionPane.INFORMATION_MESSAGE);
} catch (Exception exp) {
exp.printStackTrace();
JOptionPane.showMessageDialog(parent, "File uploaded failed", "Error", JOptionPane.ERROR_MESSAGE);
}
}
});
worker.execute();
}
}
protected class ListFilesWorker extends AbstractObservableSwingWorker<Void, String> {
private ClientSocketManager socketManager;
private DefaultListModel<String> listModel;
public ListFilesWorker(ClientSocketManager socketManager, DefaultListModel<String> listModel) {
this.socketManager = socketManager;
this.listModel = listModel;
}
@Override
protected Void doInBackground() throws Exception {
socketManager.listFiles(new ClientSocketManager.FileListObserver() {
@Override
public void didRecieveFileName(String name) {
publish(name);
}
});
return null;
}
@Override
protected void process(List<String> chunks) {
listModel.addAll(chunks);
}
public ClientSocketManager getSocketManager() {
return socketManager;
}
public DefaultListModel<String> getListModel() {
return listModel;
}
}
protected class UploadFileWorker extends ListFilesWorker {
private File file;
public UploadFileWorker(ClientSocketManager socketManager, File file, DefaultListModel<String> listModel) {
super(socketManager, listModel);
this.file = file;
}
@Override
protected Void doInBackground() throws Exception {
getSocketManager().uploadFile(file);
return super.doInBackground();
}
}
public abstract class AbstractObservableSwingWorker<T, V> extends SwingWorker<T, V> {
public interface Observer<T, V> {
public void didStart(AbstractObservableSwingWorker<T, V> source);
public void didComplete(AbstractObservableSwingWorker<T, V> source);
public void progressDidChange(AbstractObservableSwingWorker<T, V> source, int progress);
}
public static class DefaultObserver<T, V> implements Observer<T, V> {
@Override
public void didStart(AbstractObservableSwingWorker<T, V> source) {
}
@Override
public void didComplete(AbstractObservableSwingWorker<T, V> source) {
}
@Override
public void progressDidChange(AbstractObservableSwingWorker<T, V> source, int progress) {
}
}
private List<Observer<T, V>> listeners;
public AbstractObservableSwingWorker() {
this.listeners = new ArrayList<>();
addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
Object value = evt.getNewValue();
String propertyName = evt.getPropertyName();
if ("state".equals(propertyName) && value instanceof SwingWorker.StateValue) {
SwingWorker.StateValue state = (SwingWorker.StateValue) value;
if (state == SwingWorker.StateValue.DONE) {
fireDidComplete();
} else if (state == SwingWorker.StateValue.STARTED) {
fireDidStart();
}
} else if ("progress".equals(propertyName) && value instanceof Integer) {
int progress = (int) value;
fireProgressDidChange(progress);
}
}
});
}
public void addObserver(Observer<T, V> observer) {
listeners.add(observer);
}
public void removeObserver(Observer<T, V> observer) {
listeners.add(observer);
}
protected void fireDidComplete() {
if (listeners.isEmpty()) {
return;
}
for (Observer observer : listeners) {
observer.didComplete(this);
}
}
protected void fireDidStart() {
if (listeners.isEmpty()) {
return;
}
for (Observer observer : listeners) {
observer.didStart(this);
}
}
protected void fireProgressDidChange(int progress) {
if (listeners.isEmpty()) {
return;
}
for (Observer observer : listeners) {
observer.progressDidChange(this, progress);
}
}
}
}
ClientSocketManager
导入 java.io.BufferedInputStream; 导入 java.io.Closeable; 导入 java.io.DataInputStream; 导入 java.io.DataOutputStream; 导入 java.io.File; 导入 java.io.FileInputStream; 导入 java.io.IOException; 导入 java.net.Socket; 导入 java.util.ArrayList; 导入 java.util.List;
公共类 ClientSocketManager 实现 Closeable { private Socket 插座; 私有 DataInputStream dis; 私有 DataOutputStream dos;
public interface FileListObserver {
public void didRecieveFileName(String name);
}
public ClientSocketManager(Socket socket) throws IOException {
this.socket = socket;
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
}
@Override
public void close() throws IOException {
try {
dis.close();
} catch (IOException exp) {
}
try {
dos.close();
} catch (IOException exp) {
}
socket.close();
}
public List<String> listFiles(FileListObserver observer) throws IOException {
dos.writeUTF("list");
int numFiles = dis.readInt();
List<String> files = new ArrayList<>(numFiles);
for (int i = 0; i < numFiles; i++) {
String fileName = dis.readUTF();
files.add(fileName);
if (observer != null) {
observer.didRecieveFileName(fileName);
}
}
return files;
}
public void uploadFile(File file) throws IOException {
dos.writeUTF("upload");
dos.writeUTF(file.getName());
dos.writeLong(file.length());
try (BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream(file))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fileIn.read(buffer)) != -1) {
dos.write(buffer, 0, bytesRead);
}
}
dos.flush();
}
}
有关更多详细信息,请查看 Worker Threads 和 SwingWorker 和 How to Use Actions
评论
SwingWorker
SwingWorker
publish
process
SwingWorker
PropertyChangeListener
DONE
SwingWorker
listFiles
listModel
publish
process
DataInputStream#read
-1
-1