多线程断点续传研究之二

时间:2022-03-21 18:33:02

上篇文章写完,由于整体思路是正确的,但是没有真正形成多线程下载,所以对本身的代码进行关键点的检查,尤其在一些操作web请求的地方,看看是否有什么问题,最后发现显示的关闭HttpWebResponse对象,能稍微有所改进。

原文参看:

http://blog.csdn.net/Knight94/archive/2006/08/04/1018305.aspx

 

那么修改后的类,大致代码如下:

//--------------------------- Download File class ---------------------------------------

//---------------------------------------------------------------------------------------

//---File:          clsNewDownloadFile

//---Description:   The multi-download file class file using HttpWebRequest class

//---Author:        Knight

//---Date:          Aug.4, 2006

//---------------------------------------------------------------------------------------

//---------------------------{Download File class}---------------------------------------

 

namespace DownloadFile

{

    using System;

    using System.IO;

    using System.Net;

    using System.Text;

    using System.Security;

    using System.Threading;

    using System.Collections.Specialized;

    using System.Diagnostics;

    using System.Data;

 

    /// <summary>

    /// Download file info

    /// </summary>

    public class DownloadFileInfo

    {

        private string _RequestURL;

        private string _ResponseURL;

        private string _FileName;

        private int _TotalSize;

        private int _BlockLeftSize;

 

        public string RequestURL

        {

            get{ return _RequestURL; }

        }

 

        public string ResponseURL

        {

            get{ return _ResponseURL; }

        }

        public string FileName

        {

            get{ return _FileName; }

        }

        public int TotalSize

        {

            get{ return _TotalSize;}

            set{ _TotalSize = value;}

        }

        public int BlockLeftSize

        {

            get{ return _BlockLeftSize;}

            set{ _BlockLeftSize = value;}

        }

 

        public DownloadFileInfo(

            string RequestURL,

            string ResponseURL,

            string FileName,

            int TotalSize,

            int BlockLeftSize )

        {

            this._RequestURL = RequestURL;

            this._ResponseURL = ResponseURL;

            this._FileName = FileName;

            this._TotalSize = TotalSize;

            this._BlockLeftSize = BlockLeftSize;

        }

 

        public DownloadFileInfo(

            string RequestURL,

            string ResponseURL,

            string FileName ):

            this( RequestURL, ResponseURL, FileName, 0, 0 )

        {

        }

    }

 

    /// <summary>

    /// Download event arguments

    /// </summary>

    public class DownLoadEventArgs : System.EventArgs

    {

        private DownloadFileInfo _DownloadFileInfo;

        private int _Position;

        private int _ReadCount;

        private int _ThreadNO;

 

        public DownloadFileInfo DownFileInfo

        {

            get{ return _DownloadFileInfo; }

        }

        public int StartPosition

        {

            get{ return _Position; }

        }

        public int ReadCount

        {

            get{ return _ReadCount; }

        }

        public int ThreadNO

        {

            get{ return _ThreadNO; }

        }

 

        public DownLoadEventArgs(

            DownloadFileInfo DownFileInfo,

            int nStartPostion,

            int nReadCount,

            int nThreadNO )

        {

            this._DownloadFileInfo = DownFileInfo;

            this._Position = nStartPostion;

            this._ReadCount = nReadCount;

            this._ThreadNO = nThreadNO;

        }

    }

   

    public delegate void DataReceivedEventHandler( DownLoadEventArgs e );

    public delegate void ThreadFinishedHandler();

 

    /// <summary>

    /// class for sub-download threads

    /// </summary>

    public class SubDownloadThread

    {

        private readonly DownloadFileInfo _FileInfo;

        private int _ThreadNO;

        private int _Position;

        private int _BlockSizeLeft;

        public event DataReceivedEventHandler DataReceived;

        private ThreadFinishedHandler _Finished;

        private bool _IsStopped;

 

        private Thread thdDownload;

 

        public SubDownloadThread(

            DownloadFileInfo DownFileInfo,

            int ThreadNO,

            int Position,

            int BlockSizeLeft,

            ThreadFinishedHandler Finished )

