前端之实现大文件上传的解决方案———断点续传

介绍

断点续传是一种网络数据传输方式,允许从中断的地方恢复下载或上传操作,而不是从头开始。这对于大文件传输尤其有用,因为它可以节省时间并减少网络资源的浪费。在前端开发中,实现大文件的断点续传可以提升用户体验,尤其是在网络不稳定或速度较慢的情况下。

场景

  1. 用户上传大文件至服务器,如视频、图片集合或大型文档。
  2. 用户下载服务器上的大文件,如高清视频、大型软件安装包。
  3. 网络不稳定导致传输中断,用户希望从中断处继续传输。

 

原理

断点续传的基本原理是将大文件分割成多个小块,然后分别传输这些小块。每个小块都有自己的编号,客户端和服务器端都记录已成功传输的块。如果传输过程中断,客户端可以从最后成功传输的块之后继续传输,而不是从头开始。

 

实现方案

  1. 文件分片:将大文件分割成多个小块。
  2. 并行上传:为了提高上传速度,可以同时上传多个小块。
  3. 校验和记录:每个文件块传输前后都需要进行校验,确保数据的完整性,同时记录已上传的块。
  4. 请求恢复:在传输中断后,客户端向服务器请求恢复中断的传输。
  5. 服务器支持:服务器需要能够理解客户端的恢复请求,并提供未完成传输的文件块。

示例代码说明 

以下是使用JavaScript实现大文件断点续传的一个简单示例:

// 假设我们有一个文件对象
let file = document.getElementById('fileInput').files[0];

// 分割文件
const chunkSize = 2 * 1024 * 1024; // 2MB
let chunks = [], currentChunk = 0, totalChunks = 0;
for (let i = 0; i < file.size; i += chunkSize) {
    chunks.push(file.slice(i, i + chunkSize));
    totalChunks++;
}

// 上传函数
function uploadNextChunk() {
    if (currentChunk >= totalChunks) return;

    const chunk = chunks[currentChunk];
    const formData = new FormData();
    formData.append('file', chunk);
    formData.append('chunkNumber', currentChunk);
    formData.append('totalChunks', totalChunks);

    fetch('/upload', { // 假设服务器端点是 '/upload'
        method: 'POST',
        body: formData,
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            currentChunk++;
            uploadNextChunk(); // 上传下一块
        } else {
            console.error('Upload error: ', data.message);
        }
    })
    .catch(error => console.error('Upload error: ', error));
}

// 开始上传
uploadNextChunk();

这段代码首先将文件分割成多个2MB的块,然后使用递归函数uploadNextChunk来逐个上传这些块。在上传过程中,我们使用FormData对象来构建上传请求的正文,并发送到服务器。服务器需要相应地处理这些请求,并在上传中断时能够从中断的地方恢复。

请注意,这只是一个简化的示例,实际的实现可能需要考虑更多的因素,如错误处理、上传进度显示、服务器端的逻辑等。此外,为了实现断点续传,服务器端也需要相应的支持。

1. 文件分片

文件分片是将大文件分割成多个小块的过程。这可以通过JavaScript的Blob对象来实现。

示例代码:

function splitFile(file, chunkSize) {
  const chunks = [];
  for (let start = 0; start < file.size; start += chunkSize) {
    const end = Math.min(start + chunkSize, file.size);
    chunks.push(file.slice(start, end));
  }
  return chunks;
}

const file = document.getElementById('fileInput').files[0];
const chunkSize = 2 * 1024 * 1024; // 2MB
const chunks = splitFile(file, chunkSize);

2. 并行上传

并行上传可以提高上传速度,特别是当网络带宽允许多个连接同时进行时。这可以通过JavaScript的Promise.all来实现。

示例代码:

 

async function uploadChunks(chunks, fileIdentifier) {
  const uploadPromises = chunks.map((chunk, index) => {
    const formData = new FormData();
    formData.append('file', chunk);
    formData.append('index', index);
    formData.append('filename', fileIdentifier);
    
    return fetch('/upload', {
      method: 'POST',
      body: formData,
    }).then(response => response.json());
  });

  return Promise.all(uploadPromises);
}

