Java Socket 多线程文件传输上传后卡住

问题描述 投票:0回答:1

我已经使用 Java Socket 和多线程实现了一个服务器客户端程序。该程序允许客户端将文件发送到服务器、列出文件并下载该文件。但我遇到了一个问题,当我的客户端上传文件时,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;
    }
}
java multithreading file swing sockets
1个回答
0
投票

那么,您的主要问题就在这里......

} 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

相反,您应该利用

publish
/
process
支持将更新同步回 EDT。

以下是重点修复线程问题的重新实现。它使用委托、观察者模式和单一职责概念,因此它利用

Action
API 并尝试将
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 { 私有Socket套接字; 私有数据输入流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 以及如何使用操作了解更多详细信息

© www.soinside.com 2019 - 2024. All rights reserved.