/* .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
*/
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;
//
internal void startdownloadfilechunk()
{
if (this._threadcallback != null)
{
this._threadcallback(this._requesturl, this._filename, this._position, this._length);
}
}
}
//委托代理线程的所执行的方法签名一致
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;
}
}
/// <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; //发生异常事件
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();
//多线程下载
//thread t =
new thread(new threadstart(x.startdownloadfilechunk)).start();
//t.start();
}
p += s;
}
s = ss;
byte[] buffer = this.responseasbytes(address, hwrp, s, 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, 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;
}
}
}
/// <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
{
static void main()
{
apptest a = new apptest();
microshaoft.utils.httpwebclient x = new microshaoft.utils.httpwebclient();
//订阅 datareceive 事件
x.datareceive += new microshaoft.utils.httpwebclient.datareceiveeventhandler(a.x_datareceive);
//订阅 exceptionoccurrs 事件
x.exceptionoccurrs += new microshaoft.utils.httpwebclient.exceptioneventhandler(a.x_exceptionoccurrs);
string f = "http://localhost/download/phpmyadmin-2.6.1-pl2.zip";
a._f = f;
f = "http://localhost/download/jdk-1_5_0_01-windows-i586-p.aa.exe";
//f = "http://localhost/download/resharper1.5.exe";
//f = "http://localhost/mywebapplications/webapplication7/webform1.aspx";
//f = "http://localhost:1080/test/download.jsp";
//f = "http://localhost/download/webcast20050125_ppt.zip";
//f = "http://www.morequick.com/greenbrowsergb.zip";
//f = "http://localhost/download/test_local.rar";
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, @"e:/temp/temp/" + f, 10);
// x.downloadfilechunk(f, @"e:/temp/" + f,15,34556);
system.console.readline();
// upload 测试
// 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;
}
}
/*
* 用于 upload 测试的 action php:
http://localhost/phpmyadmin/uploadaction.php:
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<html>
<head>
<title> new document </title>
</head>
<body>
<?php
print_r($_request);
$uploaddir = '';
$uploadfile = $uploaddir . $_files['file1']['name'];
print "<pre>";
if (move_uploaded_file($_files['file1']['tmp_name'], $uploadfile))
{
print "file is valid, and was successfully uploaded. ";
}
else
{
print "possible file upload attack! here's some debugging info:/n";
print_r($_files);
}
print "</pre>";
?>
</body>
</html>
*/
国内最大的酷站演示中心!