using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Net;
using System.Diagnostics;namespace Download
{/// <summary>/// 多线程下载文件工具类/// </summary>public class MultiDownload{#region 变量// 线程数量private int _threadNum;//文件大小private long _fileSize;//文件地址private string _fileUrl;//保存路径private string _savePath;//线程完成数量private short _threadCompleteNum;//是否完成private bool _isComplete;//当前总文件下载大小(实时的)private volatile int _downloadSize;//线程数组private Thread[] _threads;//未下载完全分片,最多尝试次数private int _maxTryTimes;    // 临时文件列表private List<string> _tempFiles = new List<string>();// 线程锁private object locker = new object();#endregion/// <summary>/// 构造函数/// </summary>/// <param name="threahNum">线程数量</param>/// <param name="fileUrl">文件Url路径</param>/// <param name="savePath">本地保存路径</param>public MultiDownload(int threahNum, string fileUrl, string savePath,int maxTryTimes=20){this._threadNum = threahNum;this._threads = new Thread[threahNum];this._fileUrl = fileUrl;this._savePath = savePath;this._maxTryTimes = maxTryTimes;}/// <summary>/// 开始下载文件/// </summary>/// <param name="wait">是否为同步,true为同步会等待下载完成,false为异步继续执行后面的代码</param>public void Start(bool wait=false){HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_fileUrl);HttpWebResponse response = (HttpWebResponse)request.GetResponse();// 获取文件总大小_fileSize = response.ContentLength;// 平均分配long singelNum = (_fileSize / _threadNum);// 获取剩余的long remainder = (_fileSize % _threadNum);  request.Abort();response.Close();for (int i = 0; i < _threadNum; i++){List<long> range = new List<long>{i * singelNum};if (remainder != 0 && (_threadNum - 1) == i){//剩余的交给最后一个线程range.Add(i * singelNum + singelNum + remainder - 1);}else{range.Add(i * singelNum + singelNum - 1);}// 下载文件指定位置的数据long[] ran = new long[] { range[0], range[1] };_threads[i] = new Thread(new ParameterizedThreadStart(Download)){Name = Path.GetFileNameWithoutExtension(_fileUrl) + "_{0}".Replace("{0}", Convert.ToString(i + 1))};_threads[i].Start(ran);}if (wait){foreach (Thread item in _threads){//数组中的所有子线程都对主线程进行阻塞,只是阻塞了启动item.Join();}}}/// <summary>/// 下载文件/// </summary>/// <param name="obj">参数为分片起始位置,range[0], range[1] </param>private void Download(object obj){long[] rans = obj as long[];string tmpFileBlock = Path.GetTempPath() + Thread.CurrentThread.Name + ".tmp";_tempFiles.Add(tmpFileBlock);// 当前需要下载分片大小long shardFileSize = rans[1] - rans[0] + 1;// 尝试重新下载次数int tryTimes = 0;
#if DEBUGTrace.WriteLine($"开始下载分片文件:{tmpFileBlock},长度: {shardFileSize}");
#endifDownload(tmpFileBlock,true, rans[0], rans[1],out long completedSize);while (completedSize < shardFileSize){// 未下载完成 继续下载
#if DEBUGTrace.WriteLine($"未下载完的文件:{tmpFileBlock},未完成部分大小: {shardFileSize - completedSize}");
#endifDownload(tmpFileBlock,false, (rans[0] + completedSize), rans[1], out completedSize);tryTimes++;if (tryTimes > _maxTryTimes){break;}}if (completedSize == shardFileSize){lock (locker) _threadCompleteNum++;}if (_threadCompleteNum == _threadNum){// 所有分片都已下载完成Complete();_isComplete = true;}}/// <summary>/// 下载文件/// </summary>/// <param name="tmpFileBlock">临时文件存放路径</param>/// <param name="isFirst">是否第一次下载,若为第一次下载则创建新文件,若不是第一次下载则在原文件上追加</param>/// <param name="fromRange">分片开始位置</param>/// <param name="toRange">分片结束位置</param>/// <param name="completedSize">返回已下载长度,用于判断分片文件是否下载完成</param>private void Download(string tmpFileBlock,bool isFirst, long fromRange, long toRange,out long completedSize){Stream httpFileStream = null, localFileStram = null; long shardFileSize = 0L;try{HttpWebRequest httprequest = (HttpWebRequest)WebRequest.Create(_fileUrl);httprequest.AddRange(fromRange, toRange);HttpWebResponse httpresponse = (HttpWebResponse)httprequest.GetResponse();httpFileStream = httpresponse.GetResponseStream();localFileStram = new FileStream(tmpFileBlock, isFirst? FileMode.Create: FileMode.Append);// 获取当前分片大小shardFileSize = httpresponse.ContentLength + localFileStram.Length;
#if DEBUG       Trace.WriteLine($"开始下载分片文件:{tmpFileBlock},下载片段位置: {fromRange}-{toRange},下载长度:{toRange - fromRange + 1}-{shardFileSize}");
#endif  byte[] by = new byte[4096];// Read方法将返回读入by变量中的总字节数int getByteSize = httpFileStream.Read(by, 0, (int)by.Length);while (getByteSize > 0){lock (locker) _downloadSize += getByteSize;localFileStram.Write(by, 0, getByteSize);getByteSize = httpFileStream.Read(by, 0, (int)by.Length);}}catch (WebException ex){Console.WriteLine("下载异常: " + ex.Message);throw new WebException(ex.Message.ToString());}finally{#if DEBUG       Trace.WriteLine($"{(shardFileSize == localFileStram.Length)} 结束下载分片文件:{tmpFileBlock},下载片段位置: {fromRange}-{toRange},本次需完成长度:{toRange - fromRange + 1},未完成长度:{shardFileSize - localFileStram.Length}");
#endif// 计算未下载文件长度completedSize = localFileStram.Length;if (httpFileStream != null) httpFileStream.Dispose();if (localFileStram != null) localFileStram.Dispose();}}/// <summary>/// 下载完成后合并文件块/// </summary>private void Complete(){Stream mergeFile = new FileStream(@_savePath, FileMode.Create);BinaryWriter writer = new BinaryWriter(mergeFile);foreach (string file in _tempFiles){using (FileStream fs = new FileStream(file, FileMode.Open)){BinaryReader tempReader = new BinaryReader(fs);writer.Write(tempReader.ReadBytes((int)fs.Length));tempReader.Close();}File.Delete(file);}writer.Close();}}
}