const fileIdentifier = 'unique_file_identifier'; // 服务器用来识别文件的标识
uploadChunks(chunks, fileIdentifier).then(results => {
  if (results.every(result => result.success)) {
    console.log('All chunks uploaded successfully.');
  } else {
    console.error('Some chunks failed to upload.');
  }
});

3. 校验和记录

校验和用于验证数据的完整性。记录已上传的块可以用于断点续传。

示例代码:

// 假设服务器返回每个块的校验和
async function verifyChunks(chunks) {
  const results = await uploadChunks(chunks, fileIdentifier);
  const checksums = results.map(result => result.checksum);
  return checksums;
}

// 假设有一个函数用于记录校验和
function recordChecksums(checksums) {
  // 将校验和存储在localStorage或数据库中
}

// 上传并记录校验和
verifyChunks(chunks).then(recordChecksums);

 

4. 请求恢复

当传输中断时,客户端需要请求恢复中断的传输。

示例代码:

function resumeUpload(fileIdentifier, lastUploadedIndex) {
  const remainingChunks = chunks.slice(lastUploadedIndex + 1);
  return uploadChunks(remainingChunks, fileIdentifier);
}

// 假设从localStorage或数据库中获取最后上传的块的索引
const lastUploadedIndex = getLastUploadedIndex(fileIdentifier);
if (lastUploadedIndex !== undefined) {
  resumeUpload(fileIdentifier, lastUploadedIndex).then(results => {
    if (results.every(result => result.success)) {
      console.log('Resuming upload completed.');
    } else {
      console.error('Failed to resume upload.');
    }
  });
}

5. 服务器支持

服务器端需要能够接收分片数据,处理并行上传,并支持断点续传。

示例伪代码:

/upload (POST method)
  Receive file chunk data
  Validate chunk index and file identifier
  Save the chunk to the storage
  Calculate and return the checksum of the chunk

 请注意,这些示例代码仅用于说明断点续传的实现原理,实际应用中需要考虑更多的细节,如错误处理、安全性、性能优化等。服务器端的实现也需要相应的逻辑来处理分片上传、验证、存储和恢复。

6.完整案例

为了实现一个简单的断点续传功能,使用Node.js作为后端服务器,并且使用Express框架来简化HTTP请求的处理。前端将使用JavaScript的Fetch API来处理文件的上传。

后端实现 (Node.js + Express)

npm install express body-parser multipart-parser --save

 以下是Node.js服务器的示例代码:

const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const multipartParser = require('parse-multipart');

const app = express();
const port = 3000;

// 配置中间件
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// 文件上传的端点
app.post('/upload', (req, res) => {
  const body = req.body;
  const file = req.files.file;

  // 假设我们有一个文件标识符和块编号
  const fileIdentifier = body.fileIdentifier;
  const chunkNumber = parseInt(body.chunkNumber);
  const totalChunks = parseInt(body.totalChunks);

  // 定义文件保存的路径和文件名
  const filePath = `./uploads/${fileIdentifier}`;
  const chunkPath = `${filePath}/chunk_${chunkNumber}`;

  // 检查文件标识符对应的文件夹是否存在,如果不存在则创建
  if (!fs.existsSync(filePath)) {
    fs.mkdirSync(filePath, { recursive: true });
  }

  // 保存文件块
  const fileStream = fs.createWriteStream(chunkPath);
  fileStream.write(file.data, 'binary', (err) => {
    if (err) {
      return res.status(500).send('Error saving file chunk.');
    }
    res.status(200).json({ success: true, message: 'Chunk uploaded successfully.' });
  });

  // 检查是否所有块都已上传
  const allChunksUploaded = Array.from({ length: totalChunks }, (_, i) =>
    fs.existsSync(`${filePath}/chunk_${i + 1}`)
  );

  if (allChunksUploaded.every(Boolean)) {
    // 合并文件块
    const chunks = fs.readdirSync(filePath).map(chunk => fs.readFileSync(path.join(filePath, chunk)));
    const output = fs.createWriteStream(`./uploads/${fileIdentifier}.complete`);

    chunks.forEach((chunk) => output.write(chunk));
    
    // 删除临时文件夹
    fs.rmSync(filePath, { recursive: true });

    res.status(200).json({ success: true, message: 'File assembled successfully.' });
  }
});

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

