文件上传下载系列——大文件分片上传

简介: 文件上传下载系列——大文件分片上传

概述

  文件分片上传又叫文件切片上传,是将大文件切分成小的文件片段,分别上传到服务器,并在服务器端将这些文件片段合并成完整的文件。

实现步骤:

 假设有一个需要上传的文件 “example.jpg”,大小为 102 MB。我们将其切分为大小为 5 MB 的文件片段进行上传,要进行下面几个步骤:


客户端将 “example.jpg” 切分成 21 个文件片段,每个文件片段大小为 5 MB,最后一个分片大小不足5MB。


客户端逐个将文件片段上传到服务器,按顺序依次上传。每个文件片段都携带对应的分片索引,例如:第一个文件片段携带索引 1,第二个文件片段携带索引 2,依此类推。


服务器端接收到文件片段后,对每个文件片段进行校验和保存。校验和算法如 MD5 或 SHA1 可以用于校验文件片段的完整性。服务器将每个文件片段保存到临时存储区。


当所有文件片段上传完成后,服务器根据文件片段的顺序或索引,将这些文件片段按照正确的顺序合并,生成完整的文件 “example.jpg”。


服务器将完整的文件保存到指定的目标位置,并通知客户端上传成功。

应用场景

大文件上传:在上传大文件时,文件分片可以减小每个请求的数据量,降低传输失败率,同时可以在上传失败后,只需重新上传丢失的分片,而不需要重新上传整个文件。


视频直播:直播时,需要将视频实时传输到服务器,但由于网络传输不稳定,可能导致视频传输中断。通过将视频分片上传,可以减小每个请求的数据量,降低视频传输失败率。


CDN 分发:CDN 分发中使用文件分片上传可以提高文件上传速度和分发速度。CDN 节点会缓存文件的分片,然后在 CDN 节点之间进行分发,以加速文件的传输。

代码实操:

前端:

文件切片:

  将要上传的大文件按照固定大小或指定的分片大小进行切片。通常使用二进制流方式切割,确保每个文件片段的大小相同或接近。

分片上传:

  将每个文件片段逐个上传到服务器。上传可以通过HTTP协议的POST请求或者其他上传协议进行,每个文件片段都携带对应的分片索引或标识信息。

  代码如下:

// 设置每个分片的大小(字节)
const CHUNK_SIZE = 1 * 1024 * 1024; // 1MB
// 获取文件的MD5码
function getMD5(file) {
  // 在这里实现获取文件的MD5码的逻辑
  // 返回文件的MD5码
  // 可以使用第三方库或自己实现MD5算法来计算文件的MD5码
}
// 分片上传文件
function uploadFile(file) {
  const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
  let currentChunk = 0;
  // 递归上传分片
  function uploadChunk() {
    const start = currentChunk * CHUNK_SIZE;
    const end = Math.min(start + CHUNK_SIZE, file.size);
    const chunk = file.slice(start, end);
    const formData = new FormData();
    formData.append('file', chunk);//文件块
    formData.append('chunkIndex', currentChunk); //当前块号
    formData.append('totalChunks', totalChunks); //总块数
    formData.append('chunkMD5', getMD5(chunk)); //当前块的MD5
    formData.append('fileName', file.name); //文件名
    // 使用axios发送分片数据到服务器
    axios.post('/upload', formData)
      .then(response => {
        // 分片上传成功
        currentChunk++;
        if (currentChunk < totalChunks) {
          // 继续上传下一个分片
          uploadChunk();
        } else {
          // 所有分片上传完成
          console.log('文件上传完成');
        }
      })
      .catch(error => {
        // 分片上传失败
        console.error('分片上传失败:', error);
      });
  }
  // 开始上传第一个分片
  uploadChunk();
}
// 选择文件并触发上传
const fileInput = document.getElementById('file-input');
fileInput.addEventListener('change', (event) => {
  const file = event.target.files[0];
  if (file) {
    uploadFile(file);
  }
});

后端:

