珂珂的个人博客 - 一个程序猿的个人网站

c#多线程下载实例

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

这个程序实现了多线程,断点续传,磁盘缓存等.


写这个程序呢,由于对多线程实在不是很熟...用了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;
        }
    }
}
 


上一篇:jUploader上传

下一篇:个人代码全部开源


1 评论

   5/9/2014 5:37:53 PM     119.129.48....

楼主 话说是不是没有做断点续传


查看所有评论

给个评论吧