WebApi分块下载文件
事情是这样的,最近改了下载文件的接口,原来是直接返回文件在服务器的路径,感觉不怎么好,所以就改了一下改成直接返回文件流。但是别人嵌入式的同事调用以后发现改成流以后就不能分块下载文件了,这才了解到原来嵌入式设备下载大文件一般会采取分块的方式进行下载,这样的好处是一部分一部分的下载,如果断了也能断点续传。
特意研究了一下,文件的断点续传原来http协议中本身就支持这个,在请求的header中设置range属性就可以实现断点续传了,实现代码如下:
1 /// <summary> 2 /// 分块下载文件 3 /// 分块下载文件时需要在header中加一个range参数,格式为 Range:bytes=0-100/1234 起始位置-结束位置/总长度 4 /// </summary> 5 /// <returns></returns> 6 public dynamic BlockDownload(string fileName) 7 { 8 string filePath = HttpRuntime.AppDomainAppPath + $"Files/{fileName}"; 9 if (File.Exists(filePath)) 10 { 11 //设置FileShare 防止 同时访问造成占用 12 using (FileStream fs = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) 13 { 14 HttpResponseMessage response = new HttpResponseMessage(); 15 int bufferSize = 1024 * 100; 16 byte[] bytes = new byte[bufferSize]; 17 //判断是否分块下载 18 if (Request.Headers.Range == null || 19 Request.Headers.Range.Ranges.Count == 0) 20 { 21 fs.Read(bytes, 0, bufferSize); 22 response.Headers.AcceptRanges.Add("bytes"); 23 MemoryStream ms = new MemoryStream(bytes); 24 response.Content = new StreamContent(ms); 25 response.Content.Headers.ContentLength = fs.Length; 26 } 27 else 28 { 29 var range = Request.Headers.Range.Ranges.FirstOrDefault(); 30 long endPosition = 0; 31 if (range != null && range.To.HasValue) 32 { 33 endPosition = range.To.Value; 34 } 35 else 36 { 37 endPosition = fs.Length; 38 } 39 40 //code 206 41 response.StatusCode = HttpStatusCode.PartialContent; 42 int size = Convert.ToInt32(endPosition - range.From.Value) + 1; 43 bytes = new byte[size]; 44 fs.Position = range.From.Value; 45 fs.Read(bytes, 0, size); 46 MemoryStream ms = new MemoryStream(bytes); 47 response.Content = new StreamContent(ms); 48 response.Content.Headers.ContentRange = new ContentRangeHeaderValue(range.From.Value, endPosition, fs.Length); 49 response.Content.Headers.ContentLength = size; 50 } 51 return response; 52 } 53 } 54 return null; 55 }
这样就可以实现文件分块下载了,也可以用多线程的方式下载文件,每个下称下载一小块,这样也能提高下载速度。
下面再放一个下载的demo,是用线程下载的,代码如下:
1 /// <summary> 2 /// 获得文件 3 /// </summary> 4 /// <param name="fileName"></param> 5 [HttpGet] 6 public async void GetFile(string fileName) 7 { 8 string url = $"http://localhost:57583/api/FileManage/BlockDownload?fileName={fileName}"; 9 string filePath = HttpRuntime.AppDomainAppPath + $"Files/"; 10 using (HttpClient client = new HttpClient()) 11 { 12 var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); 13 long fileLength = response.Content.Headers.ContentLength.Value; 14 long blockSize = (long)fileLength / 4; 15 List<long> sizeList = new List<long>(); 16 var tasks = new List<Task>(); 17 //强行分了4块下载 18 for (int i = 0; i < 3; i++) 19 { 20 var begin = i * blockSize; 21 var end = begin + blockSize - 1; 22 tasks.Add(DownLoad(url, begin, end, i)); 23 } 24 tasks.Add(DownLoad(url, 3 * blockSize, fileLength, 3)); 25 await Task.WhenAll(tasks); 26 27 for (int i = 0; i < 4; i++) 28 { 29 using (FileStream fs = new FileStream(filePath + DateTime.Now.ToString("yyyyMMddhhmmss") + fileName, FileMode.Append, FileAccess.Write)) 30 { 31 byte[] bytes = File.ReadAllBytes(filePath + i); 32 fs.Write(bytes, 0, bytes.Length); 33 } 34 File.Delete(filePath + i); 35 } 36 } 37 } 38 39 /// <summary> 40 /// 下载 41 /// </summary> 42 /// <param name="url"></param> 43 /// <param name="start"></param> 44 /// <param name="end"></param> 45 /// <param name="index"></param> 46 /// <returns></returns> 47 private async Task DownLoad(string url, long start, long end, int index) 48 { 49 string filePath = HttpRuntime.AppDomainAppPath + $"Files/"; 50 using (HttpClient client = new HttpClient()) 51 { 52 client.DefaultRequestHeaders.Range = new RangeHeaderValue(start, end); 53 var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); 54 var stream = await response.Content.ReadAsStreamAsync(); 55 var bufferSize = 1024 * 100; 56 int wirtesize; 57 byte[] bytes = new byte[bufferSize]; 58 while ((wirtesize = stream.Read(bytes, 0, bufferSize)) > 0) 59 { 60 using (FileStream fs = new FileStream(filePath + index, FileMode.Append, FileAccess.Write)) 61 { 62 fs.Write(bytes, 0, (int)wirtesize); 63 } 64 } 65 } 66 }
ok,全部结束,如有问题欢迎批评指正。