NET WebAPi之断点续传下载(下)

前言

上一篇我们穿插了C#的内容,本篇我们继续来讲讲webapi中断点续传的其他情况以及利用webclient来实现断点续传,至此关于webapi断点续传下载以及上传内容都已经全部完结,一直嚷嚷着把SQL Server和Oracle数据库再重新过一遍,这篇过完,就要开始新的征程,每一个阶段都应该有自己的小目标,要不然当工作太忙没时间去充电,太闲又变得懒散,想想一切是为了未来买得起孩子高档的奶粉就又有动力了。

话题

关于webapi断点续传下载的情况,之前我们利用webapi内置的api展开了具体的实现,这一节我们利用已经老掉牙的技术来实现,这个是看了一篇老外文章而想到的,具体地址忘记了,利用内存映射文件来实现断点续传,内存映射文件最常见的应用场景莫过于对于多个进程之间共享数据,我们知道进程与进程之间只能操作已经分配好各自的内存,当我们需要一个进程与另外一个进程共享一块数据时我们该如何做呢,这个时候就要用到内存映射文件(MemoryMappedFile),内存映射文件是单一机器多进程间数据通信的最高效的方式,好了关于内存映射文件具体内容可以参考园友【.net 流氓】的文章。我们通过内存映射文件管理虚拟内存然后将其映射到磁盘上具体的文件中,当然我们得知道所谓的文件能够被映射并不是将文件复制到虚拟内存中,而是由于会被应用程序访问到,很显然windows会加载部分物理文件,通过使用内存映射文件我们能够保证操作系统会优化磁盘访问,此外我们能够得到内存缓存的形式。因为文件被映射到虚拟内存中,所以在管理大文件时我们需要在64位模式下运行我们的程序,否则将无法满足我们所需的所有空间。

断点续传(内存映射文件)

关于涉及到的类以及接口在之前文章已经叙述,这里我们就不再啰嗦,这里我们给出下载文件的逻辑。

        /// <summary>
/// 下载文件
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public HttpResponseMessage GetFile(string fileName)
{
if (!FileProvider.Exists(fileName))
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
long fileLength = FileProvider.GetLength(fileName);
var fileInfo = GetFileInfoFromRequest(this.Request, fileLength);
.........
}

我们从请求信息中获取到了文件的信息,接下来我们就是利用内存映射文件的时候

 MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.Read);

自定义一个映射名称,若此时已存在我们则继续读打开的文件,若不存在我们将打开要下载的文件并创建内存映射文件。

mmf = MemoryMappedFile.CreateFromFile(FileProvider.Open(fileName), mapName, fileLength,
MemoryMappedFileAccess.Read, null, HandleInheritability.None,
false);

接着我们创建一个映射文件内存的视图流并返回最终将其写入到响应中的HttpContent中。

mmf.CreateViewStream(0, fileLength, MemoryMappedFileAccess.Read);

response.Content = new StreamContent(stream);

整个利用内存映射文件下载文件的逻辑如下:

        /// <summary>
/// 下载文件
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public HttpResponseMessage GetFile(string fileName)
{
if (!FileProvider.Exists(fileName))
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
long fileLength = FileProvider.GetLength(fileName);
var fileInfo = GetFileInfoFromRequest(this.Request, fileLength);
var mapName = string.Format("FileDownloadMap_{0}", fileName);
MemoryMappedFile mmf = null;
try
{
mmf = MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.Read);
}
catch (FileNotFoundException)
{ mmf = MemoryMappedFile.CreateFromFile(FileProvider.Open(fileName), mapName, fileLength,
MemoryMappedFileAccess.Read, null, HandleInheritability.None,
false);
}
using (mmf)
{
Stream stream
= fileInfo.IsPartial
? mmf.CreateViewStream(fileInfo.From, fileInfo.Length, MemoryMappedFileAccess.Read)
: mmf.CreateViewStream(0, fileLength, MemoryMappedFileAccess.Read); var response = new HttpResponseMessage();
response.Content = new StreamContent(stream);
SetResponseHeaders(response, fileInfo, fileLength, fileName);
return response;
}
}

有时候运行会出现如下错误:

再要么下载过程中出现访问遭到拒绝的情况

若将权限修改为 MemoryMappedFileAccess.ReadWrite ,也会出现访问遭到拒绝的情况,此二者错误的出现暂未找到解决方案,期待读者能给出一点见解或者答案。

断点续传(WebClient)

利用WebClient进行断点续传下载最主要的是对象 HttpWebRequest 中的AddRange方法,类似webapi中的RangeHeaderItemValue对象的from和to显示表明请求从哪里到哪里的数据。其余的就比较简单了,我们获取文件下载路径以及下载目的路径,下载过程中获取目的路径中文件的长度,若路径长度大于0则继续添加,小于0则从0创建文件并下载直到响应头中的文件长度即Content-Length和目的文件长度相等才下载完成。

第一步(打开Url下载)

 client.OpenRead(url);

