Asp.Net Core 实现分片下载的最简单方式
技术群里的朋友遇到了这个问题,起初的原因是他对文件增加了一个属性配置
fileResult.EnableRangeProcessing = true;
这个属性我从未遇到过,然后,去F1查看这个属性的描述信息也依然少的可怜,只有简单的描述为(获取或设置为 启用范围处理 FileResult的值)。
范围处理,很容易理解,可能就是实现分片下载的关键,但是,是不是就很简单配置就可以了呢,我对此十分感兴趣,就开始查询它的实际信息。
EnableRangeProcessing 属性的含义
经查看Asp.net Core 源码可以看到,它实际上只是配置了一个头信息。
具体的方法内部,只是一个赋值
赋值的是什么呢,其实就是一个HTTP 头标记
所以,实际上,它只做了一件事情,那就是把请求的头变成以下的样子。
是的,它只是做了这一件事情,那就是把请求头 Accept-Ranges 的值设置为了 bytes 。
HTTP 协议头之 Accept-Ranges
经查寻,此头就是表示着 (Accept-Ranges HTTP 响应标头是服务器使用的一个标记,用于向客户端宣传其对文件下载的部分请求的支持。此字段的值表示可用于定义范围的单位。 )
当存在 Accept-Ranges 标头时,浏览器可能会尝试恢复中断的下载,而不是尝试重新启动下载。
所以,设置它就相当于设置了Asp.net Core 支持分片下载。
Asp.net Core 分片下载实际案例
主要分为服务端和客户端,服务端设置允许分片下载,客户端则需要按照分片进行多线程下载,我这里实际通过并行下载实现。
最后的验证,能运行它或者打开它即可(比如zip格式等)。
服务端代码
服务端异常的简单,新建一个Asp.net Core 项目即可,其他都默认。
我们要做的事情只是在 HomeController 里增加以下代码
private FileExtensionContentTypeProvider provider = new FileExtensionContentTypeProvider();
public IActionResult Down()
{var file = @"H:\百度网盘\ubuntu.zip";provider.TryGetContentType(file, out var contentType);var result = PhysicalFile(file, contentType);result.EnableRangeProcessing = true;result.FileDownloadName = Path.GetFileName(file);return result;
}
里面有几个点
- 设置文件的类型可以用 FileExtensionContentTypeProvider 这个类来的,不用自己写(特殊类型它不支持)
- 设置EnableRangeProcessing 为 true ,才能实现分片下载
- 设置FileDownloadName为具体的下载文件名,才可以在客户端知道你下载的文件名的名字,很重要。
客户端代码
客户端稍微复杂一些,得获取文件的大小和名字,然后,进行多线程下载,我这边会进行一个简单的模拟。
直接下载文件的逻辑
public static async Task DownUrl(string url)
{var result = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, url), HttpCompletionOption.ResponseHeadersRead);if (result.IsSuccessStatusCode){var filename = "temp.zip";using (var contentStream = await result.Content.ReadAsStreamAsync()){using (var file = File.OpenWrite(filename)){await contentStream.CopyToAsync(file);}}}
}
当然,如果不想分片下载,可以直接下载即可。
获取文件大小以及名字
public static async Task<(long? length, string filename)> GetFileLengthandName(string url)
{var result = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, url), HttpCompletionOption.ResponseHeadersRead);if (result.IsSuccessStatusCode){return (result.Content.Headers.ContentLength, result.Content.Headers.ContentDisposition.FileName);}return (null, null);
}
主要就是通过httpclient 自带的头信息,直接获取即可。挺简单的。
分片下载核心
首先是对获取到的文件大小进行一个范围的分割
public struct BytesRange
{public long Start { get; set; }public long End { get; set; }public long Length { get { return End - Start + 1; } }public override string ToString(){return $"{Start} {End} : {Length}";}public static List<BytesRange> GetRanges(long length, long BufferSize = 1 * 1024 * 1024){List<BytesRange> list = new List<BytesRange>();long count = length / BufferSize;long Lost = length - BufferSize * count;if (Lost > 0){list.Add(new BytesRange() { Start = count * BufferSize, End = count * BufferSize + Lost - 1 });}if (count > 0){for (long i = 0; i < count; i++){list.Add(new BytesRange() { Start = i * BufferSize, End = (i + 1) * BufferSize - 1 });}}else{list.Add(new BytesRange() { Start = 0, End = Lost - 1 });}list.OrderByDescending(t => t.Start);return list;}
}
这样就可以获取具体的分片信息,具体每片的大小。
public static async Task<byte[]> GetBytesAsync(string url, BytesRange range)
{var request = new HttpRequestMessage(HttpMethod.Get, url);request.Headers.Add("Range", $"bytes={range.Start}-{range.End}");using (HttpResponseMessage response = await client.SendAsync(request)){using (Stream stream = await response.Content.ReadAsStreamAsync()){if (range.Length != stream.Length){throw new Exception("数据不匹配!");}byte[] bytes = new byte[stream.Length];stream.Read(bytes, 0, bytes.Length);return bytes;}}
}
GetBytesAsync 就是按照指定的大小分为进行请求,并返回所需的文件大小。
实际代码
static HttpClient client = new HttpClient();
static object lockObj = new object();
static async Task Main(string[] args)
{var url = "http://localhost:5034/home/down";Stopwatch stopwatch = Stopwatch.StartNew();await DownUrl(url);stopwatch.Stop();Console.WriteLine($"单线程 直接下载耗时:{stopwatch.Elapsed.TotalSeconds}");stopwatch.Restart();(long? length, string filename) = await GetFileLengthandName(url);if (length.HasValue){var number = 10;//获取分片大小,默认1M 缓存区,太小又太慢 设置成5M。var list = BytesRange.GetRanges(length.Value, 5 * 1024 * 1024);Console.WriteLine($"分片数:{list.Count} 每片大小:5MB 并发数:{number}");var path = Path.Combine(AppContext.BaseDirectory, filename);using (var write = File.OpenWrite(path)){write.SetLength(length.Value);await write.FlushAsync();// 并行下载,每秒默认10并发Parallel.ForEach(list, new ParallelOptions() { MaxDegreeOfParallelism = number }, range =>{//Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {range}");var bytes = GetBytesAsync(url, range).Result;lock (lockObj){write.Seek(range.Start, SeekOrigin.Begin);write.Write(bytes);}});}Console.WriteLine("下载完毕,请验证!");}else{Console.WriteLine("没有获取到下载文件的信息!");}stopwatch.Stop();Console.WriteLine($"并发下载 耗时:{stopwatch.Elapsed.TotalSeconds}秒");Console.ReadLine();
}
验证结果
先运行服务端
再运行下载客户端
看到结果后,有点差异
从结果上来看,直接下载是最快的,应该是少了分片的开销,而且服务都是在本机上,各种IO的限制基本上只有文件IO,带宽IO影响最小。
总结
虽然直接下载是最快的,但是,如果网络中断的话,基本得重新下载,所以,它的风险反而是最高的,而分片下载虽然有了分片的开销的,但是可以从断点处继续下载,风险反而最低,各有优势。
代码地址
https://github.com/kesshei/WebDown.git
https://gitee.com/kesshei/WebDown.git
参考资料地址
《enableRangeProcessing 的代码地址》
https://github.com/dotnet/aspnetcore/blob/53db4d97d7c77d13e20e58a98f104e88d6af6040/src/Shared/ResultsHelpers/FileResultHelper.cs#L141
《Accept-Ranges 》
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Ranges
阅
一键三连呦!,感谢大佬的支持,您的支持就是我的动力!
相关文章:

Asp.Net Core 实现分片下载的最简单方式
技术群里的朋友遇到了这个问题,起初的原因是他对文件增加了一个属性配置 fileResult.EnableRangeProcessing true;这个属性我从未遇到过,然后,去F1查看这个属性的描述信息也依然少的可怜,只有简单的描述为(获取或设置为 启用范围…...
[Mac软件]Leech for Mac v3.2 - 轻量级mac下载工具
黑果魏叔推荐Leech是由Many Tricks开发的适用于Mac OS X的轻量级且功能强大的下载管理器。 Leech让您完全控制下载,并与浏览器完全集成。您可以将下载排队,暂停和恢复,从受密码保护的服务器下载,并将密码存储在系统范围的安全钥匙…...

留给“端侧大模型”的时间不多了
端侧大模型(Edge AI models),也就是只在设备本地(如智能手机、IoT设备、嵌入式系统等)运行的大模型,过去一两年来非常流行。 具体表现在,终端设备厂商,如苹果、荣耀、小米、OV等&…...

Pytest框架中的Setup和Teardown功能
在 pytest 测试框架中,setup 和 teardown是用于在每个测试函数之前和之后执行设置和清理的动作,而pytest 实际上并没有内置的 setup 和 teardown 函数,而是使用了一些装饰器或钩子函数来实现类似的功能。 学习目录 钩子函数(Hook…...

yolov10/v8 loss详解
v10出了就想看看它的loss设计有什么不同,看下来由于v8和v10的loss部分基本一致就放一起了。 v10的论文笔记,还没看的可以看看,初步尝试耗时确实有提升 好记性不如烂笔头,还是得记录一下,以免忘了,废话结束…...
Typescript高级: 深入理解infer关键字
概述 在 TS 中,infer 是一个高级类型操作,特别是条件类型和映射类型中非常有用的关键字它在泛型中使用也会是一个强大工具,增强了类型推断的能力,让开发者更灵活地处理和操作类型它允许在泛型类型推导过程中捕获一个具体的类型&a…...

JQC-3FF-S-Z 继电器模块使用(arduino)
前言 继电器模块可以控制电流的接通和非接通状态,和开关一样。实际上是用小电流去控制大电流运作的一种“自动开关” 本文只是简单使用继电器模块做一个 led 点亮和熄灭的案例,结合案例可以和 nodemcu 等板子结合做出远程控制开关。 材料准备 杜邦线…...

黑马一站制造数仓实战2
问题 DG连接问题 原理:JDBC:用Java代码连接数据库 Hive/SparkSQL:端口有区别 可以为同一个端口,只要不在同一台机器 项目:一台机器 HiveServer:10000 hiveserver.port 10000 SparkSQL:10001…...

网络I/O模型
网络I/O模型 同步I/O阻塞I/O非阻塞I/OI/O多路复用select函数接口示例 poll函数接口示例 poll 和 select 的区别epoll原理:示例 异步I/O 同步I/O 阻塞I/O 一个基本的C/S模型如下图所图:其中 listen()、connect()、write()、read() 都是阻塞I/O࿰…...

Docker 简介和安装
目录 Docker 是什么 跟普通虚拟机的对比 打包、分发、部署 Docker 部署的优势 Docker 通常用来做什么 重要概念:镜像、容器 安装 镜像加速源 Docker 是什么 Docker 是一个应用打包、分发、部署的工具 你也可以把它理解为一个轻量的虚拟机,它只虚…...

【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(二)
Spring Data JPA系列 1、SpringBoot集成JPA及基本使用 2、Spring Data JPA Criteria查询、部分字段查询 3、Spring Data JPA数据批量插入、批量更新真的用对了吗 4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作 5、Spring Data JPA自定…...
Vue前端中从后端获取图片验证码
前端发送请求 <template><el-form :model"user" :rules"rules" ref"userForm" class"login" label-width"auto" style"max-width: 600px"><el-form-item label"用户名" prop"name…...

【源码】多语言H5聊天室/thinkphp多国语言即时通讯/H5聊天室源码/在线聊天/全开源
多语言聊天室系统,可当即时通讯用,系统默认无需注册即可进入群聊天,全开源 【海外聊天室】多语言H5聊天室/thinkphp多国语言即时通讯/H5聊天室源码/在线聊天/全开源 - 吾爱资源网...

gitlab 创建 ssh 和 token
文章目录 一、创建ssh key二、将密钥内容复制到gitlab三、创建token 一、创建ssh key 打开控制台cmd,执行命令 ssh-keygen -t rsa -C xxxxx xxxxx是你自己的邮箱 C:\Users\xx\.ssh 目录下会创建一个名为id_rsa.pub的文件,用记事本打开,并…...
Docker - Kafka
博文目录 文章目录 说明命令 说明 Docker Hub - bitnami/kafka Docker Hub - apache/kafka Kafka QuickStart Kafka 目前没有 Docker 官方镜像, 目前拉取次数最多的是 bitnami/kafka, Apache 提供的是 apache/kafka (更新最及时), 本文使用 bitnami/kafka bitnami/kafka 镜像…...

一键实现文件夹批量高效重命名:轻松运用随机一个字母命名,让文件管理焕然一新!
在数字化时代,文件夹管理是我们日常生活和工作中不可或缺的一部分。然而,随着文件数量的不断增加,文件夹命名的繁琐和重复成为了一个让人头疼的问题。你是否曾因为手动一个个重命名文件夹而感到枯燥乏味?你是否曾渴望有一种方法能…...

Vue3项目练习详细步骤(第二部分:主页面搭建)
主页面搭建 页面主体结构 路由 子路由 主页面搭建 页面主体结构 在vuews目录下新建Layout.vue文件 主页面内容主体代码 <script setup> import {Management,Promotion,UserFilled,User,Crop,EditPen,SwitchButton,CaretBottom } from element-plus/icons-vue imp…...

[个人总结]-java常用方法
1.获取项目根路径 user.dir是一个系统属性,表示用户当前的工作目录,大多数情况下,用户的当前工作目录就是java项目的根目录(src文件的同级路径) System.getProperty("user.dir") 结果:D:\code…...
什么是Java泛型?它有什么作用
Java泛型(Generics)是一种允许在定义类、接口和方法时使用类型参数的机制。泛型提供了一种机制,使得代码可以对多种类型的对象进行操作,而无需进行类型转换。 Java泛型的作用 类型安全:通过在编译时进行类型检查&…...
[机缘参悟-197] - 《道家-水木然人间清醒1》读书笔记 -21-看问题从现象到本质的层次
目录 1. 现象层: 2. 关联层: 3. 原因层: 4. 本质层: 5. 解决方案层: 6. 设计实现层: 7. 泛化: 8. 创新与发现: 看问题从现象到本质的层次是一个逐步深入、由表及里的过程。这…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...