使用 Java 套接字和 java.nio.file.Files.copy 发送文件

Send file with Java Socket and java.nio.file.Files.copy

我正在尝试制作一个简单的 server/client 应用程序来发送消息和文件。

我的客户端首先发送文件名,然后是文件本身,最后等待服务器的响应。

我的服务器做相反的事情,读取文件名,读取文件,发送响应。

问题是客户端卡在 String response = dataInputStream.readUTF(); 当服务器卡在 Files.copy(Paths.get(fileName), dataOutputStream);

我试图从客户端中删除 String response = dataInputStream.readUTF();,但没有它也能正常工作。有人可以帮助我理解为什么在发送文件后执行 readUtf() 时卡住吗?

谢谢

这是我的客户

public static void main(String[] args) throws IOException {
    String fileName = "hello.txt";

    try (Socket clientSocket = new Socket(HOST, PORT)) {
        DataInputStream dataInputStream = new DataInputStream(clientSocket.getInputStream());
        DataOutputStream dataOutputStream = new DataOutputStream(clientSocket.getOutputStream());

        dataOutputStream.writeUTF(fileName);

        Files.copy(Paths.get(fileName), dataOutputStream);

        String response = dataInputStream.readUTF();
        LOGGER.info(response);
    }
}

这是我的服务器

    public static void main(String args[]) throws IOException {

    try (ServerSocket ss = new ServerSocket(PORT)) {
        Socket clientSocket = ss.accept();

        DataOutputStream dataOutputStream = new DataOutputStream(clientSocket.getOutputStream());
        DataInputStream dataInputStream = new DataInputStream(clientSocket.getInputStream());

        String fileName = dataInputStream.readUTF();

        Files.copy(dataInputStream, Paths.get(fileName));

        dataOutputStream.writeUTF("New file saved");
    }
}

这是我从客户端向服务器发送文件的整个工作示例。 当我在服务器上使用 Files.copy(...) 时,我不确定特别好我被卡住了所以你可以检查我是否添加了布尔条件如果读取小于缓冲区大小然后没有项目存在流因此退出并关闭文件。

package com.company.socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

class SocketClient {
    public static void main(String[] args) {
        Thread clientThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try (Socket serverSocket = new Socket("localhost", 5000)) {
                    final DataOutputStream outputStream = new DataOutputStream(serverSocket.getOutputStream());
                    final BufferedReader br = new BufferedReader(new InputStreamReader(serverSocket.getInputStream()));
                    Files.copy(Path.of("hello.txt"), serverSocket.getOutputStream());
                    outputStream.flush();
                    System.out.println("file sent from client");
                    String content = null;
                    while ((content = br.readLine()) != null) {
                        System.out.println("Client response: " + content);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        clientThread.start();

    }
}

public class SocketServer {
    public static void main(String[] args) throws InterruptedException, IOException {
        final Thread serverThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Started server");
                List<Socket> clients = new ArrayList<>();
                Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("Server shutdown!");
                        final List<String> collect = clients.stream()
                                .map(socket -> {
                                    try {
                                        socket.close();
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                    return "Closed client: " + socket.getInetAddress().getHostAddress();
                                })
                                .collect(Collectors.toList());
                        collect.forEach(System.out::println);
                    }
                }));
                ServerSocket serverSocket = null;
                try {
                    serverSocket = new ServerSocket(5000);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                while (true) {
                    try (Socket client = serverSocket.accept()) {
                        clients.add(client);
                        System.out.println("CLient connected: " + client.getInetAddress().getHostAddress() + ":" + client.getPort());
                        client.getOutputStream().write("Hello from server!".getBytes());
                        client.getOutputStream().flush();
                        final InputStream inputStream = client.getInputStream();
                        System.out.println("copyingReceiving file on server");
                        copyFile(inputStream);
                        System.out.println("File received successfully");
                        client.getOutputStream().write("\nFile received successfully".getBytes());
                        client.getOutputStream().flush();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            }

            void copyFile(InputStream inputStream) throws IOException {
                final byte[] buffer = new byte[2048];
                final FileOutputStream outputStream = new FileOutputStream("file-" + UUID.randomUUID().toString() + ".txt");
                int length = 0;
                while ((length = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, length);
                    outputStream.flush();
                    if (length < buffer.length) break;
                }
                outputStream.close();
            }
        });
        serverThread.start();

    }
}

服务器输出

Started server
CLient connected: 127.0.0.1:38118
copyingReceiving file on server
File received successfully

客户端输出

file sent from client
Client response: Hello from server!
Client response: File received successfully

根据我的评论,这里的问题是 input/output 流对仅在 try-with-resources 块退出时关闭。所以在服务器上,行 Files.copy(dataInputStream, Paths.get(fileName)) 不会因为没有到达流的末尾而退出,因为客户端中的流只有在到达客户端中的 try 块末尾时才会自动关闭。

但是客户端在 try 结束之前 运行 String response = dataInputStream.readUTF() 所以它从未真正关闭流。因此他们都卡住了。

最简单的解决方案是在客户端中的文件名后发送文件大小,并在 Server 中替换使用 Files.copy - 只需读取 Server 中的文件大小并传输那么多字节到 Files.newOutputStream。然后下一个字节就没有阻塞了。

这是一个示例客户端,其中包含发送大小的额外行:

Path path = Path.of(fileName);

try (Socket clientSocket = new Socket(HOST, PORT)) {
    DataInputStream dataInputStream = new DataInputStream(clientSocket.getInputStream());
    DataOutputStream dataOutputStream = new DataOutputStream(clientSocket.getOutputStream());

    dataOutputStream.writeUTF(fileName);
    dataOutputStream.writeLong(Files.size(path));
    Files.copy(path, dataOutputStream);

    String response = dataInputStream.readUTF();
    System.out.println(response);
}

这是调整后的服务器,它用 size 字节的循环替换 File.copy

try (ServerSocket ss = new ServerSocket(PORT)) {
    Socket clientSocket = ss.accept();

    DataOutputStream dataOutputStream = new DataOutputStream(clientSocket.getOutputStream());
    DataInputStream dataInputStream = new DataInputStream(clientSocket.getInputStream());

    String fileName = dataInputStream.readUTF();
    long len = dataInputStream.readLong();
    int read = 0;
    byte[] arr = new byte[8192];
    try(var fos = Files.newOutputStream(Paths.get(fileName))) {
        while(len > 0 && (read = dataInputStream.read(arr,0, (int)Math.min(len, arr.length))) != -1) {
            len -= read;
            fos.write(arr, 0, read);
        }
    }
    dataOutputStream.writeUTF("New file saved");
}