c# Http下载器
一、简述
记--简单的http下载器,支持断点续传、计算下载网速、剩余时间、用时、平均网速。
例子打包:https://wwa.lanzous.com/iKQzue5h8xc
二、效果
三、工程结构
四、源文件
HttpHelper.cs文件
/*
* 类名:HttpHelper
* 描述:Http下载帮助类
* 功能:
* 1. 断点续传
* 2. 可暂停下载
* 3. 计算下载速度、下载剩余时间、下载用时
* 作者:Liang
* 版本:V1.0.1
* 修改时间:2020-06-29
*/
using System;
using System.IO;
using System.Net;
namespace HttpDownload
{
public enum eDownloadSta
{
STA_NUL,//没有下载任务
STA_START,//开始下载
STA_ING,//下载中
STA_PAUSE,//暂停下载
STA_CONTINUE,//继续下载
STA_STOP,//停止下载
STA_FINISH,//下载完成
STA_ERR//下载出错
}
class HttpHelper
{
//定时器间隔 与下载速度的计算有关
const int TIMER_TICK = 1000;//每秒计算一次网速
//接收数据的缓冲区
const int BUF_SIZE = 8*1024;
//最大读取错误数,达到之后会出下载
const int READ_ERR_MAX_CNT = 3;
///
/// 下载数据更新
///
/// 下载文件总大小
/// 已下载文件大小
/// 下载速度
/// 剩余下载时间
public delegate void delegateDownProcess(double curSize, double speed, uint mRemainTime, uint spanTime, eDownloadSta sta);
public delegateDownProcess process;
//文件大小
public delegate void delegateFileSize(double fileSize);
public delegateFileSize downFileSize;
//定时器,定时将下载进度等信息发送给UI线程
public System.Timers.Timer mTimer = null;
private double mFileSize;//文件大小
private double mCurReadSize;//已下载大小
private double mOneSecReadSize;//1秒下载大小
private double mSpeed;//下载速度
private uint mRemainTime;//剩余下载时间
private uint mTotalTimeSec = 0;//下载总时间 单位:秒
private eDownloadSta mCurDownloadSta;//当前下载状态
string mSaveFileName = "";//文件保存名称
string mHttpUrl = "";//http下载路径
public HttpHelper()
{ }
//务必使用此函数初始化,因为定时器需要再主类创建
public HttpHelper(System.Timers.Timer _timer, delegateDownProcess processShow, delegateFileSize downloadFileSize)
{
if (mTimer == null)
{
mTimer = _timer;
mTimer.Interval = TIMER_TICK;
mTimer.Stop();
mTimer.Elapsed += new System.Timers.ElapsedEventHandler(tickEventHandler); //到达时间的时候执行事件;
mTimer.AutoReset = true; //设置重复执行(true);
mTimer.Enabled = true; //设置执行tickEventHandler事件
this.process += processShow;
this.downFileSize += downloadFileSize;
}
init();
mCurDownloadSta = eDownloadSta.STA_FINISH;
}
public void init()
{
mFileSize = 0.0;
mCurReadSize = 0.0;
mOneSecReadSize = 0.0;
mSpeed = 0.0;
mRemainTime = 0;
mTotalTimeSec = 0;
}
///
///格式化文件大小,格式化为合适的单位
///
/// 字节大小
///
public static string formateSize(double size)
{
string[] units = new string[] { "B", "KB", "MB", "GB", "TB", "PB" };
double mod = 1024.0;
int i = 0;
while (size >= mod)
{
size /= mod;
i++;
}
return size.ToString("f2") + units[i];
}
///
/// 将秒数格式化为 时分秒格式
///
///
///
public static string formatTime(uint second)
{
//return new DateTime(1970, 01, 01, 00, 00, 00).AddSeconds(second).ToString("HH:mm:ss");
uint hour = second / 3600;
uint tmp1 = second - hour * 3600;
uint min = tmp1 / 60;
uint sec = tmp1 - min * 60;
return string.Format("{0}:{1}:{2}", hour.ToString("00"), min.ToString("00"), sec.ToString("00"));
}
///
/// 下载文件(同步) 支持断点续传
///
public int dowLoadFile()
{
int errCnt = 0;
//打开上次下载的文件或新建文件
long startPos = 0;
FileStream fs = null;
if (File.Exists(mSaveFileName))//文件已经存在就继续下载
{
try
{
fs = File.OpenWrite(mSaveFileName);
if (null == fs)
{
Console.WriteLine("open file failed:" + mSaveFileName);
return -1;
}
startPos = fs.Length;
}
catch(Exception e)
{
Console.WriteLine("open file err:"+e.Message);
if (null != fs)
{
fs.Close();
return -2;
}
}
}
else//新文件
{
fs = new FileStream(mSaveFileName, FileMode.Create);
Console.WriteLine("创建文件");
startPos = 0;
}
/**获取文件大小**/
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(mHttpUrl);
if (null == request)
{
return -3;
}
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36";
//request.KeepAlive = true;//保持连接
//设置Range值,请求指定位置开始的数据,实现断点续传
request.AddRange(startPos);//request.AddRange(startPos, endPos)获取指定范围数据,续传跟重传(中间某部分)可使用该函数
WebResponse respone = null;
//注:断点续传必须在request后、并设置AddRange后第一次获取的Response才是正确的数据流
try
{
respone = request.GetResponse();
if (null == respone)
{
request.Abort();
fs.Close();
return -4;
}
}
catch (Exception e)
{
Console.WriteLine("getResponse err:"+e.Message);
fs.Close();
return -4;
}
mFileSize = Convert.ToDouble(respone.ContentLength) + startPos;
//发送文件大小
downFileSize?.Invoke(mFileSize);
if (mFileSize < 0)
{
Console.WriteLine("获取文件大小失败");
return -5;
}
//文件错误,清空文件
if (mFileSize < startPos)
{
fs.SetLength(0);//截断文件,相当于是清空文件内容
Console.WriteLine("文件错误,清空后重新下载");
}
mCurReadSize = startPos;
//文件已下载
if (mCurReadSize >= mFileSize)
{
fs.Close();
respone.Close();
request.Abort();
Console.WriteLine("文件已经下载");
return 0;
}
fs.Seek(startPos, SeekOrigin.Begin); //移动文件流中的当前指针
//Console.WriteLine("startPos:{0}", startPos);
Stream responseStream = null;
byte[] dataBuf = new byte[BUF_SIZE];
//打开网络连接
try
{
//向服务器请求,获得服务器回应数据流
responseStream = respone.GetResponseStream();
if (null == responseStream)
{
fs.Close();
respone.Close();
request.Abort();
return -6;
}
int nReadSize = 0;
do
{
//读取数据
nReadSize = responseStream.Read(dataBuf, 0, BUF_SIZE);
if (nReadSize > 0)
{
fs.Write(dataBuf, 0, nReadSize);
//此处应该判断是否写入成功
//已下载大小
mCurReadSize += nReadSize;
mOneSecReadSize += nReadSize;
}
else
{
errCnt++;
if (errCnt > READ_ERR_MAX_CNT)
{
Console.WriteLine("下载出错,退出下载");
break;
}
}
if (mCurDownloadSta == eDownloadSta.STA_STOP)
{
Console.WriteLine("停止下载");
break;
}
} while (mCurReadSize
/// 开始下载
///
/// 下载url
/// 保存文件名
public void download(string url, string fileName)
{
if(mCurDownloadSta == eDownloadSta.STA_ING)
{
Console.WriteLine("url:{0} is downloading...", url);
return;
}
if (mHttpUrl != url)//新的下载文件要重新计数
{
init();
}
else
{
mFileSize = 0.0;
mCurReadSize = 0.0;
mOneSecReadSize = 0.0;
mRemainTime = 0;
}
mHttpUrl = url;
mSaveFileName = fileName;
if (fileName.Length<1)
{
mSaveFileName = Directory.GetCurrentDirectory() + Path.GetFileName(url);
}
mCurDownloadSta = eDownloadSta.STA_START;
//Task.Run(() =>
//{
//});
mTimer.Start();
mCurDownloadSta = eDownloadSta.STA_ING;
Console.WriteLine("start download:");
int ret = -1;
try
{
ret = dowLoadFile();
}
catch(Exception e)
{
ret = -100;
Console.WriteLine("dowload err, err:"+e.Message);
}
mCurDownloadSta = eDownloadSta.STA_FINISH;
mTimer.Stop();
if (ret < 0)
{
process?.Invoke(mFileSize, mFileSize, 0, 0, eDownloadSta.STA_ERR);
}
else if (ret == 0)
{
process?.Invoke(mFileSize, mFileSize, 0, 0, eDownloadSta.STA_FINISH);
}
else
{
process?.Invoke(mCurReadSize, mSpeed, mRemainTime, mTotalTimeSec, eDownloadSta.STA_FINISH);
}
}
///
/// 定时器方法 定时将下载进度和速度和剩余时间发送出去
///
///
///
private void tickEventHandler(object sender, EventArgs e)
{
if (mCurDownloadSta == eDownloadSta.STA_ING)
{
mTotalTimeSec++;
//下载速度 1秒内下载大小/1秒
mSpeed = mOneSecReadSize;
mOneSecReadSize = 0;
//剩余时间 剩余大小/速度 单位:秒
if (mSpeed != 0)
{
mRemainTime = (uint)((mFileSize - mCurReadSize) / mSpeed);
}
else
{
mRemainTime = 0;
}
process?.Invoke(mCurReadSize, mSpeed, mRemainTime, mTotalTimeSec, eDownloadSta.STA_ING);
}
}
}
}
Form1.cs文件
using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;
namespace HttpDownload
{
public partial class Form1 : Form
{
private string mSaveFileName = "";//下载文件的保存文件名
string curPath = "";//应用当前目录
HttpHelper mHttpHelper = null;
public delegate void DelegateProcessShow(double curSize, double speed, uint remainTime, uint spanTime, eDownloadSta sta);
public delegate void DelegateSetFileSize(double val);
private double mDownloadFileSize = 0.0;//文件大小
private double mTotalSpd = 0.0;//用于计算平均速度
uint mTotalSpdCnt = 0;//用于计算平均速度
public Form1()
{
InitializeComponent();
//应用当前目录
curPath = AppDomain.CurrentDomain.BaseDirectory.ToString();
//初始化Http下载对象
initHttpManager();
}
//初始化Http下载对象
private void initHttpManager()
{
if (null == mHttpHelper)
{
mHttpHelper = new HttpHelper(new System.Timers.Timer(), processShow, downloadFileSize);
}
}
///
/// 更新下载进度
///
/// 文件总大小
/// 已下载
/// 进度
/// 速度
/// 剩余时间
/// 消息
public void processShow(double curSize, double speed, uint remainTime, uint spanTime, eDownloadSta sta)
{
if (this.InvokeRequired)
{
DelegateProcessShow delegateprocShow = new DelegateProcessShow(processShow);
this.Invoke(delegateprocShow, new object[] { curSize, speed, remainTime, spanTime, sta});
}
else
{
//Console.WriteLine("curSize:{0}, mDownloadFileSize:{1}", curSize, mDownloadFileSize);
if (mDownloadFileSize < 0 || sta == eDownloadSta.STA_ERR)
{
MessageBox.Show("下载出错!请删除"+ mSaveFileName + ",并重新尝试!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
mHttpHelper.downloadStop();
buttonDownload.Text = "下载";
textBoxHttpUrl.Enabled = true;
}
else if (mDownloadFileSize > 0)
{
labelDownSize.Text = "已下载:" + HttpHelper.formateSize(curSize);
int proc = 0;
mTotalSpdCnt++;
mTotalSpd += speed;
if (curSize < mDownloadFileSize)
{
proc = (int)((curSize / mDownloadFileSize) * 100);
}
else if (curSize == mDownloadFileSize)
{
proc = progressBarDownloadFile.Maximum;
}
else
{
mHttpHelper.downloadStop();
buttonDownload.Text = "下载";
textBoxHttpUrl.Enabled = true;
MessageBox.Show("文件续传出错!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
this.labelProcess.Text = string.Format("{0}%", proc);
progressBarDownloadFile.Value = proc;
this.labelDownSpd.Text = string.Format("速度:{0}/s", HttpHelper.formateSize(speed));
this.labelDownNeedTime.Text = string.Format("剩余时间:{0}", HttpHelper.formatTime(remainTime));
this.labelSpanTime.Text = string.Format("用时:{0}", HttpHelper.formatTime(spanTime));
if (proc == progressBarDownloadFile.Maximum)
{
mHttpHelper.downloadStop();
buttonDownload.Text = "下载";
textBoxHttpUrl.Enabled = true;
this.labelProcess.Text = "100%";
progressBarDownloadFile.Value = proc;
labelAvgSpd.Visible = true;
labelAvgSpd.Text = string.Format("均速:{0}/s", HttpHelper.formateSize((mTotalSpd / mTotalSpdCnt)));
//Console.WriteLine("finish:" + DateTime.Now.ToString("yyyyMMdd-HHmmss"));
MessageBox.Show("存放路径:" + curPath + mSaveFileName, "下载完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
}
//下载文件大小
void downloadFileSize(double fileSize)
{
if (this.InvokeRequired)
{
DelegateSetFileSize delegateSetFileSize = new DelegateSetFileSize(downloadFileSize);
this.Invoke(delegateSetFileSize, new object[] { fileSize });
}
else
{
if (fileSize > 0)
{
this.labelFileSizeTotal.Text = "文件大小:" + HttpHelper.formateSize(fileSize);
mDownloadFileSize = fileSize;
}
else if (fileSize < 0)
{
MessageBox.Show("获取文件长度出错!", "错误");
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
//测试使用,这是tx软件中心的qq最新的下载链接,https://dl.softmgr.qq.com/original/im/QQ9.3.5.27030.exe
//如果链接失效请自行获取或再找一个链接
textBoxHttpUrl.Text = "https://dl.softmgr.qq.com/original/im/QQ9.3.5.27030.exe"; //"请输入http下载地址";
}
//下载
private void download(string httpUrl, string saveFileName)
{
mHttpHelper.download(httpUrl, mSaveFileName);
}
private void buttonDownload_Click(object sender, EventArgs e)
{
if (buttonDownload.Text == "下载")
{
//获取http下载路径
string httpUrl = textBoxHttpUrl.Text.Trim();
if (!httpUrl.StartsWith("http://") && !httpUrl.StartsWith("https://"))
{
MessageBox.Show("请输入正确的http下载链接!", "提示");
textBoxHttpUrl.Focus();//url地址栏获取焦点
return;
}
mSaveFileName = Application.StartupPath + "\\" + Path.GetFileName(httpUrl);
progressBarDownloadFile.Value = 0;
buttonDownload.Text = "停止";
labelProcess.Text = "0%";
mTotalSpd = 0.0;
mTotalSpdCnt = 0;
labelAvgSpd.Visible = false;
textBoxHttpUrl.Enabled = false;
//Console.WriteLine("start:"+DateTime.Now.ToString("yyyyMMdd-HHmmss"));
new Thread(() => download(httpUrl, mSaveFileName)).Start(); //开启下载线程
}
else//停止
{
buttonDownload.Text = "下载";
textBoxHttpUrl.Enabled = true;
mHttpHelper.downloadStop();
}
}
}
}
五、待完善
5.1 待完善细节
5.2 多线程下载
5.3 模拟浏览器下载非直链、网盘文件
六、附
例子中的http链接时是当时QQ新版本下载地址:https://dl.softmgr.qq.com/original/im/QQ9.3.5.27030.exe