前端实现 (HTML + JavaScript)

以下是前端HTML和JavaScript的示例代码,用于选择文件并上传:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File Upload with Chunking</title>
</head>
<body>
    <input type="file" id="fileInput" />
    <button onclick="uploadFile()">Upload</button>

    <script>
        const fileInput = document.getElementById('fileInput');
        let file, chunks, fileIdentifier;

        fileInput.addEventListener('change', () => {
            file = fileInput.files[0];
            fileIdentifier = Date.now().toString(); // 简单的文件标识符
            chunks = splitFile(file);
        });

        function splitFile(file, chunkSize = 2 * 1024 * 1024) {
            const chunks = [];
            for (let i = 0; i < file.size; i += chunkSize) {
                chunks.push(file.slice(i, i + chunkSize));
            }
            return chunks;
        }

        async function uploadFile() {
            for (let i = 0; i < chunks.length; i++) {
                const chunk = chunks[i];
                const formData = new FormData();
                formData.append('file', chunk);
                formData.append('fileIdentifier', fileIdentifier);
                formData.append('chunkNumber', i);
                formData.append('totalChunks', chunks.length);

                try {
                    const response = await fetch('http://localhost:3000/upload', {
                        method: 'POST',
                        body: formData,
                    });
                    if (!response.ok) {
                        throw new Error(`HTTP error! status: ${response.status}`);
                    }
                    console.log('Chunk uploaded successfully');
                } catch (error) {
                    console.error('Upload error:', error);
                }
            }
        }
    </script>
</body>
</html>

在这个示例中,前端使用<input type="file">允许用户选择一个文件,然后通过splitFile函数将文件分割成多个块。当用户点击“Upload”按钮时,uploadFile函数被调用,它将循环遍历所有的块,并将它们作为表单数据上传到服务器。

后端使用Express处理上传请求,并将文件块保存在本地磁盘上。一旦所有块都上传完毕,服务器将它们合并成原始文件,并删除临时文件块。

请注意,这个示例是一个简化的版本,没有实现所有可能的错误处理、安全性措施(如验证用户权限、限制文件大小和类型等)以及生产环境中可能需要的其他功能。在实际部署之前,需要添加这些功能以确保系统的健壮性和安全性。

总结 

断点续传是一种在网络传输中提高效率和可靠性的技术,特别适用于大文件的上传和下载。以下是实现大文件断点续传的关键步骤的总结:

  1. 文件分片:将大文件分割成多个小块,这允许并行上传和从中断处恢复。

  2. 并行上传:通过同时上传多个文件块,可以提高整体的上传速度。

  3. 校验和记录:每个文件块在上传前后都进行校验,以确保数据的完整性。同时,记录已成功上传的块,为断点续传提供依据。

  4. 请求恢复:当传输中断时,客户端使用记录的信息请求从最后成功上传的块继续上传。

  5. 服务器支持:服务器端需要能够接收分片数据,验证块的完整性,并支持断点续传的逻辑。

在前端实现中,JavaScript提供了强大的API来处理文件操作和网络请求。通过使用Blob对象分割文件,FormData对象构建请求,以及异步编程模式(如Promise),前端可以有效地管理文件的上传过程。

然而,为了实现一个完整的断点续传功能,还需要服务器端的配合。服务器需要能够接收分片数据,存储它们,并在客户端请求恢复时提供必要的信息。

最后,实现断点续传时,还需要考虑实际应用中的各种挑战,包括但不限于网络波动、错误处理、上传进度的显示、安全性(如认证和加密)以及性能优化。通过综合这些因素,可以为用户提供一个可靠、高效和用户友好的大文件传输解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/589052.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】:链表的带环问题

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;数据结构 &#x1f337;追光的人&#xff0c;终会万丈光芒 前言&#xff1a; 链表的带环问题在链表中是一类比较难的问题&#xff0c;它对我们的思维有一个比较高的要求&#xff0c;但是这一类…

