使用HttpWebRequst.分块下载思路:
(为什么用它?原因在于: request.AddRange(startPos, endPos); 可以设置下载的起始位置)
1.先计算每个线程下载块的平均值,
//计算每条线程下载的数据长度 this.block = (this.fileSize % this.threads.Length) == 0 ? this.fileSize / this.threads.Length : this.fileSize / this.threads.Length + 1;
2.计算每个线程的起始位置
int startPos = (int)(block * (threadId - 1) + downLength);//开始位置 int endPos = (int)(block * threadId - 1);//结束位置
3.当前下载块失败后.重新下载当前块内容,并设置当前失败标记,以便重新启动该线程
this.downLength = -1;
具体实现
创建一个接口.获取当前下载文件的总和
public interface IDownloadProgressListener { void OnDownloadSize(long size); }
实现上面的接口,并创建一个委托.告知调用者当前下载的总和
public class DownloadProgressListener : IDownloadProgressListener { public delegate void dlgSendMsg(DownMsg msg); public dlgSendMsg doSendMsg = null; public void OnDownloadSize(long size) { DownMsg msg = new DownMsg(); msg.speed = (float)(size - Form1.presize); //下载速度 msg.size = size; //下载总量 Form1.presize = size; msg.tag = 1; if (doSendMsg != null) doSendMsg(msg);//通知具体调用者下载进度 } } public class DownMsg { public int tag { get; set; } public long size { get; set; } public float speed { get; set; } }
每个线程调用当前ThreadRun()方法
public class DownloadThread { private string saveFilePath; private string downUrl; private long block; private int threadId = -1; private long downLength; private bool finish = false; private FileDownloader downloader; public DownloadThread(FileDownloader downloader, string downUrl, string saveFile, long block, long downLength, int threadId) { this.downUrl = downUrl; this.saveFilePath = saveFile; this.block = block; this.downloader = downloader; this.threadId = threadId; this.downLength = downLength; } public void ThreadRun() { //task Thread td = new Thread(new ThreadStart(() => { if (downLength < block)//未下载完成 { try { int startPos = (int)(block * (threadId - 1) + downLength);//开始位置 int endPos = (int)(block * threadId - 1);//结束位置 Console.WriteLine("Thread " + this.threadId + " start download from position " + startPos + " and endwith " + endPos); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(downUrl); request.Referer = downUrl.ToString(); request.Method = "GET"; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.1124)"; request.AllowAutoRedirect = false; request.ContentType = "application/octet-stream"; request.Accept = "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"; request.Timeout = 10 * 1000; request.AllowAutoRedirect = true; request.AddRange(startPos, endPos); //Console.WriteLine(request.Headers.ToString()); //输出构建的http 表头 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); WebResponse wb = request.GetResponse(); using (Stream _stream = wb.GetResponseStream()) { byte[] buffer = new byte[1024 * 50]; //缓冲区大小 long offset = -1; using (Stream threadfile = new FileStream(this.saveFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) //设置文件以共享方式读写,否则会出现当前文件被另一个文件使用. { threadfile.Seek(startPos, SeekOrigin.Begin); //移动文件位置 while ((offset = _stream.Read(buffer, 0, buffer.Length)) != 0) { //offset 实际下载流大小 downloader.append(offset); //更新已经下载当前总文件大小 threadfile.Write(buffer, 0, (int)offset); downLength += offset; //设置当前线程已下载位置 downloader.update(this.threadId, downLength); } threadfile.Close(); //using 用完后可以自动释放..手动释放一遍.木有问题的(其实是多余的) _stream.Close(); Console.WriteLine("Thread " + this.threadId + " download finish"); this.finish = true; } } } catch (Exception e) { this.downLength = -1; Console.WriteLine("Thread " + this.threadId + ":" + e.Message); } } })); td.IsBackground = true; td.Start(); } /// <summary> /// 下载是否完成 /// </summary> /// <returns></returns> public bool isFinish() { return finish; } /// <summary> /// 已经下载的内容大小 /// </summary> /// <returns>如果返回值为-1,代表下载失败</returns> public long getDownLength() { return downLength; } }
获取下载文件大小,并分配每个线程的任务
public class FileDownloader { /// <summary> /// 已下载文件长度 /// </summary> private long downloadSize = 0; /// <summary> /// 原始文件长度 /// </summary> private long fileSize = 0; /// <summary> /// 线程数 /// </summary> private DownloadThread[] threads; /// <summary> /// 本地保存文件 /// </summary> private string saveFile; /// <summary> /// 缓存各线程下载的长度 /// </summary> public Dictionary<int, long> data = new Dictionary<int, long>(); /// <summary> /// 每条线程下载的长度 /// </summary> private long block; /// <summary> /// 下载路径 /// </summary> private String downloadUrl; /// <summary> /// 获取线程数 /// </summary> /// <returns> 获取线程数</returns> public int getThreadSize() { return threads.Length; } /// <summary> /// 获取文件大小 /// </summary> /// <returns>获取文件大小</returns> public long getFileSize() { return fileSize; } /// <summary> /// 累计已下载大小 /// </summary> /// <param name="size">累计已下载大小</param> public void append(long size) { lock (this) //锁定同步..............线程开多了竟然没有同步起来.文件下载已经完毕了,下载总数量却不等于文件实际大小,找了半天原来这里错误的 { downloadSize += size; } } /// <summary> /// 更新指定线程最后下载的位置 /// </summary> /// <param name="threadId">threadId 线程id</param> /// <param name="pos">最后下载的位置</param> public void update(int threadId, long pos) { if (data.ContainsKey(threadId)) { this.data[threadId] = pos; } else { this.data.Add(threadId, pos); } } /// <summary> /// 构建下载准备,获取文件大小 /// </summary> /// <param name="downloadUrl">下载路径</param> /// <param name="fileSaveDir"> 文件保存目录</param> /// <param name="threadNum">下载线程数</param> public FileDownloader(string downloadUrl, string fileSaveDir, int threadNum) { try { //构建http 请求 this.downloadUrl = downloadUrl; if (!Directory.Exists(fileSaveDir)) Directory.CreateDirectory(fileSaveDir); this.threads = new DownloadThread[threadNum]; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(downloadUrl); request.Referer = downloadUrl.ToString(); request.Method = "GET"; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.1124)"; request.ContentType = "application/octet-stream"; request.Accept = "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"; request.Timeout = 20 * 1000; request.AllowAutoRedirect = true; using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode == HttpStatusCode.OK) { this.fileSize = response.ContentLength;//根据响应获取文件大小 if (this.fileSize <= 0) throw new Exception("获取文件大小失败"); string filename = Uri.UnescapeDataString(Path.GetFileName(downloadUrl));//获取文件名称 uri 解码中文字符 if (filename.Length == 0) throw new Exception("获取文件名失败"); this.saveFile = Path.Combine(fileSaveDir, filename); //构建保存文件 //计算每条线程下载的数据长度 this.block = (this.fileSize % this.threads.Length) == 0 ? this.fileSize / this.threads.Length : this.fileSize / this.threads.Length + 1; } else { throw new Exception("服务器返回状态失败,StatusCode:" + response.StatusCode); } } } catch (Exception e) { Console.WriteLine(e.Message); throw new Exception("无法连接下载地址"); } } /// <summary> /// 开始下载文件 /// </summary> /// <param name="listener">监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null</param> /// <returns>已下载文件大小</returns> public long download(IDownloadProgressListener listener) { try { using (FileStream fstream = new FileStream(this.saveFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)) { if (this.fileSize > 0) fstream.SetLength(this.fileSize); fstream.Close(); } if (this.data.Count() != this.threads.Length) { this.data.Clear(); for (int i = 0; i < this.threads.Length; i++) { this.data.Add(i + 1, 0);//初始化每条线程已经下载的数据长度为0 } } for (int i = 0; i < this.threads.Length; i++) {//开启线程进行下载 long downLength = this.data[i + 1]; if (downLength < this.block && this.downloadSize < this.fileSize) {//判断线程是否已经完成下载,否则继续下载 + // Console.WriteLine("threads" + i.ToString() + ",下载块" + this.block.ToString() + " " + this.data[i + 1].ToString() + " " + downloadSize.ToString()); this.threads[i] = new DownloadThread(this, downloadUrl, this.saveFile, this.block, this.data[i + 1], i + 1); this.threads[i].ThreadRun(); } else { this.threads[i] = null; } } bool notFinish = true;//下载未完成 while (notFinish) {// 循环判断所有线程是否完成下载 Thread.Sleep(900); notFinish = false;//假定全部线程下载完成 for (int i = 0; i < this.threads.Length; i++) { if (this.threads[i] != null && !this.threads[i].isFinish()) {//如果发现线程未完成下载 notFinish = true;//设置标志为下载没有完成 if (this.threads[i].getDownLength() == -1) {//如果下载失败,再重新下载 this.threads[i] = new DownloadThread(this, downloadUrl, this.saveFile, this.block, this.data[i + 1], i + 1); this.threads[i].ThreadRun(); } } } if (listener != null) { listener.OnDownloadSize(this.downloadSize);//通知目前已经下载完成的数据长度 Console.WriteLine(this.downloadSize); } } } catch (Exception e) { Console.WriteLine(e.Message); throw new Exception("下载文件错误"); } return this.downloadSize; } }
主窗体调用下载
public partial class Form1 : Form { public Form1() { InitializeComponent(); } Stopwatch stop; public static long presize = 0; private void btnDown_Click(object sender, EventArgs e) { stop = new Stopwatch(); stop.Start(); //计时 string path = Uri.EscapeUriString(txtUrl.Text.Trim()); string dir = @"G:\test"; bar.Value = 0; btnDown.Enabled = false; Task tsk = new Task(() => { download(path, dir); }); tsk.Start(); lbPercent.Visible = true; lbSpeed.Visible = true; } private void download(string path, string dir) { try { FileDownloader loader = new FileDownloader(path, dir, (int)NumThreads.Value); loader.data.Clear(); this.Invoke(new MethodInvoker(() => { bar.Maximum = (int)loader.getFileSize(); })); DownloadProgressListener linstenter = new DownloadProgressListener(); linstenter.doSendMsg = new DownloadProgressListener.dlgSendMsg(SendMsgHander); loader.download(linstenter); } catch (Exception ex) { DownMsg msg = new DownMsg(); msg.tag = -1; SendMsgHander(msg); Console.WriteLine(ex.Message); } } private void SendMsgHander(DownMsg msg) { switch (msg.tag) { case 1: this.Invoke(new MethodInvoker(() => { bar.Value = (int)msg.size; float count = (float)bar.Value / (float)bar.Maximum; lbPercent.Text = ((int)(count * 100)).ToString() + "%"; lbSpeed.Text = ((int)msg.speed / 1024).ToString() + "K"; Console.WriteLine(msg.size + " " + msg.speed); if (bar.Maximum == msg.size) { stop.Stop(); string str = "使用线程数 " + NumThreads.Value.ToString() + " 耗时:" + stop.ElapsedMilliseconds.ToString(); Console.WriteLine(str); lbSpeed.Visible = false; btnDown.Enabled = true; MessageBox.Show(str, "下载完毕"); } })); break; case -1: MessageBox.Show("下载失败"); break; } } private void Form1_Load(object sender, EventArgs e) { lbPercent.Visible = false; lbSpeed.Visible = false; } }
窗体界面: 下载一个132M的某软件为例
下载中.....
下载完毕后
整个下载完毕,并可以执行下载文件.说明分块下载成功.
ps:看到最后一张图没有人认为我在打广告吧.那只能呵呵呵呵呵呵呵呵呵了
编码调试时,遇见的问题
1.下载中文路径处理(已处理).
2.计算总下载字节数同步问题(调试了好久才发觉是同步问题)(已处理)
3.并不是开的线程越多,下载的速度越快.(在debug的时候.会抛出大量的请求超时) ,3-5个差不多了
ps:伪原创啦.原创代码是java的.
下载地址: 源代码
vs2010 framework 4.0