使用方法

string httpUrl = @"http://127.0.0.1/file/test.zip";
string saveUrl = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "//" + System.IO.Path.GetFileName(httpUrl);
int threadNumber = 10;
MultiDownload md = new MultiDownload(threadNumber, httpUrl, saveUrl);
md.Start();

C# 多线程下载文件功能实现,优化文件下载不全问题相关推荐

  1. Java多线程下载文件

    Java多线程下载文件 优化:合理利用服务器资源,将资源利用最大化,加快下载速度 一般有两种方式: 线程池里面有N个线程,多线程下载单个文件,将网络路径的文件流切割成多快,每个线程下载一小部分,然后写 ...

  2. 多线程下载文件实践之旅

    目录 1.使用场景 2.多线程下载原理 3.请求如何分段下载 3.1.需要请求的数据如何分段. 3.2.分段下载的数据如何组装成完整的数据文件. 4.关键代码实现 3.成果展现 4.总结 5.参考文章 ...

  3. 在QT中采用多线程下载文件

    在QT中采用多线程下载文件 这里的线程是指下载的通道(和操作系统中的线程不一样),一个线程就是一个文件的下载通道,多线程也就是同时开起好几个下载通道.当服务器提供下载服务 时,使用下载者是共享带宽的, ...

  4. python多线程下载文件

    看到一篇多线程下载的文章,这里把自己的理解写一篇多线程下载的文章. 我们访问http://192.168.10.7/a.jpg时是get请求,response的head包含Content-Length ...

  5. java 多线程下载文件并实时计算下载百分比(断点续传)

    多线程下载文件 多线程同时下载文件即:在同一时间内通过多个线程对同一个请求地址发起多个请求,将需要下载的数据分割成多个部分,同时下载,每个线程只负责下载其中的一部分,最后将每一个线程下载的部分组装起来 ...

  6. java线程下载文件_Java多线程下载文件实例详解

    本文实例为大家分享了Java多线程下载文件的具体代码,供大家参考,具体内容如下 import java.io.File; import java.io.InputStream; import java ...

  7. android ftp同步程序,ftp同步 安卓,安卓手机ftp上传下载文件功能同步视频照片

    手机拍照越来方便,手机里的照片也越积越多,手机运行缓慢,本文利用安卓的每步FTP服务APP来自动实现手机视频照片的同步,释放手机被占用的存储空间.在机顶盒上运行每步FTP服务,机顶盒USB口连接U盘做 ...

  8. springboot ajax下载文件功能封装

    通过js ajax下载文件功能封装 function exportExcel(formId, url) {try {var queryForm = $("#" + formId); ...

  9. 【Java】网络编程——多线程下载文件

    前言 多线程下载文件,比单线程要快,当然,线程不是越多越好,这和获取的源文件还有和网速有关. 原理:在请求服务器的某个文件时,我们能得到这个文件的大小长度信息,我们就可以下载此长度的某一个片段,来达到 ...

最新文章

  1. int[]到string[]的转换方法 Array.ConvertAll
  2. windows API 创建系统托盘图标
  3. jquery的sortable拖拽排序问题,在页面上多次拖拽保存顺序之后,刷新页面,排序出现紊乱
  4. gbdt降低学习率可以实现正则化效果呢
  5. 老虞学GoLang笔记-数组和切片
  6. 基于SDN的应用定义安全方案
  7. 如何将linux内核的带级别控制的printk内容打印出来
  8. Kafka使用经验小结
  9. html前端通过canvas生成验证码
  10. docker 错误:Error response from daemon: cannot stop container: connect: connection refused: unknown...
  11. jquery下插入标签以及clone的应用
  12. 第三季-第11课-进程控制理论
  13. 王者荣耀是用什么代码变成MOBA游戏的,该怎么学?有前途吗?
  14. 逻辑程序设计语言Prolog
  15. 读书笔记-互联网鲇鱼法则
  16. 针对部分软件无法开机自启动情况的解决措施(已解决)
  17. 项目管理中成本管理相关指标
  18. RIME中州韵输入法lua配置获取当前时间(二)
  19. 【错误记录】Mac 中 Python 报错 ( ERROR: Could not build wheels for numpy which use PEP 517 | 问题未解决 | 问题记录 )
  20. linux系统的文件句柄数

热门文章

  1. 行为驱动:python+behave,学习记录
  2. Hexo+Github博客:新建菜单,并在该菜单内添加单篇/多篇文章
  3. 嫦娥成功“打水漂”返回暴露了美国登月造假
  4. PCI Express 6.0 规范
  5. 分享一款灵动微电动车辆方案-MM32SPIN05
  6. 讲一下prototype是什么东西,原型链的理解,什么时候用prototype?
  7. 网络攻防实验:离线攻击工具——彩虹表破解
  8. 耽美是女性调节大脑反应的潜在性吸引力
  9. 疯狂java讲义第五版电子书,一文说清!
  10. chatgpt4.0体验