c# Http下载器

                                                                                         c# Http下载器

一、简述

     记--简单的http下载器,支持断点续传、计算下载网速、剩余时间、用时、平均网速。

       例子打包:https://wwa.lanzous.com/iKQzue5h8xc

二、效果

c# Http下载器_第1张图片

三、工程结构

c# Http下载器_第2张图片

四、源文件

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

c# Http下载器_第3张图片

你可能感兴趣的:(C#)