        {

            this._FileInfo = DownFileInfo;

            this._ThreadNO = ThreadNO;

            this._Position = Position;

            this._BlockSizeLeft = BlockSizeLeft;

            this._Finished = Finished;

        }

 

        /// <summary>

        /// Start to create thread to download file

        /// </summary>

        public void StartDownload()

        {

            thdDownload = new Thread( new ThreadStart( this.Download ) );

            thdDownload.Start();

        }

 

        /// <summary>

        /// Stop current download-thread

        /// </summary>

        public void Stop()

        {

            _IsStopped = true;

            if( thdDownload.ThreadState == System.Threading.ThreadState.Running )

                thdDownload.Join( 10 );

            Debug.WriteLine( string.Format( "Thread NO:{0} is stopped!" ,_ThreadNO ) );

        }

 

        /// <summary>

        /// Function for sub-thread

        /// </summary>

        private void Download()

        {

            HttpWebResponse hwrp = null;

 

            try

            {

                Debug.WriteLine( string.Format( "Thread NO:{0} begins to download!" ,_ThreadNO ) );

                // Creat request by url

                HttpWebRequest hwrq = (HttpWebRequest) WebRequest.Create(

                    new Uri( this._FileInfo.RequestURL ));

                // Go to download position

                hwrq.AddRange( _Position );

                // Get response from request

                hwrp = (HttpWebResponse) hwrq.GetResponse();

            }

            catch (Exception e)

            {

                Debug.WriteLine( e.Message );

                return;

            }

 

            // download and write data from

            Debug.WriteLine( string.Format( "Thread NO:{0} call function named DownloadData!" ,

                _ThreadNO ) );

            DownloadData( hwrp );

 

            hwrp.Close();//Close response here

        }

 

        /// <summary>

        /// Download data through web request

        /// </summary>

        /// <param name="Response"></param>

        private void DownloadData( WebResponse Response )

        {

            const int BUFFER_SIZE = 0x10000;//64k buffer size

            byte[] bBuffer = new byte[ BUFFER_SIZE ];

 

            Stream sReader = Response.GetResponseStream();

            if( sReader == null ) return;

           

            int nReadCount = 0;

 

            while( !_IsStopped && this._BlockSizeLeft > 0 )

            {

                Debug.WriteLine( string.Format( "Thread NO:{0} reads data!" ,

                    _ThreadNO ) );

                // Set read size

                nReadCount = ( BUFFER_SIZE > this._BlockSizeLeft )

                    ? this._BlockSizeLeft:BUFFER_SIZE ;//Get current read count

 

                // Read data

                int nRealReadCount = sReader.Read( bBuffer, 0, nReadCount );

                if( nRealReadCount <= 0 ) break;

 

                Debug.WriteLine( string.Format( "Thread NO:{0} writes data!" ,

                    _ThreadNO ) );

                // Write data

                using( FileStream sw = new FileStream(

                           this._FileInfo.FileName,

                           System.IO.FileMode.OpenOrCreate,

                           System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite))

                {

                    sw.Position = this._Position;

                    sw.Write( bBuffer, 0, nRealReadCount );

                    Debug.WriteLine( string.Format( "Thread NO:{0} writes {1} data!" ,

                        _ThreadNO, nRealReadCount ) );

                    sw.Flush();

                    sw.Close();

                }

 

                Debug.WriteLine( string.Format( "Thread NO:{0} send callback info!" ,

                    _ThreadNO ) );

                // Call back

                DataReceived( new DownLoadEventArgs( this._FileInfo,

                    this._Position,

                    nRealReadCount,

                    this._ThreadNO ) );

 

                // Set position and block left

                this._Position += nRealReadCount;

                this._BlockSizeLeft -= nRealReadCount;

            }

 

            if( _IsStopped ) return;

 

            Debug.WriteLine( string.Format( "Thread NO:{0} sends finish-info!" ,

                _ThreadNO ) );

            _Finished();

        }

    }

 

    /// <summary>

    /// Class for download thread

    /// </summary>

