双非鼠不想认输 2023-08-14 20:57 采纳率: 50%
浏览 11
已结题

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();
        }
    }
}


  • 写回答

1条回答 默认 最新

  • CSDN-Ada助手 CSDN-AI 官方账号 2023-08-14 23:19
    关注

    【以下回答由 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();
            }
        }
    }
    

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

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

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

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


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

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 11月28日
  • 创建了问题 8月14日

悬赏问题

  • ¥15 vue2项目里接口请求报错,把接口放到浏览器里面正常
  • ¥15 Qt安装后运行不了,这是我电脑的问题吗
  • ¥15 数据量少可以用MK趋势分析吗
  • ¥15 使用VH6501干扰RTR位,CANoe上显示的错误帧不足32个就进入bus off快慢恢复,为什么?
  • ¥15 大智慧怎么编写一个选股程序
  • ¥100 python 调用 cgps 命令获取 实时位置信息
  • ¥15 两台交换机分别是trunk接口和access接口为何无法通信,通信过程是如何?
  • ¥15 C语言使用vscode编码错误
  • ¥15 用KSV5转成本时,如何不生成那笔中间凭证
  • ¥20 ensp怎么配置让PC1和PC2通讯上