上篇文章《多线程断点续传研究(1) 》写完,由于整体思路是正确的,但是没有真正形成多线程下载,所以对本身的代码进行关键点的检查,尤其在一些操作web请求的地方,看看是否有什么问题,最后发现显示的关闭HttpWebResponse对象,能稍微有所改进。
那么修改后的类,大致代码如下:
//--------------------------- 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看到现象一样,那么估计要支持多个线程,服务器需要做些设置,不过这些还需要进一步的验证。
作者:knight94