    public class DownloadThread

    {

        private DownloadFileInfo _FileInfo;

        private SubDownloadThread[] subThreads;

        private int nThreadFinishedCount;

        private DataRow drFileInfo;

        private DataTable dtThreadInfo;

        public DataReceivedEventHandler DataReceived;

        public event ThreadFinishedHandler Finished;

       

        /// Class's Constructor

        public DownloadThread(

            string RequestURL,

            string ResponseURL,

            string FileName,

            DataRow NewFileInfo,

            int SubThreadNum )

        {

            // Create download file info

            _FileInfo = new DownloadFileInfo( RequestURL, ResponseURL, FileName );

 

            // Create request to get file info

            bool blnMultiDownload = GetFileDownInfo();

 

            // Create file info datarow

            drFileInfo = NewFileInfo;

            InitDataRow();

 

            // Create subthreads and set these info in threadinfo table

            if( SubThreadNum <= 0 ) SubThreadNum = 5;//Defualt value

 

            //Not support to be multi-thread download or less than 64k

            if( !blnMultiDownload || _FileInfo.TotalSize <= 0x10000 ) SubThreadNum = 1;

 

            subThreads = new SubDownloadThread[SubThreadNum];

            nThreadFinishedCount = 0;

            CreateThreadTable( SubThreadNum );

        }

 

        public DownloadThread( DataRow DownloadFileInfo, DataTable ThreadInfos )

        {

            // Create download file info

            drFileInfo = DownloadFileInfo;

            _FileInfo = new DownloadFileInfo(

                drFileInfo["RequestURL"].ToString(),

                drFileInfo["ResponseURL"].ToString(),

                drFileInfo["FileName"].ToString(),

                Convert.ToInt32( drFileInfo["TotalSize"].ToString() ),

                Convert.ToInt32( drFileInfo["BlockLeftSize"].ToString() ) );

 

            // Create sub thread class objects

            dtThreadInfo = ThreadInfos;

            subThreads = new SubDownloadThread[ dtThreadInfo.Rows.Count ];

            nThreadFinishedCount = 0;

        }

        /// {Class's Constructor}

 

        /// <summary>

        /// Start to download

        /// </summary>

        public void Start()

        {

            StartSubThreads();

        }

 

        /// <summary>

        /// Create table to save thread info

        /// </summary>

        /// <param name="SubThreadNum"></param>

        private void CreateThreadTable( int SubThreadNum )

        {

            int nThunkSize = _FileInfo.TotalSize / SubThreadNum;

            dtThreadInfo = new DataTable("DownloadFileInfo");

            dtThreadInfo.Columns.Add( "ThreadNO", typeof(int) );

            dtThreadInfo.Columns.Add( "Position", typeof(int) );

            dtThreadInfo.Columns.Add( "BlockLeftSize", typeof(int) );

 

            DataRow drNew;

            int i = 0;

            for( ; i < SubThreadNum-1; i++ )

            {

                drNew = dtThreadInfo.NewRow();

                drNew["ThreadNO"] = i;

                drNew["Position"] = i * nThunkSize;

                drNew["BlockLeftSize"] = nThunkSize;

                dtThreadInfo.Rows.Add( drNew );

            }

 

            drNew = dtThreadInfo.NewRow();

            drNew["ThreadNO"] = i;

            drNew["Position"] = i * nThunkSize;

            drNew["BlockLeftSize"] = _FileInfo.TotalSize - i * nThunkSize;

            dtThreadInfo.Rows.Add( drNew );

            dtThreadInfo.AcceptChanges();

        }

 

        /// <summary>

        /// Start sub-threads to download file

        /// </summary>

        private void StartSubThreads()

