socket传输文件完成后,文件打不开,小文件可以,但似乎分块传输就不行,是初步怀疑黏包问题?希望排查修正一下

socket传输文件完成后,文件打不开,小文件可以,但似乎分块传输就不行,是初步怀疑黏包问题?希望排查修正一下
clent:

public class Client {
    public static void main(String[] args) {
        File file=new File("D:\\data.mp4");
        try (
                Socket socket = new Socket("127.0.0.1", 12345);
                FileInputStream fileInputStream=new FileInputStream(file);
                ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());
        ) {
            byte[] buffer = new byte[1024000];
            int blockSize = 1024000; // 块大小
            //向上取整
            int totalBlocks = (int) Math.ceil((double) file.length() / blockSize);
            long bytesRead,chunkNumber=1;
            while((bytesRead=fileInputStream.read(buffer))!=-1){
                FileInfo fileInfo=new FileInfo(file.getName(),totalBlocks, Math.toIntExact(chunkNumber),bytesRead,buffer);
                objectOutputStream.writeObject(fileInfo);
                objectOutputStream.flush();
                chunkNumber++;
            }
//            //往服务器写出结束标记
//            socket.shutdownOutput();
            System.out.println("传输完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

FileInfo :

@Data
public class FileInfo implements Serializable {
    private String fileName;
    private int totalBlocks;//分块总数
    private Integer chunkNumber;//当前分块序号
    private long currentChunkSize;//当前分块大小
    private byte[] data;

    public FileInfo(String fileName, int totalBlocks, Integer chunkNumber, Long currentChunkSize, byte[] data) {
        this.fileName = fileName;
        this.totalBlocks = totalBlocks;
        this.chunkNumber = chunkNumber;
        this.currentChunkSize = currentChunkSize;
        this.data = data;
    }
}


Server :

public class Server {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(12345)) {
             while (true){
                 Socket socket = serverSocket.accept();
                 ServerThread serverThread=new ServerThread(socket);
                 Thread thread=new Thread(serverThread);
                 thread.start();
             }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


ServerThread :

public class ServerThread implements Runnable{
    private Socket socket;

    public ServerThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try (
                ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream());
        ) {
            while (true){
                FileInfo fileInfo=(FileInfo)objectInputStream.readObject();
                String fileName = fileInfo.getFileName();
                long fileSize = fileInfo.getCurrentChunkSize();
                System.out.println("正在接收文件: " + fileName + " (" + fileSize + " bytes)");
                FileOutputStream fos = new FileOutputStream(fileName,true);
                fos.write(fileInfo.getData(),0,(int) fileInfo.getCurrentChunkSize());
                fos.flush();
                fos.close(); // 关闭 FileOutputStream 对象
                if(fileInfo.getChunkNumber()==fileInfo.getTotalBlocks()){
                    System.out.println("文件传输完成!");
                    break;
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}


【以下回答由 GPT 生成】

问题分析

根据提供的信息,问题出现在使用Java的Socket传输文件时,传输完成后无法打开接收到的文件。小文件可以正常传输,但是对于大文件来说,似乎分块传输就出错了,产生的猜测是黏包问题导致的。

对于黏包问题,一般发生在数据的发送和接收过程中,并且是因为一次接收到的数据大小超过了期望的大小,导致多个数据包黏在一起,造成解析错误。

在这个问题中,我们可以猜测黏包问题是由于数据不完整或者传输不及时导致的。当传输大文件时,我们需要分块读取文件内容并发送,但是每次发送数据时,都需要等待接收方确认接收完毕,否则可能会导致多次发送数据,从而发生黏包问题。

解决方案

为了解决这个问题,我们需要对代码进行一些调整和优化。

首先,我们需要在发送数据之后等待接收方的确认。可以在发送每个数据块之后,都加入一个确认消息的处理。

然后,为了保证每个块的信息都能够正确地传输,我们需要将文件块的具体内容也加入到FileInfo对象中进行传输。

最后,接收方收到每个块的时候需要进行处理,将接收到的数据写入到文件中。

下面是更新后的代码:

public class Client {
    public static void main(String[] args) {
        File file = new File("D:\\data.mp4");
        try (
            Socket socket = new Socket("127.0.0.1", 12345);
            FileInputStream fileInputStream = new FileInputStream(file);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
        ) {
            byte[] buffer = new byte[1024000];
            int blockSize = 1024000; // 块大小
            int totalBlocks = (int) Math.ceil((double) file.length() / blockSize); // 分块总数

            long bytesRead, chunkNumber = 1;

            while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                FileInfo fileInfo = new FileInfo(file.getName(), totalBlocks, (int) Math.toIntExact(chunkNumber), bytesRead, buffer);
                objectOutputStream.writeObject(fileInfo);
                objectOutputStream.flush();
                chunkNumber++;

                // 等待接收方的确认
                String confirmation = (String) objectInputStream.readObject();
                if (!confirmation.equals("OK")) {
                    System.out.println("接收方确认错误!");
                    break;
                }
            }

            System.out.println("传输完成!");
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

接下来,我们需要调整服务器端的代码以接收和写入文件数据。

public class Server {
    public static void main(String[] args) {
        try (
            ServerSocket serverSocket = new ServerSocket(12345);
            Socket socket = serverSocket.accept();
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
        ) {
            FileInfo fileInfo;

            while ((fileInfo = (FileInfo) objectInputStream.readObject()) != null) {
                FileOutputStream fileOutputStream = new FileOutputStream("D:\\received_" + fileInfo.getFileName(), true);
                fileOutputStream.write(fileInfo.getData(), 0, fileInfo.getSize());

                // 发送确认消息
                objectOutputStream.writeObject("OK");
                objectOutputStream.flush();

                System.out.println("块 " + fileInfo.getChunkNumber() + " 接收并写入完成!");

                if (fileInfo.getChunkNumber() == fileInfo.getTotalBlocks()) {
                    // 所有块已经接收完毕
                    break;
                }
            }

            System.out.println("文件接收完毕!");
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

更新后的代码在每次发送一个块之后,等待接收方的确认消息,确认成功后才继续发送下一个块。

接收方接收到每个块之后,将其写入到指定的文件中,并发送确认消息。

这样,通过加入确认机制,我们可以尽可能地避免黏包问题的发生,从而保证文件能够成功传输并且正确打开。

请尝试使用更新后的代码,如果还有其他问题,或者对解决方案有疑问,请随时告诉我。


如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^