开发者社区> jeffcky> 正文

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

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

前言

上一篇我们穿插了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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
ASP.NET Core on K8S学习初探(2)部署WebAPI到K8S
本文首先主要快速地不完全地不求甚解地过了一下K8S中的一些重要的基本概念,然后通过kubectl部署一个ASP.NET Core WebAPI到K8S中,并初步使用了K8S的伸缩特性对Deployment进行实例的伸缩,体验了一下所谓的容器的编排。
2487 0
ASP.NET WebApi技术从入门到实战演练
一、课程介绍    曾经有一位不知名的讲师说过这么一句名言: 一门RPC技术不会,那么千万万门RPC技术将都不会!在今天移动互联网的时代,作为攻城师的我们,谁不想着只写一套API就可以让我们的Web, Android APP, IOS APP, iPad APP, Hybired APP, H5 Web共用共同的逻辑呢? 希望大家学完本次分享课程能够达到使用ASP.NET WebAPI技术,并且掌握如何优雅提供对外API接口。
2434 0
Asp.Net WebApi 调试利器“单元测试”
当我们编辑好一个WebApi应用程序后,需要对该Api接口进行调试,传统的调试办法是在方法内设置断点,然后用PostMan等http工具模拟访问进行查看WebAPI的运行情况,但这种除了效率较低还进行出现http的请求无法准确“命中”方法的难题。
1251 0
ASP.NET WebAPI String 传值问题
如果我们再WebAPI中定义了只有一个string参数的WebAPI函数,如下所示:   [HttpPost] public string TrackBill(string str) { return str; } 就是这样一个简单的函数,有一个字符串类型的参数,  当我们用PostMan对该API进行测试,给api接口传一个Key为str,value为123的键值对。
1420 0
ASP.NET WebAPi之断点续传下载(中)
前言 前情回顾:上一篇我们遗留了两个问题,一个是未完全实现断点续传,另外则是在响应时是返回StreamContent还是PushStreamContent呢?这一节我们重点来解决这两个问题,同时就在此过程中需要注意的地方一并指出,若有错误之处,请指出。
1116 0
+关注
jeffcky
耕耘博客、走进技术的世界!
文章
问答
视频
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载