校验和保存:

  在服务器端接收到文件片段后,对每个文件片段进行校验,使用校验和算法如MD5或SHA1计算校验和,确保文件片段的完整性。同时,将每个文件片段保存到临时存储区,通常是磁盘或内存。

合并文件片段:

  在所有文件片段上传完毕后,服务器端根据文件片段的顺序或标识,将这些文件片段按照正确的顺序进行合并,生成完整的文件。

完成上传:

  当文件合并完成后,将生成的完整文件保存到指定的目标位置,并返回上传成功的标识或信息给客户端,表示文件上传完成。

  代码如下:

@Controller
public class UploadController {
    private final static String utf8 ="utf-8";
    @RequestMapping("/upload")
    @ResponseBody
    public void upload(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //分片
        response.setCharacterEncoding(utf8);//向浏览器输出 统一用该编码输出
        Integer schunk = null;//当前是哪个分片
        Integer schunks = null;//总分片数
        String name = null;//文件名
        String uploadPath = "E:\\ceshi";//文件临时存储目录,存储文件片
        BufferedOutputStream os = null;//文件合并时用的流
        try{
            DiskFileItemFactory factory = new DiskFileItemFactory();//设计文件上传参数
            factory.setSizeThreshold(1024);//缓冲区大小1024字节(文件先读到内存中,在往硬盘里写,中间需要缓存区)
            factory.setRepository(new File(uploadPath));//设置临时目录
            ServletFileUpload upload = new ServletFileUpload(factory);//upload帮助解析request
            upload.setFileSizeMax(5l *1024l *1024l*1024l);//设置参数,多文件上传,限制限制单个文件大小最大为5G
            upload.setSizeMax(10l *1024l *1024l*1024l);//限制所有文件最大为10G
            List<FileItem> items = upload.parseRequest(request);
            //解析文件
            for(FileItem item : items){
                //判断是否是文件对象
                if(item.isFormField()){
                    //取出分片信息
                    if("chunkIndex".equals(item.getFieldName())){
                        schunk = Integer.parseInt(item.getString(utf8));//当前是哪个分片
                    }
                    if("totalChunks".equals(item.getFieldName())){
                        schunks = Integer.parseInt(item.getString(utf8));//总分片数
                    }
                    if("chunkMD5".equals(item.getFieldName())){
                        fileMD5 = item.getString(utf8);//文件块MD5
                    }
                    if("fileName".equals(item.getFieldName())){
                        name = item.getString(utf8);//文件名
                    }
                }
            }
            for(FileItem item : items){
                if(!item.isFormField()){
                    String temFileName = name;//临时目录
                    if(name != null){
                        if(schunk != null){
                            temFileName = schunk +"_"+name;//生成临时文件
                        }
                        File temFile = new File(uploadPath,temFileName);
                        //给临时文件生成MD5,和前端传来的MD5比较的代码略
                        //如果文件不存在就上传
                        if(!temFile.exists()){
                            item.write(temFile);
                        }
                    }
                }
            }
            //文件合并,判断是不是传到最后一个分片了
            if(schunk != null && schunk.intValue() == schunks.intValue()-1){
                File tempFile = new File(uploadPath,name);//准备临时文件把分片写入文件
                os = new BufferedOutputStream(new FileOutputStream(tempFile));//向流中写入文件信息
                //找到所有分片
                //判断所有分片是都存在
                for(int i=0 ;i<schunks;i++){
                    File file = new File(uploadPath,i+"_"+name);
                    //如果有分片不存在,先休眠,直到所有分片都存在
                    while(!file.exists()){
                        Thread.sleep(100);
                    }
                    //读取分片
                    byte[] bytes = FileUtils.readFileToByteArray(file);
                    os.write(bytes);//写入到流中
                    os.flush();//
                    file.delete();//删除临时文件
                }
                os.flush();
            }
            response.getWriter().write("上传成功"+name);
        }finally {
            try{
                if(os != null){
                    os.close();//关文件流
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

总结

优点:

支持大文件上传:文件分片上传能够处理大文件,通过将文件切分成小片段进行上传,可以绕过上传文件大小限制。


断点续传:由于文件分片上传将文件拆分成多个片段,当上传过程中出现异常或中断时,只需要重新上传失败的片段,而不需要重新上传整个文件,实现了断点续传的功能。


提高上传效率:文件分片上传能够并行上传多个片段,从而提高上传速度。同时,由于每个片段相对较小,上传的网络延迟对整体上传时间的影响较小。


服务器资源优化:文件分片上传可以分散服务器的负载,由于每个片段可以在不同的服务器上处理,可以更好地利用服务器资源。

缺点:

处理逻辑复杂:文件分片上传需要在客户端和服务器端实现分片切割、上传顺序管理、校验和合并等复杂的逻辑,增加了开发和维护的难度。


需要额外存储空间:文件分片上传需要在服务器端临时存储每个文件片段,可能会占用额外的存储空间。


管理和维护成本:文件分片上传需要处理分片上传的逻辑,并管理临时文件的存储和清理,增加了系统管理和维护的复杂度。

升华

 除了文件分片传输,还有其他类似的数据分片传输情况。


TCP报文分段传输:在TCP协议中,会将大的数据包分割成多个较小的报文段进行传输。这是由于TCP协议对数据包大小进行了限制,根据网络传输的情况和MTU(最大传输单元)的限制,TCP会将数据切割成适当的大小进行分段传输,以确保数据的可靠传输。


IP分片传输:在IP协议中,当数据包的大小超过网络的MTU(最大传输单元)时,IP层会将数据包分割成多个较小的分片进行传输。接收方会重新组装这些分片以还原原始的数据包。


数据库分页查询:在数据库查询中,当结果集非常大时,可以使用分页查询来分割结果集并逐页进行传输。客户端可以根据需要逐页获取数据,而不必一次性获取所有数据。


视频流传输:在实时视频流传输中,视频数据通常会被分割成小的数据包,然后通过网络进行传输。这样可以降低延迟,并支持实时的流媒体播放。


大数据处理:在大数据处理中,数据可能会被分割成小的块或分区进行并行处理。这样可以利用分布式计算框架的能力,并提高处理效率。


 这些都是将大的数据切割成小的片段进行传输或处理的情况。通过分片传输,可以提高数据传输的效率、降低网络延迟,并支持更灵活的数据处理和传输方式。


相关实践学习
Serverless极速搭建Hexo博客
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
相关文章
|
4月前
|
Web App开发 存储 移动开发
大文件上传实现方式比较
大文件上传实现方式比较
76 5
|
7月前
|
前端开发 NoSQL Redis
如何实现大文件上传:秒传、断点续传、分片上传
如何实现大文件上传:秒传、断点续传、分片上传
563 0
|
7月前
大文件分片上传,断点续传,秒传 示例(待更新...)
大文件分片上传,断点续传,秒传 示例(待更新...)
|
存储 前端开发 NoSQL
注册java实现文件分片上传并且断点续传
一、简单的分片上传 针对第一个问题,如果文件过大,上传到一半断开了,若重新开始上传的话,会很消耗时间,并且你也并不知道距离上次断开时,已经上传到哪一部分了。因此我们应该先对大文件进行分片处理,防止上面提到的问题。
分片上传和断点续传的区别?实现思路是什么?
分片上传和断点续传的区别?实现思路是什么?
|
存储 前端开发 算法
minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现(三)
minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现
631 0
|
前端开发 Java 数据库
minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现(一)
minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现
1125 0
|
数据安全/隐私保护
minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现(二)
minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现
431 0
|
前端开发 关系型数据库 MySQL
大文件上传
大文件上传
170 0
|
存储 前端开发 NoSQL
java实现文件分片上传并且断点续传
针对第一个问题,如果文件过大,上传到一半断开了,若重新开始上传的话,会很消耗时间,并且你也并不知道距离上次断开时,已经上传到哪一部分了。因此我们应该先对大文件进行分片处理,防止上面提到的问题。
367 0