NET WebAPi之断点续传下载(下)
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之断点续传下载(下)的更多相关文章
- NET WebAPi之断点续传下载1
ASP.NET WebAPi之断点续传下载(上) 前言 之前一直感觉断点续传比较神秘,于是想去一探究竟,不知从何入手,以为就写写逻辑就行,结果搜索一番,还得了解相关http协议知识,又花了许久功夫 ...
- ASP.NET WebAPi之断点续传下载(下)
前言 上一篇我们穿插了C#的内容,本篇我们继续来讲讲webapi中断点续传的其他情况以及利用webclient来实现断点续传,至此关于webapi断点续传下载以及上传内容都已经全部完结,一直嚷嚷着把S ...
- ASP.NET WebAPi之断点续传下载(中)
前言 前情回顾:上一篇我们遗留了两个问题,一个是未完全实现断点续传,另外则是在响应时是返回StreamContent还是PushStreamContent呢?这一节我们重点来解决这两个问题,同时就在此 ...
- ASP.NET WebAPi之断点续传下载(上)
前言 之前一直感觉断点续传比较神秘,于是想去一探究竟,不知从何入手,以为就写写逻辑就行,结果搜索一番,还得了解相关http协议知识,又花了许久功夫去看http协议中有关断点续传知识,有时候发觉东西只有 ...
- iOS开发之网络编程--4、NSURLSessionDataTask实现文件下载(离线断点续传下载) <进度值显示优化>
前言:根据前篇<iOS开发之网络编程--2.NSURLSessionDownloadTask文件下载>或者<iOS开发之网络编程--3.NSURLSessionDataTask实现文 ...
- Android实现网络多线程断点续传下载(转)
本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...
- android 多线程断点续传下载
今天跟大家一起分享下Android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基 ...
- Android开发多线程断点续传下载器
使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线.电量不足等情况下,这就需要使用到断点 ...
- Android之断点续传下载
今天学习了Android开发中比较难的一个环节,就是断点续传下载,很多人看到这个标题就感觉头大,的确,如果没有良好的逻辑思维,这块的确很难搞明白.下面我就将自己学到的知识和一些见解写下供那些在这个环节 ...
随机推荐
- bzoj 4034: [HAOI2015]树上操作 (树剖+线段树 子树操作)
4034: [HAOI2015]树上操作 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 6779 Solved: 2275[Submit][Stat ...
- Java移动文件到另外一个目录
private void moveTotherFolders(String pathName,String fileName,String ansPath){ String startPath = t ...
- 洛谷P3703 [SDOI2017]树点涂色(LCT,dfn序,线段树,倍增LCA)
洛谷题目传送门 闲话 这是所有LCT题目中的一个异类. 之所以认为是LCT题目,是因为本题思路的瓶颈就在于如何去维护同颜色的点的集合. 只不过做着做着,感觉后来的思路(dfn序,线段树,LCA)似乎要 ...
- 沉迷AC自动机无法自拔之:[UVALive 4126] Password Suspects
图片加载可能有点慢,请跳过题面先看题解,谢谢 一看到这么多模式串就非常兴奋,又是\(AC\)自动机 题目就是要求:经过 \(n\) 个节点,把所有单词都遍历一遍的方案数,和那道题差不多嘛 所以这样设: ...
- Link Cut Tree 总结
Link-Cut-Tree Tags:数据结构 ##更好阅读体验:https://www.zybuluo.com/xzyxzy/note/1027479 一.概述 \(LCT\),动态树的一种,又可以 ...
- svn问题汇总
1 svn图标 2 问题 SVN删除文件 一.本地删除SVN删除文件中的本地删除,指的是在客户端delete了一个文件,但还没有commit,使用revert来撤销删除. 二.服务器删除1.通过本地删 ...
- asp.net中SQL语句太长,怎么换行写?
http://bbs.csdn.net/topics/390639485?page=1 string strfac="insert into CarInfo values('"+T ...
- linux shell重定向
http://note.youdao.com/noteshare?id=e944e6315d1566b3417e6f59305ddedc
- zabbix server安装(二)
https://mp.weixin.qq.com/s/ogaqiX4vhtGLepuNf-1ItA zabbix依赖LNMP或LAMP,下面讲解LNMP安装到zabbix web页面的访问. 一.ng ...
- ELK logstash 处理MySQL慢查询日志
在生产环境下,logstash 经常会遇到处理多种格式的日志,不同的日志格式,解析方法不同.下面来说说logstash处理多行日志的例子,对MySQL慢查询日志进行分析,这个经常遇到过,网络上疑问也很 ...