【模板】前缀和

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 前缀和模板题。 前缀和中数组下标为1~n。 前缀和&#xff1a;pre[i]pre[i-1]a[i]; 某段区间 [l,r]的和&#xff1a;pre[r]-pre[l-1] 3.…

【C语言】atoi和atof函数的使用

人生应该树立目标&#xff0c;否则你的精力会白白浪费。&#x1f493;&#x1f493;&#x1f493; 目录 •&#x1f319;知识回顾 &#x1f34b;知识点一&#xff1a;atoi函数的使用和实现 • &#x1f330;1.函数介绍 • &#x1f330;2.代码演示 • &#x1f330;3.atoi函数的…

【高校科研前沿】云南大学陈峰研究员联合多家单位在Sci. Bull发文揭示了明末特大干旱背景下北京降水变化及其以太平洋海温变化为主导的驱动新机制

文章简介 论文名称&#xff1a;Coupled Pacific Rim megadroughts contributed to the fall of the Ming Dynasty’s capital in 1644 CE&#xff08;环太平洋地区的特大干旱影响了公元 1644 年明朝的灭亡&#xff09; 第一作者及通讯作者&#xff1a;陈峰研究员&王涛研究…

38-4 Web应用防火墙 - WAF的使用及规则

准备:38-3 Web应用防火墙 - 安装配置WAF-CSDN博客 WAF的使用 启动 Nginx /usr/local/nginx/sbin/nginx 为了测试未启动 ModSecurity 时的访问效果,我们可以模拟攻击。要查看当前虚拟机的 IP 地址,可以使用命令 ifconfig 浏览器中访问ip,如果要在真实机中访问就需要关闭…

Linux 学习 --- 编辑 vi 命令

1、vi 基本概念&#xff08;了解&#xff09; 基本上 vi 可以分为三种状态&#xff0c;分别是命令模式 (command mode)、插入模式 (Insert mode) 和底行模式 (last line mode)&#xff0c;各模式的功能区分如下: 命令行模式 command mode&#xff09;  控制屏幕光标的移动&a…

c3 笔记7 css基本语法

相关内容&#xff1a;字体、段落、词间距、文字效果&#xff08;对齐、上下标、阴影&#xff09;、背景图、背景渐变、…… 单位pt与px的差别pt是印刷使用的字号单位&#xff0c;不管屏幕分辨率是多少&#xff0c;打印到纸上看起来都是相同的&#xff0c;lot的长度是0.01384英寸…

[PS小技能学习]抠图和切图

详情见视频教程&#xff1a;PS小技巧--抠图与切图 今天我们来学习如何使用PS对表情包合辑进行抠图和裁剪保存 1、首先&#xff0c;将图片导入&#xff0c;双击图层新建一个图层 2、然后点击工具栏的魔棒工具&#xff0c;再点击顶部菜单栏的添加到选区 3、点击图片的空白区域即…

《QT实用小工具·五十一》带动画的 CheckBox

1、概述 源码放在文章末尾 该项目实现了带动画效果的多选框&#xff0c;鼠标放在上面或者选中都会呈现炫酷的动画效果&#xff0c;demo演示如下&#xff1a; 项目部分代码如下所示&#xff1a; #ifndef LINEARCHECKBOX_H #define LINEARCHECKBOX_H#include <QCheckBox> …

C/C++不定参函数使用

C语言中不定参函数的使用和访问 例子 例如&#xff0c;这里想写一个打印的函数&#xff0c;但是参数并不确定该怎么办呢&#xff0c;这就要用到不定参函数 #include<stdarg.h> void printNum(int count,...){va_list ap;va_start(ap,count);//获取指定参数的起始地址&…

【CTF Reverse】XCTF GFSJ0492 insanity Writeup(反汇编+字符串搜索)