        {

            foreach( DataRow dr in dtThreadInfo.Rows )

            {

                int ThreadNO = Convert.ToInt32( dr["ThreadNO"].ToString() );

                if( Convert.ToInt32( dr["BlockLeftSize"].ToString() ) > 0 )

                {

                    subThreads[ ThreadNO ] = new SubDownloadThread(

                        _FileInfo,

                        ThreadNO,

                        Convert.ToInt32( dr["Position"].ToString() ),

                        Convert.ToInt32( dr["BlockLeftSize"].ToString() ),

                        new ThreadFinishedHandler( this.DownloadFinished ) );

 

                    subThreads[ ThreadNO ].DataReceived += this.DataReceived;

                    subThreads[ ThreadNO ].StartDownload();

                }

                else

                    DownloadFinished();

            }

        }

 

        /// <summary>

        /// Save download file info

        /// </summary>

        private void InitDataRow()

        {

            drFileInfo.BeginEdit();

            drFileInfo["RequestURL"] = _FileInfo.RequestURL;

            drFileInfo["ResponseURL"] = _FileInfo.ResponseURL;

            drFileInfo["FileName"] = _FileInfo.FileName;

            drFileInfo["TotalSize"] = _FileInfo.TotalSize;

            drFileInfo["BlockLeftSize"] = _FileInfo.BlockLeftSize;

            drFileInfo["CreatedTime"] = DateTime.Now.ToString( "yyyyMMddHHmmss" );

            drFileInfo.EndEdit();

            drFileInfo.AcceptChanges();

        }

 

        /// <summary>

        /// Check url to get download file info

        /// </summary>

        /// <returns></returns>

        private bool GetFileDownInfo()

        {

            HttpWebRequest hwrq;

            HttpWebResponse hwrp = null;

            try

            {

                //Create request

                hwrq = (HttpWebRequest) WebRequest.Create( _FileInfo.RequestURL );

                hwrp = (HttpWebResponse) hwrq.GetResponse();

 

                //Get file size info

                long L = hwrp.ContentLength;

                L = ((L == -1) || (L > 0x7fffffff)) ? ((long) 0x7fffffff) : L;

               

                _FileInfo.TotalSize = (int)L;

                _FileInfo.BlockLeftSize = _FileInfo.TotalSize;

 

                //Check whether this url is supported to be multi-threads download

                bool blnMulti = ( hwrp.Headers["Accept-Ranges"] != null

                    && hwrp.Headers["Accept-Ranges"] == "bytes");

                hwrp.Close();//Close response here

                return blnMulti;

            }

            catch (Exception e)

            {

                throw e;

            }

        }

 

        /// <summary>

        /// Update download thread info

        /// </summary>

        /// <param name="ThreadNO"></param>

        /// <param name="Position"></param>

        /// <param name="DownloadSize"></param>

        public void UpdateDownloadInfo( int ThreadNO, int Position, int DownloadSize )

        {

            if( ThreadNO >= dtThreadInfo.Rows.Count ) return;

 

            DataRow drThreadNO = dtThreadInfo.Rows[ThreadNO];

            drThreadNO["Position"] = Position + DownloadSize;

            drThreadNO["BlockLeftSize"] = Convert.ToInt32( drThreadNO["BlockLeftSize"].ToString() )

                - DownloadSize;

            drThreadNO.AcceptChanges();

 

            drFileInfo["BlockLeftSize"] = Convert.ToInt32( drFileInfo["BlockLeftSize"].ToString() )

                - DownloadSize;

        }

 

        /// <summary>

        /// Stop download threads

        /// </summary>

        public void Stop()

        {

            for( int i = 0; i < subThreads.Length; i++ )

                subThreads[i].Stop();

        }

 

        /// <summary>

        /// Thread download finished

        /// </summary>

        private void DownloadFinished()

        {

            // Some download thread finished

            nThreadFinishedCount++;

 

            if( nThreadFinishedCount == subThreads.Length )

            {

                // All download threads finished

                drFileInfo.Delete();

                //drFileInfo.AcceptChanges();

 

                Finished();

            }

        }

 

        public DataTable ThreadInfo

        {

            get{ return dtThreadInfo; }

        }

    }

 

}

 

不过对于这个类来说,我也只实现了两个线程同时下载,当初始化为3个以上线程的时候,就有等待线程出现,这一点和我用FlashGet看到现象一样,那么估计要支持多个线程,服务器需要做些设置,不过这些还需要进一步的验证。