很久很久以前就想实现多线程下载了,虽然迅雷和旋风还有很多工具都有,可是还是想自己实现,因为有些程序中要集成嘛。之前写到过一个调用迅雷下载引擎的,几个月后试了下不行了,就没有再管它了。
这个程序实现了多线程,断点续传,磁盘缓存等.

写这个程序呢,由于对多线程实在不是很熟...用了3天呢,收货嘛了解到一些多线程的控制.还有线程的暂停等..不用Thread.Abort。。
关于具体思路...由于写过一段时间已经忘了,就简单的看下代码吧..有一些注释,不懂得可以一起探究。
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Net;
namespace FYJ.Download
{
class ThreadDownloadInfo
{
public long startLength; //开始位置
public int length; //下载量
}
public class DownloadHelper
{
private const int DOWNLOAD_BUFFER_SIZE = 102400; //每次下载量 100KB
private const int THREAD_BUFFER_SIZE = 10485760; //每个线程每次最大下载大小 设为10MB 不能太小 否则会创建太多的request对象
public delegate void ErrorMakedEventHandler(String errorString);
public event ErrorMakedEventHandler ErrorMakedEvent;
public delegate void DownloadEventHandler(DownloadEvent e);
public event DownloadEventHandler DownloadEvent;
public delegate void StopEventHandler();
public event StopEventHandler StopEvent;
private object locker = new object();
private long downloadSize = 0; //已经下载的字节
private CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
private ManualResetEvent mre = new ManualResetEvent(true); //初始化不等待
private AutoResetEvent eventFinished = new AutoResetEvent(false);
private void ThreadWork(string url, FileStream fs, QueuedownQueue)
{
mre.WaitOne();
if (cancelTokenSource.IsCancellationRequested)
{
return;
}
ThreadDownloadInfo downInfo = null;
Monitor.Enter(downQueue);
if (downQueue.Count == 0)
{
return;
}
downInfo = downQueue.Dequeue();
Monitor.Exit(downQueue);
HttpWebRequest request = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(url);
request.AddRange(downInfo.startLength); //设置Range值
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
System.IO.Stream ns = response.GetResponseStream();
byte[] nbytes = new byte[DOWNLOAD_BUFFER_SIZE];
int temp = 0;
int nReadSize = 0;
byte[] buffer = new byte[downInfo.length]; //文件写入缓冲
nReadSize = ns.Read(nbytes, 0, Math.Min(DOWNLOAD_BUFFER_SIZE, downInfo.length));
while (temp < downInfo.length)
{
mre.WaitOne();
Buffer.BlockCopy(nbytes, 0, buffer, temp, nReadSize);
lock (locker)
{
this.downloadSize += nReadSize;
}
temp += nReadSize;
nReadSize = ns.Read(nbytes, 0, Math.Min(DOWNLOAD_BUFFER_SIZE, downInfo.length - temp));
}
lock (locker)
{
fs.Seek(downInfo.startLength, SeekOrigin.Begin);
fs.Write(buffer, 0, buffer.Length);
}
ns.Close();
ThreadWork(url, fs, downQueue);
}
public async void StartDownload(DownloadInfo info)
{
this.downloadSize = 0;
if (String.IsNullOrEmpty(info.DownLoadUrl))
throw new Exception("下载地址不能为空!");
if (info.DownLoadUrl.ToLower().StartsWith("http://"))
{
await Task.Run(() =>{
try
{
long totalSize = 0;
long threadInitedLength = 0; //分配线程任务的下载量
#region 获取文件信息
//打开网络连接
System.Net.HttpWebRequest initRequest = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(info.DownLoadUrl);
System.Net.WebResponse initResponse = initRequest.GetResponse();
FileMessage fileMsg = GetFileMessage(initResponse);
totalSize = fileMsg.Length;
if ((!String.IsNullOrEmpty(fileMsg.FileName)) && info.LocalSaveFolder != null)
{
info.SavePath = Path.Combine(info.LocalSaveFolder, fileMsg.FileName);
}
//ReaderWriterLock readWriteLock = new ReaderWriterLock();
#endregion
#region 读取配置文件
string configPath = info.SavePath.Substring(0, info.SavePath.LastIndexOf(".")) + ".cfg";
ListinitInfo = null;
if (File.Exists(configPath) && (info.IsNew == false))
{
initInfo = this.ReadConfig(configPath);
downloadSize = (long)initInfo[0];
totalSize = (long)initInfo[1];
}
#endregion
#region 计算速度
//Stopwatch MyStopWatch = new Stopwatch();
long lastDownloadSize = 0; //上次下载量
bool isSendCompleteEvent = false; //是否完成
Timer timer = new Timer(new TimerCallback((o) =>{
if (!isSendCompleteEvent)
{
DownloadEvent e = new DownloadEvent();
e.DownloadSize = downloadSize;
e.TotalSize = totalSize;
if (totalSize > 0 && downloadSize == totalSize)
{
e.Speed = 0;
isSendCompleteEvent = true;
eventFinished.Set();
}
else
{
e.Speed = downloadSize - lastDownloadSize;
lastDownloadSize = downloadSize; //更新上次下载量
}
DownloadEvent(e);
}
}), null, 0, 1000);
#endregion
string tempPath = info.SavePath.Substring(0, info.SavePath.LastIndexOf(".")) + ".dat";
#region 多线程下载
//分配下载队列
QueuedownQueue = null;
if (initInfo == null || info.IsNew)
{
downQueue = new Queue(); //下载信息队列
while (threadInitedLength < totalSize)
{
ThreadDownloadInfo downInfo = new ThreadDownloadInfo();
downInfo.startLength = threadInitedLength;
downInfo.length = (int)Math.Min(Math.Min(THREAD_BUFFER_SIZE, totalSize - threadInitedLength), totalSize / info.ThreadCount); //下载量
downQueue.Enqueue(downInfo);
threadInitedLength += downInfo.length;
}
}
else
{
downQueue = (Queue)initInfo[2];
}
System.IO.FileStream fs = new FileStream(tempPath, FileMode.OpenOrCreate);
fs.SetLength(totalSize);
int threads = info.ThreadCount;
for (int i = 0; i < info.ThreadCount; i++)
{
ThreadPool.QueueUserWorkItem((state) =>{
ThreadWork(info.DownLoadUrl, fs, downQueue);
if (Interlocked.Decrement(ref threads) == 0)
{
(state as AutoResetEvent).Set();
}
}, eventFinished);
}
//等待所有线程完成
eventFinished.WaitOne();
if (fs != null)
{
fs.Close();
}
fs = null;
if (File.Exists(info.SavePath))
{
File.Delete(info.SavePath);
}
if (downloadSize == totalSize)
{
File.Move(tempPath, info.SavePath);
File.Delete(configPath);
}
if (cancelTokenSource.IsCancellationRequested && StopEvent != null)
{
StopEvent();
//保存配置文件
SaveConfig(configPath, downloadSize, totalSize, downQueue);
}
#endregion
}
catch (Exception ex)
{
if (ErrorMakedEvent != null)
{
ErrorMakedEvent(ex.Message);
}
}
});
}
}
public void Stop()
{
cancelTokenSource.Cancel();
}
public void Suspend()
{
mre.Reset();
}
public void Resume()
{
mre.Set();
}
#region 获取文件信息
public class FileMessage
{
public long Length { get; set; }
public string FileName { get; set; }
}
public FileMessage GetFileMessage(System.Net.WebResponse response)
{
FileMessage info = new FileMessage();
if (response.Headers["Content-Disposition"] != null)
{
Match match = Regex.Match(response.Headers["Content-Disposition"], "filename=(.*)");
if (match.Success)
{
string fileName = match.Groups[1].Value;
Encoding encoding = Encoding.UTF8;
string str = (response as HttpWebResponse).CharacterSet;
if (!String.IsNullOrEmpty(str))
{
encoding = Encoding.GetEncoding(str);
}
info.FileName = System.Web.HttpUtility.UrlDecode(fileName, encoding);
}
}
if (response.Headers["Content-Length"] != null)
{
info.Length = long.Parse(response.Headers.Get("Content-Length"));
}
else
{
info.Length = response.ContentLength;
}
return info;
}
#endregion
private void SaveConfig(string configPath, long downloadSize, long totalSize, QueuedownQueue)
{
StringBuilder sb = new StringBuilder();
sb.Append(downloadSize + ";" + totalSize + ";");
foreach (ThreadDownloadInfo info in downQueue)
{
sb.Append("(" + info.startLength + ",");
sb.Append(info.length + ");");
}
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
string str = Convert.ToBase64String(buffer);
File.WriteAllText(configPath, str);
}
private ListReadConfig(string configPath)
{
Listlist = new List();
string str = File.ReadAllText(configPath);
byte[] buffer = Convert.FromBase64String(str);
str = System.Text.Encoding.UTF8.GetString(buffer);
lock (locker)
{
string[] split = str.Split(';');
long downloadSize = Convert.ToInt64(split[0]);
long totalSize = Convert.ToInt64(split[1]);
QueuedownQueue = new Queue(); //下载信息队列
foreach (Match match in Regex.Matches(str, "\\((\\d+),(\\d+)\\);"))
{
ThreadDownloadInfo downInfo = new ThreadDownloadInfo();
downInfo.startLength = Convert.ToInt64(match.Groups[1].Value);
downInfo.length = Convert.ToInt32(match.Groups[2].Value);
downQueue.Enqueue(downInfo);
}
list.Add(downloadSize);
list.Add(totalSize);
list.Add(downQueue);
}
return list;
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace FYJ.Download
{
public class DownloadEvent
{
private long _downloadSize;
private long _totalSize;
public DownloadEvent()
{
}
////// 每秒下载的速度 B/s
///public double Speed
{
get;
set;
}
////// 每秒下载的速度 KB/s
///public String SpeedKb
{
get { return Math.Round(Speed / 1024.0, 2) + "KB/s"; }
}
public String PercentString
{
get { return (DownloadSize * 100.0 / TotalSize).ToString("F2") + "%"; }
}
public String DownloadMb
{
get { return (DownloadSize / 1048576.0).ToString("F2"); }
}
public String TotalMb
{
get { return (TotalSize / 1048576.0).ToString("F2"); }
}
public long DownloadSize
{
get;
set;
}
public long TotalSize
{
get;
set;
}
}
}using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace FYJ.Download
{
public class DownloadInfo
{
////// 下载地址
///public String DownLoadUrl
{
get;
set;
}
private string _localSaveFolder;
////// 本地保存路径
///public String LocalSaveFolder
{
get { return _localSaveFolder; }
set { _localSaveFolder = value; }
}
private string _savePath;
////// 包含文件名的完整保存路径
///public string SavePath
{
get
{
if (_savePath == null)
{
if (_localSaveFolder == null)
{
throw new Exception("本地保存路径不能为空");
}
_savePath = Path.Combine(_localSaveFolder, Path.GetFileName(DownLoadUrl));
if (File.Exists(_savePath))
{
if (IsNew)
{
if (IsOver)
{
File.Delete(_savePath);
}
else
{
_savePath = _savePath.Substring(0, _savePath.LastIndexOf(".")) + "(2)" + _savePath.Substring(_savePath.LastIndexOf("."));
}
}
}
}
return _savePath;
}
set
{
_savePath = value;
}
}
private int _threadCount = 1;
////// 线程数
///public int ThreadCount
{
get { return _threadCount; }
set { _threadCount = value; }
}
////// 是否覆盖已存在的文件
///public bool IsOver
{
get;
set;
}
////// 是否重新下载
///public bool IsNew
{
get;
set;
}
}
}
珂珂的个人博客 - 一个程序猿的个人网站
5/9/2014 5:37:53 PM 119.129.48....
楼主 话说是不是没有做断点续传