zoukankan      html  css  js  c++  java
  • .net c# 文件分片/断点续传之下载--客户端

    断点续传客户端实现主要参考了以下文章:

    https://blog.csdn.net/binyao02123202/article/details/76599949

    客户端实现续传的主要是一下几点

    1.客户端的下载请求要包含“Range”头部

    2.客户端通过 response 回来的头部判断是否包含“Content-Range”,“Accept-Ranges”来确认服务端是否支持断点续传,如果支持则分片取数据,否则读取整个流。

    客户端的基本实现参照 文章前面提到的参考的文章即可,本文就不赘述了,秉着学习的态度,博主将这个客户端实现进行了功能的完善,实现了对下载进行可配置。实现了 暂停下载,重复下载,继续下载等封装,理论上还支持退出重新打开继续下载(需对配置信息进行保存,另外,之所以说理论,是因为本文并未实现这个配置的保存)。

    直接看代码:

    1.TaskInfo  主要是记录子线程信息,如果是支持断点续传的话,就会开多线程进行下载,每个线程取不同的片,甚至可以是不同来源的片。

        public class TaskInfo
        {
            /// <summary>
            /// 请求方法
            /// </summary>
            public string method { get; set; }
            public string downloadUrl { get; set; }
            public string filePath { get; set; }
            /// <summary>
            /// 分片起点
            /// </summary>
            public long fromIndex { get; set; }
            /// <summary>
            /// 分片终点
            /// </summary>
            public long toIndex { get; set; }
            /// <summary>
            /// 分片的总大小
            /// </summary>
            public long count { get { return this.toIndex - this.fromIndex + 1; } }
        }

    2.DownloadService 根据 TaskInfo 实现线程初始化,机一个线程任务

        public class DownloadService
        {
            private string downloadUrl = "";//文件下载地址
            private string filePath = "";//文件保存路径
            private string method = "";//方法
            private long fromIndex = 0;//开始下载的位置
            private long toIndex = 0;//结束下载的位置
            private long count = 0;//总大小
            private long size = 524288;//每次下载大小 512kb
            private bool isRun = false;//是否正在进行
     
     
            public bool isFinish { get; private set; } = false;//是否已下载完成
            public bool isStopped { get; private set; } = true;//是否已停止
     
     
            public event Action OnStart;
            public event Action OnDownload;
            public event Action OnFinsh;
     
            public long GetDownloadedCount()
            {
                return this.count - this.toIndex + this.fromIndex - 1;
            }
     
            public void Stop()
            {
                this.isRun = false;
            }
            public bool Start(TaskInfo info,bool isReStart)
            {
                this.downloadUrl = info.downloadUrl;
                this.fromIndex = info.fromIndex;
                this.toIndex = info.toIndex;
                this.method = info.method;
                this.filePath = info.filePath;
                this.count = info.count;
                this.isStopped = false;
                if (File.Exists(this.filePath))
                {
                    if(isReStart)
                    {
                        File.Delete(this.filePath);
                        File.Create(this.filePath).Close();
                    }
                }
                else
                {
                    File.Create(this.filePath).Close();
                }
                using (var file = File.Open(this.filePath, FileMode.Open))
                {
                    this.fromIndex = info.fromIndex+file.Length;
                }
                if(this.fromIndex>=this.toIndex)
                {
                    OnFineshHandler();
                    this.isFinish = true;
                    this.isStopped = true;
                    return false;
                }
                OnStartHandler();
                this.isRun = true;
                new Action(() =>
                {
                    WebResponse rsp;
                    while (this.fromIndex < this.toIndex && isRun)
                    {
                        long to;
                        if (this.fromIndex + this.size >= this.toIndex - 1)
                            to = this.toIndex - 1;
                        else
                            to = this.fromIndex + size;
                        using (rsp = HttpHelper.Download(this.downloadUrl, this.fromIndex, to, this.method))
                        {
                            Save(this.filePath, rsp.GetResponseStream());
                        }
                    }
                    if (!this.isRun) this.isStopped = true;
                    if (this.fromIndex >= this.toIndex)
                    {
                        this.isFinish = true;
                        this.isStopped = true;
                        OnFineshHandler(); 
                    }
     
                }).BeginInvoke(null, null);
                return true;
            }
     
            private void Save(string filePath, Stream stream)
            {
                try
                {
                    using (var writer = File.Open(filePath, FileMode.Append))
                    {
                        using (stream)
                        {
                            var repeatTimes = 0;
                            byte[] buffer = new byte[1024];
                            var length = 0;
                            while ((length = stream.Read(buffer, 0, buffer.Length)) > 0 && this.isRun)
                            {
                                writer.Write(buffer, 0, length);
                                this.fromIndex += length;
                                if (repeatTimes % 5 == 0)
                                {
                                    OnDownloadHandler();
                                }
                                repeatTimes++;
                            }
                        }
                    }
                    OnDownloadHandler();
                }
                catch (Exception)
                {
                    //异常也不影响
                }
            }
     
            private void OnStartHandler()
            {
                new Action(() =>
                {
                    this.OnStart?.Invoke();
                }).BeginInvoke(null, null);
            }
            private void OnFineshHandler()
            {
                new Action(() =>
                {
                    this.OnFinsh?.Invoke();
                    this.OnDownload?.Invoke();
                }).BeginInvoke(null, null);
            }
            private void OnDownloadHandler()
            {
                new Action(() =>
                {
                    this.OnDownload?.Invoke();
                }).BeginInvoke(null, null);
            }
        }

    3.DownloadInfo 保存着下载信息,在初始化完成后,可保存该类的对象信息,以实现退出重进下载

        public class DownloadInfo
        {
            /// <summary>
            /// 子线程数量
            /// </summary>
            public int taskCount { get; set; } = 1;
            /// <summary>
            /// 缓存名,临时保存的文件名
            /// </summary>
            public string tempFileName { get; set; }
            /// <summary>
            /// 是否是新任务,如果不是新任务则通过配置去分配线程
            /// 一开始要设为true,在初始化完成后会被设为true,此时可以对这个 DownloadInfo 进行序列化后保存,进而实现退出程序加载配置继续下载。
            /// </summary>
            public bool isNewTask { get; set; } = true;
            /// <summary>
            /// 是否重新下载
            /// </summary>
            public bool isReStart { get; set; } = false;
            /// <summary>
            /// 任务总大小
            /// </summary>
            public long count { get; set; }
            /// <summary>
            /// 保存的目录
            /// </summary>
            public string saveDir { get; set; }
            /// <summary>
            /// 请求方法
            /// </summary>
            public string method { get; set; } = "get";
            public string fileName { get; set; }
            /// <summary>
            /// 下载地址,
            /// 这里是列表形式,如果同一个文件有不同来源则可以通过不同来源取数据
            /// 来源的有消息需另外判断
            /// </summary>
            public List<string> downloadUrlList { get; set; }
            /// <summary>
            /// 是否支持断点续传
            /// 在任务开始后,如果需要暂停,应先通过这个判断是否支持
            /// 默认设为false
            /// </summary>
            public bool IsSupportMultiThreading { get; set; } = false;
            /// <summary>
            /// 线程任务列表
            /// </summary>
            public List<TaskInfo> TaskInfoList { get; set; }
     
        }

    4.DownloadManager 一个下载任务的管理,实现了 暂停,继续,重新下载,以及下载信息初始化等

        public class DownloadManager
        {
            private long fromIndex = 0;//开始下载的位置
            private bool isRun = false;//是否正在进行
            private DownloadInfo dlInfo;
     
            private List<DownloadService> dls = new List<DownloadService>();
     
            public event Action OnStart;
            public event Action OnStop;
            public event Action<long,long> OnDownload;
            public event Action OnFinsh;
     
            public DownloadManager(DownloadInfo dlInfo)
            {
                this.dlInfo = dlInfo;
            }
            public void Stop()
            {
                this.isRun = false;
                dls.ForEach(dl => dl.Stop());
                OnStopHandler();
            }
     
            public void Start()
            {
                this.dlInfo.isReStart = false;
                WorkStart();
            }
            public void ReStart()
            {
                this.dlInfo.isReStart = true;
                WorkStart();
            }
     
            private void WorkStart()
            {
                new Action(() =>
                {
                    if (dlInfo.isReStart)
                    {
                        this.Stop();
                    }
     
                    while (dls.Where(dl => !dl.isStopped).Count() > 0)
                    {
                        if (dlInfo.isReStart) Thread.Sleep(100);
                        else return;
                    }
     
                    this.isRun = true;
                    OnStartHandler();
                    //首次任务或者不支持断点续传的进入
                    if (dlInfo.isNewTask||(!dlInfo.isNewTask&&!dlInfo.IsSupportMultiThreading))
                    {
                        //第一次请求获取一小块数据,根据返回的情况判断是否支持断点续传
                        using (var rsp = HttpHelper.Download(dlInfo.downloadUrlList[0], 0, 0, dlInfo.method))
                        {
     
                            //获取文件名,如果包含附件名称则取下附件,否则从url获取名称
                            var Disposition = rsp.Headers["Content-Disposition"];
                            if (Disposition != null) dlInfo.fileName = Disposition.Split('=')[1];
                            else dlInfo.fileName = Path.GetFileName(rsp.ResponseUri.AbsolutePath);
     
                            //默认给流总数
                            dlInfo.count = rsp.ContentLength;
                            //尝试获取 Content-Range 头部,不为空说明支持断点续传
                            var contentRange = rsp.Headers["Content-Range"];
                            if (contentRange != null)
                            {
                                //支持断点续传的话,就取range 这里的总数
                                dlInfo.count = long.Parse(rsp.Headers["Content-Range"]?.Split('/')?[1]);
                                dlInfo.IsSupportMultiThreading = true;
     
                                //生成一个临时文件名
                                var tempFileName = Convert.ToBase64String(Encoding.UTF8.GetBytes(dlInfo.fileName)).ToUpper();
                                tempFileName = tempFileName.Length > 32 ? tempFileName.Substring(0, 32) : tempFileName;
                                dlInfo.tempFileName = tempFileName + DateTime.Now.ToString("yyyyMMddHHmmssfff");
                                ///创建线程信息
                                ///
                                GetTaskInfo(dlInfo);
     
                            }
                            else
                            {
                                //不支持断点续传则一开始就直接读完整流
                                Save(GetRealFileName(dlInfo), rsp.GetResponseStream());
                                OnFineshHandler();
                            }
                        }
                        dlInfo.isNewTask = false;
                    }
                    //如果支持断点续传采用这个
                    if(dlInfo.IsSupportMultiThreading)
                    {
                        StartTask(dlInfo);
     
                        //等待合并
                        while (this.dls.Where(td => !td.isFinish).Count() > 0 && this.isRun)
                        {
                            Thread.Sleep(100);
                        }
                        if ((this.dls.Where(td => !td.isFinish).Count() == 0))
                        {
     
                            CombineFiles(dlInfo);
                            OnFineshHandler();
                        }
                    }
                    
                }).BeginInvoke(null, null);
            }
            private void CombineFiles(DownloadInfo dlInfo)
            {
                string realFilePath = GetRealFileName(dlInfo);
     
                //合并数据
                byte[] buffer = new Byte[2048];
                int length = 0;
                using (var fileStream = File.Open(realFilePath, FileMode.CreateNew))
                {
                    for (int i = 0; i < dlInfo.TaskInfoList.Count; i++)
                    {
                        var tempFile = dlInfo.TaskInfoList[i].filePath;
                        using (var tempStream = File.Open(tempFile, FileMode.Open))
                        {
                            while ((length = tempStream.Read(buffer, 0, buffer.Length)) > 0)
                            {
                                fileStream.Write(buffer, 0, length);
                            }
                            tempStream.Flush();
                        }
                        //File.Delete(tempFile);
                    }
                }
            }
     
            private static string GetRealFileName(DownloadInfo dlInfo)
            {
                //创建正式文件名,如果已存在则加数字序号创建,避免覆盖
                var fileIndex = 0;
                var realFilePath = Path.Combine(dlInfo.saveDir, dlInfo.fileName);
                while (File.Exists(realFilePath))
                {
                    realFilePath = Path.Combine(dlInfo.saveDir, string.Format("{0}_{1}", fileIndex++, dlInfo.fileName));
                }
     
                return realFilePath;
            }
     
            private void StartTask(DownloadInfo dlInfo)
            {
                this.dls = new List<DownloadService>();
                if (dlInfo.TaskInfoList != null)
                {
                    foreach (var item in dlInfo.TaskInfoList)
                    {
                        var dl = new DownloadService();
                        dl.OnDownload += OnDownloadHandler;
                        dls.Add(dl);
                        dl.Start(item, dlInfo.isReStart);
                    }
                }
            }
     
            private void GetTaskInfo(DownloadInfo dlInfo)
            {
                var pieceSize = (dlInfo.count) / dlInfo.taskCount;
                dlInfo.TaskInfoList = new List<TaskInfo>();
                var rand = new Random();
                var urlIndex = 0;
                for (int i = 0; i <= dlInfo.taskCount + 1; i++)
                {
                    var from = (i * pieceSize);
     
                    if (from >= dlInfo.count) break;
                    var to = from + pieceSize;
                    if (to >= dlInfo.count) to = dlInfo.count;
     
                    dlInfo.TaskInfoList.Add(
                        new TaskInfo
                        {
                            method = dlInfo.method,
                            downloadUrl = dlInfo.downloadUrlList[urlIndex++],
                            filePath = Path.Combine(dlInfo.saveDir, dlInfo.tempFileName + i + ".temp"),
                            fromIndex = from,
                            toIndex = to
                        });
                    if (urlIndex >= dlInfo.downloadUrlList.Count) urlIndex = 0;
                }
            }
     
            /// <summary>
            /// 保存内容
            /// </summary>
            /// <param name="filePath"></param>
            /// <param name="stream"></param>
            private void Save(string filePath, Stream stream)
            {
                try
                {
                    using (var writer = File.Open(filePath, FileMode.Append))
                    {
                        using (stream)
                        {
                            var repeatTimes = 0;
                            byte[] buffer = new byte[1024];
                            var length = 0;
                            while ((length = stream.Read(buffer, 0, buffer.Length)) > 0 && this.isRun)
                            {
                                writer.Write(buffer, 0, length);
                                this.fromIndex += length;
                                if (repeatTimes % 5 == 0)
                                {
                                    writer.Flush();//一定大小就刷一次缓冲区
                                    OnDownloadHandler();
                                }
                                repeatTimes++;
                            }
                            writer.Flush();
                            OnDownloadHandler();
                        }
                    }
                }
                catch (Exception)
                {
                    //异常也不影响
                }
            }
     
     
            private void OnStartHandler()
            {
                new Action(() =>
                {
                    this.OnStart?.Invoke();
                }).BeginInvoke(null, null);
            }
            private void OnStopHandler()
            {
                new Action(() =>
                {
                    this.OnStop?.Invoke();
                }).BeginInvoke(null, null);
            }
            private void OnFineshHandler()
            {
                new Action(() =>
                {
                    for (int i = 0; i < dlInfo.TaskInfoList.Count; i++)
                    {
                        var tempFile = dlInfo.TaskInfoList[i].filePath;
                        File.Delete(tempFile);
                    }
                    this.OnFinsh?.Invoke();
                }).BeginInvoke(null, null);
            }
            private void OnDownloadHandler()
            {
                new Action(() =>
                {
                    long current = GetDownloadLength();
                    this.OnDownload?.Invoke(current, dlInfo.count);
                }).BeginInvoke(null, null);
            }
     
            public long GetDownloadLength()
            {
                if (dlInfo.IsSupportMultiThreading) return  dls.Sum(dl => dl.GetDownloadedCount());
                else return this.fromIndex;
            }
        }

    以上就是这个下载的核心,使用方式也比较简单,下面是自己在winform上的简单实现效果

    代码:

        public partial class DownloadForm : Form
        {
            private DownloadManager downloadManager;
     
            public DownloadForm()
            {
                InitializeComponent();
            }
            private void ShowLog(string log)
            {
                this.rtbLog.Invoke(new Action(() =>
                {
                    this.rtbLog.Text = string.Format("{0}
    {1}", log,this.rtbLog.Text);
                }));
            }
            private void btnCreateTask_Click(object sender, EventArgs e)
            {
     
                var downloadInfo = new DownloadInfo();
                downloadInfo.saveDir = tbDir.Text;
                downloadInfo.downloadUrlList = new List<string> {
                    tbUrl.Text
                };
                downloadInfo.taskCount = 1;
                downloadManager = new DownloadManager(downloadInfo);
                downloadManager.OnDownload += DownloadManager_OnDownload;
                downloadManager.OnStart += DownloadManager_OnStart;
                downloadManager.OnStop += DownloadManager_OnStop;
                downloadManager.OnFinsh += DownloadManager_OnFinsh;
     
                ShowLog("新建任务");
            }
     
            private void DownloadManager_OnStop()
            {
                ShowLog("暂停下载");
            }
     
            private void DownloadManager_OnFinsh()
            {
                ShowLog("完成下载");
            }
     
            private void DownloadManager_OnStart()
            {
                ShowLog("开始下载");
            }
     
            private void DownloadManager_OnDownload(long arg1, long arg2)
            {
     
                this.lbProcess.Invoke(new Action(() =>
                {
                    
                    this.pgbProcess.Value = (int)(arg1 * 100.00 / arg2);
                    this.lbProcess.Text = string.Format("{0}/{1}", arg1, arg2);
                }));
            }
     
            private void btnStartDownload_Click(object sender, EventArgs e)
            {
                if (downloadManager == null) btnCreateTask_Click(null, null);
                downloadManager.Start();
            }
     
            private void btnStop_Click(object sender, EventArgs e)
            {
                downloadManager.Stop();
            }
     
            private void btnReStart_Click(object sender, EventArgs e)
            {
                if (downloadManager == null) btnCreateTask_Click(null, null);
                downloadManager.ReStart();
            }
        }

    补上 HttpHelper 类:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Threading.Tasks;
     
    namespace Downloader
    {
        public class HttpHelper
        {
            public static void init_Request(ref System.Net.HttpWebRequest request)
            {
                request.Accept = "text/json,*/*;q=0.5";
                request.Headers.Add("Accept-Charset", "utf-8;q=0.7,*;q=0.7");
                request.Headers.Add("Accept-Encoding", "gzip, deflate, x-gzip, identity; q=0.9");
                request.AutomaticDecompression = System.Net.DecompressionMethods.GZip;
                request.Timeout = 8000;
            }
     
            private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
            {
                return true; //总是接受  
            }
     
            public static System.Net.HttpWebRequest GetHttpWebRequest(string url)
            {
                HttpWebRequest request = null;
                if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
                {
                    ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
                    request = WebRequest.Create(url) as HttpWebRequest;
                    request.ProtocolVersion = HttpVersion.Version10;
                }
                else
                {
                    request = WebRequest.Create(url) as HttpWebRequest;
                }
                return request;
            }
            public static WebResponse Download(string downloadUrl, long from, long to, string method)
            {
                var request = HttpHelper.GetHttpWebRequest(downloadUrl);
                HttpHelper.init_Request(ref request);
                request.Accept = "text/json,*/*;q=0.5";
                request.AddRange(from, to);
                request.Headers.Add("Accept-Charset", "utf-8;q=0.7,*;q=0.7");
                request.Headers.Add("Accept-Encoding", "gzip, deflate, x-gzip, identity; q=0.9");
                request.AutomaticDecompression = System.Net.DecompressionMethods.GZip;
                request.Timeout = 120000;
                request.Method = method;
                request.KeepAlive = false;
                request.ContentType = "application/json; charset=utf-8";
                return request.GetResponse();
            }
            public static string Get(string url, IDictionary<string, string> param)
            {
                var paramBuilder = new List<string>();
                foreach (var item in param)
                {
                    paramBuilder.Add(string.Format("{0}={1}", item.Key, item.Value));
                }
                url = string.Format("{0}?{1}", url.TrimEnd('?'), string.Join(",", paramBuilder.ToArray()));
                return Get(url);
            }
            public static string Get(string url)
            {
                try
                {
                    var request = GetHttpWebRequest(url);
                    if (request != null)
                    {
                        string retval = null;
                        init_Request(ref request);
                        using (var Response = request.GetResponse())
                        {
                            using (var reader = new System.IO.StreamReader(Response.GetResponseStream(), System.Text.Encoding.UTF8))
                            {
                                retval = reader.ReadToEnd();
                            }
                        }
                        return retval;
                    }
                }
                catch
                {
     
                }
                return null;
            }
            public static string Post(string url, string data)
            {
                try
                {
                    var request = GetHttpWebRequest(url);
                    if (request != null)
                    {
                        string retval = null;
                        init_Request(ref request);
                        request.Method = "POST";
                        request.ServicePoint.Expect100Continue = false;
                        request.ContentType = "application/json; charset=utf-8";
                        request.Timeout = 800;
                        var bytes = System.Text.UTF8Encoding.UTF8.GetBytes(data);
                        request.ContentLength = bytes.Length;
                        using (var stream = request.GetRequestStream())
                        {
                            stream.Write(bytes, 0, bytes.Length);
                        }
                        using (var response = request.GetResponse())
                        {
                            using (var reader = new System.IO.StreamReader(response.GetResponseStream()))
                            {
                                retval = reader.ReadToEnd();
                            }
                        }
                        return retval;
                    }
                }
                catch
                {
     
                }
                return null;
            }
            
        }
    }

    界面效果

     好了,基本就是这样,有不完善之处,还请发谅解并指出。项目源码就不发了,文章已经包含了这个客户端实现的所有代码。

  • 相关阅读:
    华为移动HG8546M光猫路由器通过lan口再连接路由器
    Windows下使用Dev C++ 编写dll与使用dll(二)C++项目下的dll
    Windows下使用Dev C++ 编写dll与使用dll(一)C项目下的dll
    易语言之dll文件的编写与引入
    易语言之编写模块与引入模块
    Element ui 分页记录选中框
    MUI poppicker.js 增加搜索框
    element el-date-picker 去除自带的T格式
    element el-input小数保留两位小数,整数字符串去空格
    nginx vue三级目录配置
  • 原文地址:https://www.cnblogs.com/kevin860/p/12651994.html
Copyright © 2011-2022 走看看