欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#实现支持断点续传多线程下载客户端工具类

程序员文章站 2024-03-02 16:39:22
复制代码 代码如下: /* .net/c#: 实现支持断点续传多线程下载的 http web 客户端工具类 (c# diy httpwebclient) * reflect...
复制代码 代码如下:

/* .net/c#: 实现支持断点续传多线程下载的 http web 客户端工具类 (c# diy httpwebclient)
* reflector 了一下 system.net.webclient ,改写或增加了若干:
* download、upload 相关方法!
* download 相关改动较大!
* 增加了 datareceive、exceptionoccurrs 事件!
* 了解服务器端与客户端交互的 http 协议参阅:
* 使文件下载的自定义连接支持 flashget 的断点续传多线程链接下载! jsp/servlet 实现!
* http://blog.csdn.net/playyuer/archive/2004/08/02/58430.aspx
* 使文件下载的自定义连接支持 flashget 的断点续传多线程链接下载! c#/asp.net 实现!
* http://blog.csdn.net/playyuer/archive/2004/08/02/58281.aspx
*/
//2005-03-14 修订:
/* .net/c#: 实现支持断点续传多线程下载的工具类
* reflector 了一下 system.net.webclient ,改写或增加了若干:
* download、upload 相关方法!
* 增加了 datareceive、exceptionoccurrs 事件
*/
namespace microshaoft.utils
{
using system;
using system.io;
using system.net;
using system.text;
using system.security;
using system.threading;
using system.collections.specialized;
/// <summary>
/// 记录下载的字节位置
/// </summary>
public class downloadstate
{
private string _filename;
private string _attachmentname;
private int _position;
private string _requesturl;
private string _responseurl;
private int _length;
private byte[] _data;
public string filename
{
get
{
return _filename;
}
}
public int position
{
get
{
return _position;
}
}
public int length
{
get
{
return _length;
}
}
public string attachmentname
{
get
{
return _attachmentname;
}
}
public string requesturl
{
get
{
return _requesturl;
}
}
public string responseurl
{
get
{
return _responseurl;
}
}
public byte[] data
{
get
{
return _data;
}
}
internal downloadstate(string requesturl, string responseurl, string filename, string attachmentname, int position, int length, byte[] data)
{
this._filename = filename;
this._requesturl = requesturl;
this._responseurl = responseurl;
this._attachmentname = attachmentname;
this._position = position;
this._data = data;
this._length = length;
}
internal downloadstate(string requesturl, string responseurl, string filename, string attachmentname, int position, int length, threadcallbackhandler tch)
{
this._requesturl = requesturl;
this._responseurl = responseurl;
this._filename = filename;
this._attachmentname = attachmentname;
this._position = position;
this._length = length;
this._threadcallback = tch;
}
internal downloadstate(string requesturl, string responseurl, string filename, string attachmentname, int position, int length)
{
this._requesturl = requesturl;
this._responseurl = responseurl;
this._filename = filename;
this._attachmentname = attachmentname;
this._position = position;
this._length = length;
}
private threadcallbackhandler _threadcallback;
public httpwebclient httpwebclient
{
get
{
return this._hwc;
}
set
{
this._hwc = value;
}
}
internal thread thread
{
get
{
return _thread;
}
set
{
_thread = value;
}
}
private httpwebclient _hwc;
private thread _thread;
//
internal void startdownloadfilechunk()
{
if (this._threadcallback != null)
{
this._threadcallback(this._requesturl, this._filename, this._position, this._length);
this._hwc.onthreadprocess(this._thread);
}
}
}
//委托代理线程的所执行的方法签名一致
public delegate void threadcallbackhandler(string s, string s, int i, int i);
//异常处理动作
public enum exceptionactions
{
throw,
cancelall,
ignore,
retry
}
/// <summary>
/// 包含 exception 事件数据的类
/// </summary>
public class exceptioneventargs : system.eventargs
{
private system.exception _exception;
private exceptionactions _exceptionaction;
private downloadstate _downloadstate;
public downloadstate downloadstate
{
get
{
return _downloadstate;
}
}
public exception exception
{
get
{
return _exception;
}
}
public exceptionactions exceptionaction
{
get
{
return _exceptionaction;
}
set
{
_exceptionaction = value;
}
}
internal exceptioneventargs(system.exception e, downloadstate downloadstate)
{
this._exception = e;
this._downloadstate = downloadstate;
}
}
/// <summary>
/// 包含 download 事件数据的类
/// </summary>
public class downloadeventargs : system.eventargs
{
private downloadstate _downloadstate;
public downloadstate downloadstate
{
get
{
return _downloadstate;
}
}
public downloadeventargs(downloadstate downloadstate)
{
this._downloadstate = downloadstate;
}
}
public class threadprocesseventargs : system.eventargs
{
private thread _thread;
public thread thread
{
get
{
return this._thread;
}
}
public threadprocesseventargs(thread thread)
{
this._thread = thread;
}
}
/// <summary>
/// 支持断点续传多线程下载的类
/// </summary>
public class httpwebclient
{
private static object _synclockobject = new object();
public delegate void datareceiveeventhandler(httpwebclient sender, downloadeventargs e);
public event datareceiveeventhandler datareceive; //接收字节数据事件
public delegate void exceptioneventhandler(httpwebclient sender, exceptioneventargs e);
public event exceptioneventhandler exceptionoccurrs; //发生异常事件
public delegate void threadprocesseventhandler(httpwebclient sender, threadprocesseventargs e);
public event threadprocesseventhandler threadprocessend; //发生多线程处理完毕事件
private int _filelength; //下载文件的总大小
public int filelength
{
get
{
return _filelength;
}
}
/// <summary>
/// 分块下载文件
/// </summary>
/// <param name="address">url 地址</param>
/// <param name="filename">保存到本地的路径文件名</param>
/// <param name="chunkscount">块数,线程数</param>
public void downloadfile(string address, string filename, int chunkscount)
{
int p = 0; // position
int s = 0; // chunk size
string a = null;
httpwebrequest hwrq;
httpwebresponse hwrp = null;
try
{
hwrq = (httpwebrequest) webrequest.create(this.geturi(address));
hwrp = (httpwebresponse) hwrq.getresponse();
long l = hwrp.contentlength;
hwrq.credentials = this.m_credentials;
l = ((l == -1) || (l > 0x7fffffff)) ? ((long) 0x7fffffff) : l; //int32.maxvalue 该常数的值为 2,147,483,647; 即十六进制的 0x7fffffff
int l = (int) l;
this._filelength = l;
// 在本地预定空间(竟然在多线程下不用先预定空间)
// filestream sw = new filestream(filename, filemode.openorcreate, fileaccess.readwrite, fileshare.readwrite);
// sw.write(new byte[l], 0, l);
// sw.close();
// sw = null;
bool b = (hwrp.headers["accept-ranges"] != null & hwrp.headers["accept-ranges"] == "bytes");
a = hwrp.headers["content-disposition"]; //attachment
if (a != null)
{
a = a.substring(a.lastindexof("filename=") + 9);
}
else
{
a = filename;
}
int ss = s;
if (b)
{
s = l / chunkscount;
if (s < 2 * 64 * 1024) //块大小至少为 128 k 字节
{
s = 2 * 64 * 1024;
}
ss = s;
int i = 0;
while (l > s)
{
l -= s;
if (l < s)
{
s += l;
}
if (i++ > 0)
{
downloadstate x = new downloadstate(address, hwrp.responseuri.absolutepath, filename, a, p, s, new threadcallbackhandler(this.downloadfilechunk));
// 单线程下载
// x.startdownloadfilechunk();
x.httpwebclient = this;
//多线程下载
thread t = new thread(new threadstart(x.startdownloadfilechunk));
//this.onthreadprocess(t);
t.start();
}
p += s;
}
s = ss;
byte[] buffer = this.responseasbytes(address, hwrp, s, filename);
this.onthreadprocess(thread.currentthread);
// lock (_synclockobject)
// {
// this._bytes += buffer.length;
// }
}
}
catch (exception e)
{
exceptionactions ea = exceptionactions.throw;
if (this.exceptionoccurrs != null)
{
downloadstate x = new downloadstate(address, hwrp.responseuri.absolutepath, filename, a, p, s);
exceptioneventargs eea = new exceptioneventargs(e, x);
exceptionoccurrs(this, eea);
ea = eea.exceptionaction;
}
if (ea == exceptionactions.throw)
{
if (!(e is webexception) && !(e is securityexception))
{
throw new webexception("net_webclient", e);
}
throw;
}
}
}
internal void onthreadprocess(thread t)
{
if (threadprocessend != null)
{
threadprocesseventargs tpea = new threadprocesseventargs(t);
threadprocessend(this, tpea);
}
}
/// <summary>
/// 下载一个文件块,利用该方法可自行实现多线程断点续传
/// </summary>
/// <param name="address">url 地址</param>
/// <param name="filename">保存到本地的路径文件名</param>
/// <param name="length">块大小</param>
public void downloadfilechunk(string address, string filename, int fromposition, int length)
{
httpwebresponse hwrp = null;
string a = null;
try
{
//this._filename = filename;
httpwebrequest hwrq = (httpwebrequest) webrequest.create(this.geturi(address));
//hwrq.credentials = this.m_credentials;
hwrq.addrange(fromposition);
hwrp = (httpwebresponse) hwrq.getresponse();
a = hwrp.headers["content-disposition"]; //attachment
if (a != null)
{
a = a.substring(a.lastindexof("filename=") + 9);
}
else
{
a = filename;
}
byte[] buffer = this.responseasbytes(address, hwrp, length, filename);
// lock (_synclockobject)
// {
// this._bytes += buffer.length;
// }
}
catch (exception e)
{
exceptionactions ea = exceptionactions.throw;
if (this.exceptionoccurrs != null)
{
downloadstate x = new downloadstate(address, hwrp.responseuri.absolutepath, filename, a, fromposition, length);
exceptioneventargs eea = new exceptioneventargs(e, x);
exceptionoccurrs(this, eea);
ea = eea.exceptionaction;
}
if (ea == exceptionactions.throw)
{
if (!(e is webexception) && !(e is securityexception))
{
throw new webexception("net_webclient", e);
}
throw;
}
}
}
internal byte[] responseasbytes(string requesturl, webresponse response, long length, string filename)
{
string a = null; //attachmentname
int p = 0; //整个文件的位置指针
int num2 = 0;
try
{
a = response.headers["content-disposition"]; //attachment
if (a != null)
{
a = a.substring(a.lastindexof("filename=") + 9);
}
long num1 = length; //response.contentlength;
bool flag1 = false;
if (num1 == -1)
{
flag1 = true;
num1 = 0x10000; //64k
}
byte[] buffer1 = new byte[(int) num1];
int p = 0; //本块的位置指针
string s = response.headers["content-range"];
if (s != null)
{
s = s.replace("bytes ", "");
s = s.substring(0, s.indexof("-"));
p = convert.toint32(s);
}
int num3 = 0;
stream s = response.getresponsestream();
do
{
num2 = s.read(buffer1, num3, ((int) num1) - num3);
num3 += num2;
if (flag1 && (num3 == num1))
{
num1 += 0x10000;
byte[] buffer2 = new byte[(int) num1];
buffer.blockcopy(buffer1, 0, buffer2, 0, num3);
buffer1 = buffer2;
}
// lock (_synclockobject)
// {
// this._bytes += num2;
// }
if (num2 > 0)
{
if (this.datareceive != null)
{
byte[] buffer = new byte[num2];
buffer.blockcopy(buffer1, p, buffer, 0, buffer.length);
downloadstate dls = new downloadstate(requesturl, response.responseuri.absolutepath, filename, a, p, num2, buffer);
downloadeventargs dlea = new downloadeventargs(dls);
//触发事件
this.ondatareceive(dlea);
//system.threading.thread.sleep(100);
}
p += num2; //本块的位置指针
p += num2; //整个文件的位置指针
}
else
{
break;
}
}
while (num2 != 0);
s.close();
s = null;
if (flag1)
{
byte[] buffer3 = new byte[num3];
buffer.blockcopy(buffer1, 0, buffer3, 0, num3);
buffer1 = buffer3;
}
return buffer1;
}
catch (exception e)
{
exceptionactions ea = exceptionactions.throw;
if (this.exceptionoccurrs != null)
{
downloadstate x = new downloadstate(requesturl, response.responseuri.absolutepath, filename, a, p, num2);
exceptioneventargs eea = new exceptioneventargs(e, x);
exceptionoccurrs(this, eea);
ea = eea.exceptionaction;
}
if (ea == exceptionactions.throw)
{
if (!(e is webexception) && !(e is securityexception))
{
throw new webexception("net_webclient", e);
}
throw;
}
return null;
}
}
private void ondatareceive(downloadeventargs e)
{
//触发数据到达事件
datareceive(this, e);
}
public byte[] uploadfile(string address, string filename)
{
return this.uploadfile(address, "post", filename, "file");
}
public string uploadfileex(string address, string method, string filename, string fieldname)
{
return encoding.ascii.getstring(uploadfile(address, method, filename, fieldname));
}
public byte[] uploadfile(string address, string method, string filename, string fieldname)
{
byte[] buffer4;
filestream stream1 = null;
try
{
filename = path.getfullpath(filename);
string text1 = "---------------------" + datetime.now.ticks.tostring("x");
string text2 = "application/octet-stream";
stream1 = new filestream(filename, filemode.open, fileaccess.read);
webrequest request1 = webrequest.create(this.geturi(address));
request1.credentials = this.m_credentials;
request1.contenttype = "multipart/form-data; boundary=" + text1;
request1.method = method;
string[] textarray1 = new string[7] {"--", text1, "\r\ncontent-disposition: form-data; name=\"" + fieldname + "\"; filename=\"", path.getfilename(filename), "\"\r\ncontent-type: ", text2, "\r\n\r\n"};
string text3 = string.concat(textarray1);
byte[] buffer1 = encoding.utf8.getbytes(text3);
byte[] buffer2 = encoding.ascii.getbytes("\r\n--" + text1 + "\r\n");
long num1 = 0x7fffffffffffffff;
try
{
num1 = stream1.length;
request1.contentlength = (num1 + buffer1.length) + buffer2.length;
}
catch
{
}
byte[] buffer3 = new byte[math.min(0x2000, (int) num1)];
using (stream stream2 = request1.getrequeststream())
{
int num2;
stream2.write(buffer1, 0, buffer1.length);
do
{
num2 = stream1.read(buffer3, 0, buffer3.length);
if (num2 != 0)
{
stream2.write(buffer3, 0, num2);
}
}
while (num2 != 0);
stream2.write(buffer2, 0, buffer2.length);
}
stream1.close();
stream1 = null;
webresponse response1 = request1.getresponse();
buffer4 = this.responseasbytes(response1);
}
catch (exception exception1)
{
if (stream1 != null)
{
stream1.close();
stream1 = null;
}
if (!(exception1 is webexception) && !(exception1 is securityexception))
{
//throw new webexception(sr.getstring("net_webclient"), exception1);
throw new webexception("net_webclient", exception1);
}
throw;
}
return buffer4;
}
private byte[] responseasbytes(webresponse response)
{
int num2;
long num1 = response.contentlength;
bool flag1 = false;
if (num1 == -1)
{
flag1 = true;
num1 = 0x10000;
}
byte[] buffer1 = new byte[(int) num1];
stream stream1 = response.getresponsestream();
int num3 = 0;
do
{
num2 = stream1.read(buffer1, num3, ((int) num1) - num3);
num3 += num2;
if (flag1 && (num3 == num1))
{
num1 += 0x10000;
byte[] buffer2 = new byte[(int) num1];
buffer.blockcopy(buffer1, 0, buffer2, 0, num3);
buffer1 = buffer2;
}
}
while (num2 != 0);
stream1.close();
if (flag1)
{
byte[] buffer3 = new byte[num3];
buffer.blockcopy(buffer1, 0, buffer3, 0, num3);
buffer1 = buffer3;
}
return buffer1;
}
private namevaluecollection m_requestparameters;
private uri m_baseaddress;
private icredentials m_credentials = credentialcache.defaultcredentials;
public icredentials credentials
{
get
{
return this.m_credentials;
}
set
{
this.m_credentials = value;
}
}
public namevaluecollection querystring
{
get
{
if (this.m_requestparameters == null)
{
this.m_requestparameters = new namevaluecollection();
}
return this.m_requestparameters;
}
set
{
this.m_requestparameters = value;
}
}
public string baseaddress
{
get
{
if (this.m_baseaddress != null)
{
return this.m_baseaddress.tostring();
}
return string.empty;
}
set
{
if ((value == null) || (value.length == 0))
{
this.m_baseaddress = null;
}
else
{
try
{
this.m_baseaddress = new uri(value);
}
catch (exception exception1)
{
throw new argumentexception("value", exception1);
}
}
}
}
private uri geturi(string path)
{
uri uri1;
try
{
if (this.m_baseaddress != null)
{
uri1 = new uri(this.m_baseaddress, path);
}
else
{
uri1 = new uri(path);
}
if (this.m_requestparameters == null)
{
return uri1;
}
stringbuilder builder1 = new stringbuilder();
string text1 = string.empty;
for (int num1 = 0; num1 < this.m_requestparameters.count; num1++)
{
builder1.append(text1 + this.m_requestparameters.allkeys[num1] + "=" + this.m_requestparameters[num1]);
text1 = "&";
}
uribuilder builder2 = new uribuilder(uri1);
builder2.query = builder1.tostring();
uri1 = builder2.uri;
}
catch (uriformatexception)
{
uri1 = new uri(path.getfullpath(path));
}
return uri1;
}
}
}
/// <summary>
/// 测试类
/// </summary>
class apptest
{
int _k = 0;
int _k = 0;
static void main()
{
apptest a = new apptest();
microshaoft.utils.httpwebclient x = new microshaoft.utils.httpwebclient();
a._k = 10;
//订阅 datareceive 事件
x.datareceive += new microshaoft.utils.httpwebclient.datareceiveeventhandler(a.x_datareceive);
//订阅 exceptionoccurrs 事件
x.exceptionoccurrs += new microshaoft.utils.httpwebclient.exceptioneventhandler(a.x_exceptionoccurrs);
x.threadprocessend += new microshaoft.utils.httpwebclient.threadprocesseventhandler(a.x_threadprocessend);
string f = "http://localhost/download/phpmyadmin-2.6.1-pl2.zip";
f = "http://down6.flashget.com/flashget182cn.exe";
a._f = f;
string f = f.substring(f.lastindexof("/") + 1);
//(new system.threading.thread(new system.threading.threadstart(new threadprocessstate(f, @"e:\temp\" + f, 10, x).startthreadprocess))).start();
x.downloadfile(f, @"d:\temp\" + f, a._k);
// x.downloadfilechunk(f, @"e:\temp\" + f,15,34556);
system.console.readline();
// string uploadfile = "e:\\test_local.rar";
// string str = x.uploadfileex("http://localhost/phpmyadmin/uploadaction.php", "post", uploadfile, "file1");
// system.console.writeline(str);
// system.console.readline();
}
string bs = ""; //用于记录上次的位数
bool b = false;
private int i = 0;
private static object _synclockobject = new object();
string _f;
string _f;
private void x_datareceive(microshaoft.utils.httpwebclient sender, microshaoft.utils.downloadeventargs e)
{
if (!this.b)
{
lock (_synclockobject)
{
if (!this.b)
{
system.console.write(system.datetime.now.tostring() + " 已接收数据: ");
//system.console.write( system.datetime.now.tostring() + " 已接收数据: ");
this.b = true;
}
}
}
string f = e.downloadstate.filename;
if (e.downloadstate.attachmentname != null)
f = system.io.path.getdirectoryname(f) + @"\" + e.downloadstate.attachmentname;
this._f = f;
using (system.io.filestream sw = new system.io.filestream(f, system.io.filemode.openorcreate, system.io.fileaccess.readwrite, system.io.fileshare.readwrite))
{
sw.position = e.downloadstate.position;
sw.write(e.downloadstate.data, 0, e.downloadstate.data.length);
sw.close();
}
string s = system.datetime.now.tostring();
lock (_synclockobject)
{
this.i += e.downloadstate.data.length;
system.console.write(bs + "\b\b\b\b\b\b\b\b\b\b"+ i + " / " + sender.filelength + " 字节数据 " + s);
//system.console.write(bs + i + " 字节数据 " + s);
this.bs = new string('\b', digits(i) + 3 + digits(sender.filelength) + s.length);
}
}
int digits(int n) //数字所占位数
{
n = system.math.abs(n);
n = n / 10;
int i = 1;
while (n > 0)
{
n = n / 10;
i++;
}
return i;
}
private void x_exceptionoccurrs(microshaoft.utils.httpwebclient sender, microshaoft.utils.exceptioneventargs e)
{
system.console.writeline(e.exception.message);
//发生异常重新下载相当于断点续传,你可以自己自行选择处理方式
microshaoft.utils.httpwebclient x = new microshaoft.utils.httpwebclient();
x.downloadfilechunk(this._f, this._f, e.downloadstate.position, e.downloadstate.length);
e.exceptionaction = microshaoft.utils.exceptionactions.ignore;
}
private void x_threadprocessend(microshaoft.utils.httpwebclient sender, microshaoft.utils.threadprocesseventargs e)
{
//if (e.thread.threadstate == system.threading.threadstate.stopped)
if (this._k ++ == this._k - 1)
system.console.writeline("\nend");
}
}