html
然后来编写 html ,这没有什么好说的,写两个按钮来展示。
串行下载
多线程下载
js公共参数
const m = 1024 * 520; // 分片的大小
const url = ‘http://localhost:8888/api/rangeFile?filename=360_0388.jpg’; // 要下载的地址
单线程部分
单线程下载代码,直接去请求以blob
方式获取,然后用blobURL
的方式下载。
download1.onclick = () => {
console.time(“直接下载”);
function download(url) {
const req = new XMLHttpRequest();
req.open(“GET”, url, true);
req.responseType = “blob”;
req.onload = function (oEvent) {
const content = req.response;
const aTag = document.createElement(‘a’);
aTag.download = ‘360_0388.jpg’;
const blob = new Blob([content])
const blobUrl = URL.createObjectURL(blob);
aTag.href = blobUrl;
aTag.click();
URL.revokeObjectURL(blob);
console.timeEnd(“直接下载”);
};
req.send();
}
download(url);
}
多线程部分
首先发送一个 head 请求,来获取文件的大小,然后根据 length 以及设置的分片大小,来计算每个分片是滑动距离。通过Promise.all
的回调中,用concatenate
函数对分片 buffer 进行一个合并成一个 blob,然后用blobURL
的方式下载。
// script
function downloadRange(url, start, end, i) {
return new Promise((resolve, reject) => {
const req = new XMLHttpRequest();
req.open(“GET”, url, true);
req.setRequestHeader(‘range’, bytes=${start}-${end}
)
req.responseType = “blob”;
req.onload = function (oEvent) {
req.response.arrayBuffer().then(res => {
resolve({
i,
buffer: res
});
})
};
req.send();
})
}
// 合并buffer
function concatenate(resultConstructor, arrays) {
let totalLength = 0;
for (let arr of arrays) {
totalLength += arr.length;
}
let result = new resultConstructor(totalLength);
let offset = 0;
for (let arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result;
}
download2.onclick = () => {
axios({
url,
method: ‘head’,
}).then((res) => {
// 获取长度来进行分割块
console.time(“并发下载”);
const size = Number(res.headers[‘content-length’]);
const length = parseInt(size / m);
const arr = []
for (let i = 0; i < length; i++) {
let start = i * m;
let end = (i == length - 1) ? size - 1 : (i + 1) * m - 1;
arr.push(downloadRange(url, start, end, i))
}
Promise.all(arr).then(res => {
const arrBufferList = res.sort(item => item.i - item.i).map(item => new Uint8Array(item.buffer));
const allBuffer = concatenate(Uint8Array, arrBufferList);
const blob = new Blob([allBuffer], {type: ‘image/jpeg’});
const blobUrl = URL.createObjectURL(blob);
const aTag = document.createElement(‘a’);
aTag.download = ‘360_0388.jpg’;
aTag.href = blobUrl;
aTag.click();
URL.revokeObjectURL(blob);
console.timeEnd(“并发下载”);
})
})
}
完整示例
https://github.com/hua1995116/node-demo
// 进入目录
cd file-download
// 启动
node server.js
// 打开
http://localhost:8888/example/download-multiple/index.html
由于谷歌浏览器在 HTTP/1.1 对于单个域名有所限制,单个域名最大的并发量是 6.
这一点可以在源码以及官方人员的讨论中体现。
讨论地址
https://bugs.chromium.org/p/chromium/issues/detail?id=12066
Chromium 源码
// https://source.chromium.org/chromium/chromium/src/+/refs/tags/87.0.4268.1:net/socket/client_socket_pool_manager.cc;l=47
// Default to allow up to 6 connections per host. Experiment and tuning may
// try other values (greater than 0). Too large may cause many problems, such
// as home routers blocking the connections!?!? See http://crbug.com/12066.
//
// WebSocket connections are long-lived, and should be treated differently
// than normal other connections. Use a limit of 255, so the limit for wss will
// be the same as the limit for ws. Also note that Firefox uses a limit of 200.
// See http://crbug.com/486800
int g_max_sockets_per_group[] = {
6, // NORMAL_SOCKET_POOL
255 // WEBSOCKET_SOCKET_POOL
};
因此为了配合这个特性我将文件分成6个片段,每个片段为520kb
(没错,写个代码都要搞个爱你的数字),即开启6个线程进行下载。
我用单个线程和多个线程进行分别下载了6次,看上去速度是差不多的。那么为什么和我们预期的不一样呢?
image-20200919165242745
探索失败的原因
我开始仔细对比两个请求,观察这两个请求的速度。
6个线程并发
image-20200919170313455
单个线程
image-20200919170512650
我们按照3.7M 82ms 的速度来算的话,大约为 1ms 下载 46kb,而实际情况可以看到,533kb ,平均就要下载 20ms 左右(已经刨去了连接时间,纯 content 下载时间)。
我就去查找了一些资料,明白了有个叫做下行速度和上行速度的东西。
网络的实际传输速度要分上行速度和下行速度,上行速率就是发送出去数据的速度,下行就是收到数据的速度。ADSL是根据我们平时上网,发出数据的要求相对下载数据的较小这种习惯来实现的一种传输方式。我们说对于4M的宽带,那么我们的l理论最高下载速度就是512K/S,这就是所说的下行速度。 --百度百科
那我们现在的情况是怎么样的呢?
把服务器比作一根大水管,我来用图模拟一下我们单个线程和多个线程下载的情况。左侧为服务器端,右侧为客户端。(以下所有情况都是考虑理想情况下,只是为了模拟过程,不考虑其他一些程序的竞态影响。)
单线程
IMG_01
多线程
IMG_02
没错,由于我们的服务器是一根大水管,流速是一定的,并且我们客户端没有限制。如果是单线程跑的话,那么会跑满用户的最大的速度。如果是多线程呢,以3个线程为例子的话,相当于每个线程都跑了原先线程三分之一的速度。合起来的速度和单个线程是没有差别的。
下面我就分几种情况来讲解一下,什么样的情况才我们的多线程才会生效呢?
服务器带宽大于用户带宽,不做任何限制
这种情况其实我们遇到的情况差不多的。
服务器带宽远大于用户带宽,限制单连接网速
IMG_03
如果服务器限制了单个宽带的下载速度,大部分也是这种情况,例如百度云就是这样,例如明明你是 10M 的宽带,但是实际下载速度只有 100kb/s ,这种情况下,我们就可以开启多线程去下载,因为它往往限制的是单个TCP的下载,当然在线上环境不是说可以让用户开启无限多个线程,还是会有限制的,会限制你当前IP的最大TCP。这种情况下下载的上限往往是你的用户最大速度。按照上面的例子,如果你开10个线程已经达到了最大速度,因为再大,你的入口已经被限制死了,那么各个线程之间就会抢占速度,再多开线程也没有用了。
改进方案
由于 Node 我暂时没有找到比较简单地控制下载速度的方法,因此我就引入了 Nginx。
我们将每个TCP连接的速度控制在 1M/s。
加入配置 limit_rate 1M;
准备工作
1.nginx_conf
server {
listen 80;
server_name limit.qiufeng.com;
access_log /opt/logs/wwwlogs/limitqiufeng.access.log;
error_log /opt/logs/wwwlogs/limitqiufeng.error.log;
add_header Cache-Control max-age=60;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods ‘GET, OPTIONS’;
add_header Access-Control-Allow-Headers ‘DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,range,If-Range’;
if ($request_method = ‘OPTIONS’) {
return 204;
}
limit_rate 1M;
location / {
root 你的静态目录;
index index.html;
}
}
2.配置本地 host
127.0.0.1 limit.qiufeng.com
查看效果,这下基本上速度已经是正常了,多线程下载比单线程快了速度。基本是 5-6 : 1 的速度,但是发现如果下载过程中快速点击数次后,使用Range
下载会越来越快(此处怀疑是 Nginx 做了什么缓存,暂时没有深入研究)。
修改代码中的下载地址
const url = ‘http://localhost:8888/api/rangeFile?filename=360_0388.jpg’;
变成
const url = ‘http://limit.qiufeng.com/360_0388.jpg’;
测试下载速度
image-20200919201613507
还记得上面说的吗,关于 HTTP/1.1
同一站点只能并发 6 个请求,多余的请求会放到下一个批次。但是 HTTP/2.0
不受这个限制,多路复用代替了 HTTP/1.x
的序列和阻塞机制。让我们来升级 HTTP/2.0
来测试一下。
需要本地生成一个证书。(生成证书方法: https://juejin.im/post/6844903556722475021)
server {
listen 443 ssl http2;
ssl on;
ssl_certificate /usr/local/openresty/nginx/conf/ssl/server.crt;
ssl_certificate_key /usr/local/openresty/nginx/conf/ssl/server.key;
ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1440m;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers RC4:HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
server_name limit.qiufeng.com;
access_log /opt/logs/wwwlogs/limitqiufeng2.access.log;
error_log /opt/logs/wwwlogs/limitqiufeng2.error.log;
add_header Cache-Control max-age=60;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods ‘GET, OPTIONS’;
add_header Access-Control-Allow-Headers ‘DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,range,If-Range’;
if ($request_method = ‘OPTIONS’) {
return 204;
}
limit_rate 1M;
location / {
root 你存放项目的前缀路径/node-demo/file-download/;
index index.html;
}
}
10个线程
将单个下载大小进行修改
const m = 1024 * 400;
image-20200919200203877
12个线程
image-20200919202302096
24个线程
image-20200919202138838
当然线程不是越多越好,经过测试,发现线程达到一定数量的时候,反而速度会更加缓慢。以下是 36个并发请求的效果图。
image-20200919202427985
实际应用探索
那么多进程下载到底有啥用呢?没错,开头也说了,这个分片机制是迅雷等下载软件的核心机制。
网易云课堂
https://study.163.com/course/courseLearn.htm?courseId=1004500008#/learn/video?lessonId=1048954063&courseId=1004500008
我们打开控制台,很容易地发现这个下载 url,直接一个裸奔的 mp4 下载地址。
image-20200920222053726
把我们的测试脚本从控制台输入进行。
// 测试脚本,由于太长了,而且如果仔细看了上面的文章也应该能写出代码。实在写不出可以看以下代码。
https://github.com/hua1995116/node-demo/blob/master/file-download/example/download-multiple/script.js
直接下载
image-20200920221657541
多线程下载
image-20200920221853959
可以看到由于网易云课堂对单个TCP的下载速度并没有什么限制没有那么严格,提升的速度不是那么明显。
百度云
我们就来测试一下网页版的百度云。
image-20200919210106839
以一个 16.6M的文件为例。
打开网页版百度云盘的界面,点击下载
image-20200920222309345
这个时候点击暂停, 打开 chrome -> 更多 -> 下载内容 -> 右键复制下载链接
image-20200922004619680
依旧用上述的网易云课程下载课程的脚本。只不过你需要改一下参数。
url 改成对应百度云下载链接
m 改成 1024 * 1024 * 2 合适的分片大小~
直接下载
百度云多单个TCP连接的限速,真的是惨无人道,足足花了217秒!!!就一个17M的文件,平时我们饱受了它多少的折磨。(除了VIP玩家)
image-20200919211105023
多线程下载
image-20200919210516632
由于是HTTP/1.1 因此我们只要开启6个以及以上的线程下载就好了。以下是多线程下载的速度,约用时 46 秒。
image-20200919210550840
我们通过这个图再来切身感受一下速度差异。
image-20200922010911389
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
![](https://i-blog.csdnimg.cn/blog_migrate/7b92fc971a90620dd392d9cfa72a702c.jpeg)
最后
资料过多,篇幅有限,需要文中全部资料可以点击这里免费获取前端面试资料PDF完整版!
自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。
oss-process=image/format,png)
image-20200922010911389
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-uHylwWFL-1712309371957)]
[外链图片转存中…(img-SYuZUsSS-1712309371958)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-ELtmleSv-1712309371958)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
![](https://i-blog.csdnimg.cn/blog_migrate/7b92fc971a90620dd392d9cfa72a702c.jpeg)
最后
[外链图片转存中…(img-K0Jo3dtD-1712309371958)]
[外链图片转存中…(img-7aThfPUX-1712309371959)]
资料过多,篇幅有限,需要文中全部资料可以点击这里免费获取前端面试资料PDF完整版!
自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。