第二步(若目标文件已存在,比较其余响应头中文件长度,若大于则删除重新下载)

                if (File.Exists(filePath))
{
var finfo = new FileInfo(filePath); if (client.ResponseHeaders != null &&
finfo.Length >= Convert.ToInt64(client.ResponseHeaders["Content-Length"]))
{
File.Delete(filePath);
}
}

第三步(断点续传逻辑)

            long existLen = 0;
FileStream saveFileStream;
if (File.Exists(destinationPath))
{
var fInfo = new FileInfo(destinationPath);
existLen = fInfo.Length;
}
if (existLen > 0)
saveFileStream = new FileStream(destinationPath,
FileMode.Append, FileAccess.Write,
FileShare.ReadWrite);
else
saveFileStream = new FileStream(destinationPath,
FileMode.Create, FileAccess.Write,
FileShare.ReadWrite); var httpWebRequest = (HttpWebRequest)System.Net.HttpWebRequest.Create(sourceUrl);
httpWebRequest.AddRange((int)existLen);
var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var respStream = httpWebResponse.GetResponseStream())
{
var timout = httpWebRequest.Timeout;
respStream.CopyTo(saveFileStream);
}

第四步(判断目标文件长度与响应头中文件,相等则下载完成)

               fileInfo = fileInfo ?? new FileInfo(destinationFilePath);

                if (fileInfo.Length == Convert.ToInt64(client.ResponseHeaders["Content-Length"]))
{
Console.WriteLine("下载完成.......");
}
else
{
throw new WebException("下载中断,请尝试重新下载......");
}

整个利用WebClient下载逻辑如下:

(1)控制台调用,开始下载

            Console.WriteLine("开始下载......");
try
{
DownloadFile("http://localhost:61567/FileLocation/UML.pdf", "d:\\temp\\uml.pdf");
}
catch (Exception ex)
{
if (!string.Equals(ex.Message, "Stack Empty.", StringComparison.InvariantCultureIgnoreCase))
{
Console.WriteLine("{0}{1}{1} 出错啦: {1} {2}", ex.Message, Environment.NewLine,
ex.InnerException.ToString());
}
}

(2)下载文件并判断下载是否完成

        public static void DownloadFile(string url, string filePath)
{
var client = new WebClient();
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) =>
{ return true; }; try
{
client.OpenRead(url); FileInfo fileInfo = null; if (File.Exists(filePath))
{
var finfo = new FileInfo(filePath); if (client.ResponseHeaders != null &&
finfo.Length >= Convert.ToInt64(client.ResponseHeaders["Content-Length"]))
{
File.Delete(filePath);
}
} DownloadFileWithResume(url, filePath); fileInfo = fileInfo ?? new FileInfo(destinationFilePath); if (fileInfo.Length == Convert.ToInt64(client.ResponseHeaders["Content-Length"]))
{
Console.WriteLine("下载完成.......");
}
else
{
throw new WebException("下载中断,请尝试重新下载......");
}
}
catch (Exception ex)
{ Console.WriteLine("Error: {0} {1}", ex.Message, Environment.NewLine);
Console.WriteLine("下载中断,请尝试重新下载......"); throw;
}
}

(3)断点续传逻辑

        /// <summary>
/// 断点续传下载
/// </summary>
/// <param name="sourceUrl"></param>
/// <param name="destinationPath"></param>
private static void DownloadFileWithResume(string sourceUrl, string destinationPath)
{
long existLen = 0;
FileStream saveFileStream;
if (File.Exists(destinationPath))
{
var fInfo = new FileInfo(destinationPath);
existLen = fInfo.Length;
}
if (existLen > 0)
saveFileStream = new FileStream(destinationPath,
FileMode.Append, FileAccess.Write,
FileShare.ReadWrite);
else
saveFileStream = new FileStream(destinationPath,
FileMode.Create, FileAccess.Write,
FileShare.ReadWrite); var httpWebRequest = (HttpWebRequest)System.Net.HttpWebRequest.Create(sourceUrl);
httpWebRequest.AddRange((int)existLen);
var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var respStream = httpWebResponse.GetResponseStream())
{
var timout = httpWebRequest.Timeout;
respStream.CopyTo(saveFileStream);
}
}

总结

至此在webapi中利用内存映射文件下载以及在控制台中利用WebClient下载叙述基本已经完结,其中或多或少还是存在一点问题,后续有时间再来看看,对于上述出现的问题,有解决方案的读者可以提供一下。接下来我将开始新的征程,开始SQL Server和Oracle数据库学习之旅。

更新

所有代码已经上传到右上角github,有需要请下载,或者直接点击如下地址clone:WebAPiResumeDownload

