前言

上一篇我们穿插了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(, 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(, 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 = ;
FileStream saveFileStream;
if (File.Exists(destinationPath))
{
var fInfo = new FileInfo(destinationPath);
existLen = fInfo.Length;
}
if (existLen > )
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 = ;
FileStream saveFileStream;
if (File.Exists(destinationPath))
{
var fInfo = new FileInfo(destinationPath);
existLen = fInfo.Length;
}
if (existLen > )
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

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

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

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

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

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

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

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

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

    NET WebAPi之断点续传下载(下) 前言 上一篇我们穿插了C#的内容,本篇我们继续来讲讲webapi中断点续传的其他情况以及利用webclient来实现断点续传,至此关于webapi断点续传下载 ...

  5. 在ASP.NET中支持断点续传下载大文件(ZT)

    IE的自带下载功能中没有断点续传功能,要实现断点续传功能,需要用到HTTP协议中鲜为人知的几个响应头和请求头. 一. 两个必要响应头Accept-Ranges.ETag         客户端每次提交 ...

  6. 在ASP.NET中支持断点续传下载大文件

    IE的自带下载功能中没有断点续传功能,要实现断点续传功能,需要用到HTTP协议中鲜为人知的几个响应头和请求头. 一. 两个必要响应头Accept-Ranges.ETag        客户端每次提交下 ...

  7. Asp.Net WebAPI及相关技术介绍(含PPT下载)

    此PPT讲述了Asp.Net WebAPI及相关Web服务技术发展历史. 共80多页,Asp.Net WebAPI在讲到第36页的时候才会出现,因为这个技术不是凭空产生的,它有着自己的演变进化的历史. ...

  8. ASP.NET如何实现断点续传的上传、下载功能?

    1 背景 用户本地有一份txt或者csv文件,无论是从业务数据库导出.还是其他途径获取,当需要使用蚂蚁的大数据分析工具进行数据加工.挖掘和共创应用的时候,首先要将本地文件上传至ODPS,普通的小文件通 ...

  9. Asp.Net WebApi核心对象解析(下篇)

    在接着写Asp.Net WebApi核心对象解析(下篇)之前,还是一如既往的扯扯淡,元旦刚过,整个人还是处于晕的状态,一大早就来处理系统BUG,简直是坑爹(好在没让我元旦赶过来该BUG),队友挖的坑, ...

随机推荐

  1. 【Linux】Too many open files

    ZA 的BOSS 最近出现Too many open files 异常,这个异常一般是由于打开文件数过多引起, 最常见原因是某些连接一致未关闭 记录一些排查用到的指令 查看每个用户最大允许打开文件数量 ...

  2. 使用神经网络来识别手写数字【译】(三)- 用Python代码实现

    实现我们分类数字的网络 好,让我们使用随机梯度下降和 MNIST训练数据来写一个程序来学习怎样识别手写数字. 我们用Python (2.7) 来实现.只有 74 行代码!我们需要的第一个东西是 MNI ...

  3. id选择器、类选择器、属性选择器

    在网页编辑时,通常要对样式进行各种设置.我们借助CSS样式设计中的选择器,就能很好很方便的对它们进行管理和设置了. 今天,跟大家分享一下几个常用的选择器:id选择器.类选择器.属性选择器. id选择器 ...

  4. python实现最简单的计算器功能源码

    import re def calc(formula): formula = re.sub(' ', '', formula) formula_ret = 0 match_brackets = re. ...

  5. elasticsearch完全匹配

    $params = array( "body" => array( "query" => array( "filtered" = ...

  6. PHP:函数赋参数默认初值

    函数的默认参数 允许定义函数时,指定参数的默认值,如: function js_location($url, $alert='', $top=''){ if($alert=='' && ...

  7. webform 组合查询

    界面: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx ...

  8. Python新建动态命名txt文件

    # -*- coding: utf-8 -*- import os,sys,time fname=r"D:\01-学习资料\python" def GetNowTime():#获取 ...

  9. USACO翻译:USACO 2012 JAN三题(3)

    USACO 2012JAN(题目三) 一.题目概览 中文题目名称 放牧 登山 奶牛排队 英文题目名称 grazing climb lineup 可执行文件名 grazing climb lineup ...

  10. ABP理论学习之EntityFramework集成

    返回总目录 本篇目录 Nuget包 创建DbContext 仓储 仓储基类 实现仓储 自定义仓储方法 阅读其他 ABP可以使用任何ORM框架工作,并且已经内置了EntityFramework集成.这篇 ...