insanity 菜鸡觉得前面的题目太难了&#xff0c;来个简单的缓一下 解法 拖进 Exeinfo PE 中分析。 -> Compiler : GCC: (Debian 4.4.7-2) 4.4.7用 IDA 打开。 按 shift F12 打开 String 页面。找到 flag。 Flag 9447{This_is_a_flag}声明 本博客上发布的所有关于网络攻…

Java创建并遍历N叉树(前序遍历)

力扣 title589&#xff1a;N叉树的前序遍历 给定一个 n 叉树的根节点 root &#xff0c;返回 其节点值的 前序遍历 。 n 叉树 在输入中按层序遍历进行序列化表示&#xff0c;每组子节点由空值 null 分隔&#xff08;请参见示例&#xff09;。 思路&#xff1a; 1.初始化时…

电脑自带dll修复在哪里,使用dll修复工具解决dll问题

在我们日常与电脑相伴的工作与学习过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中最常见的就是“无法找到.dll”或“找不到.dll文件”。这种情况通常是由于dll文件丢失或损坏导致的。dll文件是动态链接库文件&#xff0c;它包含了许多程序运行所需的函数和资源…

Ant Design助力:实现用户列表的优雅展示与管理

文章目录 概要前端讲解登录组件注册组件用户列表组件 后端讲解连接数据库db.js路由routes.jsexpress应用app.js 启动项目小结 概要 在上一篇博客&#x1f6aa;中&#xff0c;我们已经成功实现了登录注册系统的基本功能。现在&#xff0c;我们将进一步完善系统&#xff0c;实现…

File contains parsing errors: file:///etc/yum.repos.d/nginx.repo报错解决,文件配置出现问题

执行yum指令出现以下错误&#xff1a; 解决方案&#xff1a;yum的配置文件出现问题&#xff0c; 先删除yum.repos.d目录下所有文件 rm -f /etc/yum.repos.d/* 然后重新下载阿里的资源 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.…

您想拥有一个属于你自己的GPT-3.5-turbo吗?来吧,开始行动起来吧!!!

背景: 在2024年4月的时候,openai公司宣布GPT-3.5-turbo免费使用,无需注册!!! 多么激动人心的消息啊!!! 但是,如何你想申请一个openai api key的时候,发现调用失败,直接报Rate Limit!!! 无语了!!! 不过没关系,我们另辟捷径!!! 下面就开始我的表演啦…

python可视化学习笔记折线图问题-起始点问题

问题描述&#xff1a; 起始点的位置不对 from pyecharts.charts import Line import pyecharts.options as opts # 示例数据 x_data [1,2,3,4,5] y_data [1, 2, 3, 4, 5] # 创建 Line 图表 line Line() line.add_xaxis(x_data) line.add_yaxis("test", y_data) li…

【全网最全】2024五一数学建模B题论文+前四问代码多种保奖进阶思路+建模过程+代码+数据处理+论文写作技巧等(后续会更新)

一定要点击文末的卡片&#xff0c;那是获取资料的入口&#xff01; 点击链接加入群聊【2024五一数学建模】&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?_wv1027&khoTDlhAS5N_Ffp-vucfG5WjeeJFxsWbz&authKey7oCSHS25VqSLauZ2PpiewRQ9D9PklaCxVS5X6i%2BAkDrey992f0t15…

【hackmyvm】vivifytech靶机

渗透思路 信息收集端口扫描端口服务信息目录扫描爆破hydra--sshgit提权 信息收集 ┌──(kali㉿kali)-[~] └─$ fping -ag 192.168.9.0/24 2>/dev/null 192.168.9.119 --主机 192.168.9.164 --靶机个人习惯&#xff0c;也方便后续操作&#xff0c;将IP地址赋值给一个变…

两院院士泌尿外科专家吴阶平教授

吴阶平&#xff08;1917-2011&#xff09;&#xff0c;男&#xff0c;江苏常州人&#xff0c;1933年天津汇文中学毕业&#xff0c;保送到北平燕京大学医预科&#xff0c;1937年毕业于北平燕京大学获理学士学位&#xff0c;1942年毕业于北平协和医学院获医学博士学位&#xff0c…
最新文章