NET WebAPi之断点续传下载(下)的更多相关文章

  1. NET WebAPi之断点续传下载1

    ASP.NET WebAPi之断点续传下载(上)   前言 之前一直感觉断点续传比较神秘,于是想去一探究竟,不知从何入手,以为就写写逻辑就行,结果搜索一番,还得了解相关http协议知识,又花了许久功夫 ...

  2. ASP.NET WebAPi之断点续传下载(下)

    前言 上一篇我们穿插了C#的内容,本篇我们继续来讲讲webapi中断点续传的其他情况以及利用webclient来实现断点续传,至此关于webapi断点续传下载以及上传内容都已经全部完结,一直嚷嚷着把S ...

  3. ASP.NET WebAPi之断点续传下载(中)

    前言 前情回顾:上一篇我们遗留了两个问题,一个是未完全实现断点续传,另外则是在响应时是返回StreamContent还是PushStreamContent呢?这一节我们重点来解决这两个问题,同时就在此 ...

  4. ASP.NET WebAPi之断点续传下载(上)

    前言 之前一直感觉断点续传比较神秘,于是想去一探究竟,不知从何入手,以为就写写逻辑就行,结果搜索一番,还得了解相关http协议知识,又花了许久功夫去看http协议中有关断点续传知识,有时候发觉东西只有 ...

  5. iOS开发之网络编程--4、NSURLSessionDataTask实现文件下载(离线断点续传下载) <进度值显示优化>

    前言:根据前篇<iOS开发之网络编程--2.NSURLSessionDownloadTask文件下载>或者<iOS开发之网络编程--3.NSURLSessionDataTask实现文 ...

  6. Android实现网络多线程断点续传下载(转)

    本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...

  7. android 多线程断点续传下载

    今天跟大家一起分享下Android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基 ...

  8. Android开发多线程断点续传下载器

    使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线.电量不足等情况下,这就需要使用到断点 ...

  9. Android之断点续传下载

    今天学习了Android开发中比较难的一个环节,就是断点续传下载,很多人看到这个标题就感觉头大,的确,如果没有良好的逻辑思维,这块的确很难搞明白.下面我就将自己学到的知识和一些见解写下供那些在这个环节 ...

随机推荐

  1. Optimal Milking POJ - 2112 (多重最优匹配+最小费用最大流+最大值最小化 + Floyd)

      Optimal Milking Time Limit: 2000MS   Memory Limit: 30000K Total Submissions: 19347   Accepted: 690 ...

  2. Spring MVC 异步测试

    从spring3.2开始,支持servlet3的异步请求,这对于处理耗时的请求如缓慢的数据库查询是非常有好处的,不至于很快的耗光servlet的线程池,影响可扩展性. 让我们先来了解一下servlet ...

  3. JXOI2017颜色 解题报告

    JXOI2017颜色 首先记录每个位置上颜色在序列中上次出现的位置 开两颗线段树,第一棵维护区间最大值,实际上是维护当前必须被删去的颜色的位置的最大值,第二棵则是维护区间和 首先倒着扫一遍,对于当前颜 ...

  4. 【洛谷P2504】聪明的猴子 最小瓶颈树

    题目大意:给定一张 N 个顶点的完全图,边有边权,求该完全图的一棵最小瓶颈树. 最小瓶颈树:一棵最大边权值在同一张图的所有生成树中最小,即:最大边权值最小的生成树,其值为该树的最大边权的权值. 引理1 ...

  5. 【java】JDK、JRE、JVM的关系

    目录结构: contents structure [-] 什么是JDK,JRE,JVM 安装JDK的时候为什么有两个jre 专用JRE 共用JRE 共用JRE和专用JRE的对比 java的跨平台特性 ...

  6. 浅谈使用NIO,AIO的感受

    花了十多天的时间把原来的WEB服务由BIO(阻塞IO)模式改写成NIO(非阻塞IO)模式,然后在xp机子上用ab测试并发性能,确实提升了30%左右的并发性能,测试完成后,当时感觉还是挺满意的.几天前在 ...

  7. linux中awk工具的使用(转载)

    awk是一个非常好用的数据处理工具.相较于sed常常一整行处理,awk则比较倾向于一行当中分成数个“字段”处理,awk处理方式如下: $ awk '条件类型1{动作1} 条件类型2{动作2} ...' ...

  8. harbor高可用集群配置

    目录 说明 双主复制 主从同步 双主复制说明 多harbor实例共享后端存储 方案说明 环境说明 配置说明 安装redis和mysql 导入registry数据库 配置harbor 挂载nfs目录 修 ...

  9. vue父组件调用子组件资源

    通过上篇博文提到的方法我们可以触发子组件的某个事件来实现调用子组件的某些资源(例如数据和方法),但是更多的情况下我们会想不通过触发子组件的事件,而直接调用子组件的资源 这个时候我们就需要用到ref了, ...

  10. 鸟哥的Linux私房菜——第八章

    参考鸟哥的Linux私房菜内容,我只是简单的记录比较重要的知识点,所以排版没怎么注意,如果写的太严肃小白也看不进去吧. 看视频!这篇文章只作为备忘录 视频链接:http://www.tudou.com ...