【C#】文件并发下载——Windows程序设计作业3

 一、实验目的

  1. 掌握Windows Forms应用程序的基本开发流程。
  2. 实现一个能够读取文件列表并从网络批量下载文件的系统。
  3. 了解多线程下载的基本原理和实现方法。

二、实验内容

  1. 创建一个Windows Forms应用程序,包含一个窗体Form1
  2. Form1上添加一个按钮btnTest用于触发下载操作,以及一个ListView控件listView1用于显示下载信息。
  3. 编写代码实现以下功能:
    • 读取一个文本文件,该文件包含要下载的文件URL和文件名(以“|”分隔)。
    • 将读取到的文件信息添加到ListView控件中。
    • 使用自定义的DownLoadFile类进行文件的下载,支持多线程下载。
    • 在下载过程中,通过事件更新ListView控件中的下载状态信息。

三、实验步骤

  1. 创建一个新的Windows Forms应用程序项目。
  2. 设计窗体界面,添加按钮和ListView控件。
  3. 编写代码实现上述功能。
  4. 调试并测试程序,确保能够正确读取文件信息、添加下载任务、显示下载状态。

四、实验代码解析

1.字段与属性

定义了DownLoadFile类的实例dlf,用于处理下载操作。

DownLoadFile dlf = new DownLoadFile();

2.btnTest_Click事件处理器

这段代码是一个按钮点击事件的处理函数,当点击btnTest按钮时,触发此事件处理器,主要功能是读取一个文本文件(软件下载1.txt),解析该文件的内容,并基于解析的结果在ListView控件中添加项目,同时启动下载任务。以下是对这段代码的详细分析:

读取文件

string[] lines = File.ReadAllLines("软件下载1.txt");

读取名为 "软件下载1.txt" 的文本文件的所有行,并将它们存储在字符串数组 lines 中。

读取文件内容:使用File.ReadAllLines方法读取名为软件下载1.txt的文件的所有行,并返回一个字符串数组lines

 private void btnTest_Click(object sender, EventArgs e)
        {
            string[] lines = File.ReadAllLines("软件下载1.txt");
            for (int i = 0; i < lines.Length; i++)
            {
                string[] line = lines[i].Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
                if (line.Length == 2)
                {
                    string path = Uri.EscapeUriString(line[1]);
                    string filename = Path.GetFileName(path);
                    string dir = @"D:\test";
                    ListViewItem item = listView1.Items.Add(new ListViewItem(new string[] { (listView1.Items.Count + 1).ToString(), filename, "0", "0", "0%", "0", "0", DateTime.Now.ToString(), "等待中", line[1] }));
                    int id = item.Index;
                    dlf.AddDown(path, dir, id, id.ToString());
                }
            }
            dlf.StartDown();
        }

3.Form1_Load事件处理器

窗体加载时设置下载线程数和绑定消息处理事件。

设置线程数
dlf.ThreadNum = 3; 这行代码设置了一个名为 dlf 的对象的 ThreadNum 属性值为3。在这里,它被设置为3,意味着当进行文件下载时,将使用3个线程来并行处理下载任务

private void Form1_Load(object sender, EventArgs e)  
{  
    dlf.ThreadNum = 3; // 设置线程数  
    dlf.doSendMsg += SendMsgHander; // 绑定下载消息处理事件  
}

4.SendMsgHander事件处理器

处理来自DownLoadFile类的消息,更新ListView中的下载状态。

 private void SendMsgHander(DownMsg msg)
        {
            switch (msg.Tag)
            {
                case DownStatus.Start:
                    this.Invoke((MethodInvoker)delegate ()
                    {
                       //不过多赘述,省略相关代码...
                    });
                    break;
                case DownStatus.GetLength:
                    this.Invoke((MethodInvoker)delegate ()
                    {
                      //不过多赘述,省略相关代码...
                    });
                    break;
                case DownStatus.End:
                case DownStatus.DownLoad:
                    this.Invoke(new MethodInvoker(() =>
                    {
                       //不过多赘述,省略相关代码...

                    }));
                    break;
                case DownStatus.Error:
                    this.Invoke((MethodInvoker)delegate ()
                    {
                         //不过多赘述,省略相关代码...
                    });
                    break;
            }
        }

五、引用

1.DownLoadFile

从以下的代码片段来看,这是一个名为 DownLoadFile 的类,该类属于 Gac 命名空间,并且似乎用于处理文件的下载任务。以下是每个部分的作用:

  1. 类成员变量

    ThreadNum: 设定了默认的线程数量,但在这段代码中它似乎并未被用作限制同时运行的线程数。list: 这是一个 List<Thread> 类型的变量,用于存储创建的下载线程。
  2. 构造函数

    DownLoadFile(): 构造函数中,将 Change 方法绑定到 doSendMsg 事件上。这意味着当 doSendMsg 事件被触发时,Change 方法将被调用。
  3. Change 方法

    这是一个私有方法,用于处理接收到的 DownMsg 消息。如果消息标记为 Error 或 End(下载出错或完成),则调用 StartDown 方法开始新的下载任务。
  4. AddDown 方法

    此方法接受下载URL、目标目录、ID(可选)和文件名(可选)作为参数,并创建一个新的线程来执行 download 方法。新创建的线程被添加到 list 列表中。
  5. StartDown 方法

    该方法用于启动列表中的线程进行下载。默认情况下,它会启动 StartNum 个线程,但如果列表中没有处于 Unstarted 或 Suspended 状态的线程,它将不会启动任何新线程。这里使用了 lock 关键字来确保线程安全地访问 list
  6. 事件和委托

    dlgSendMsg: 这是一个委托,定义了事件处理程序的签名。doSendMsg: 这是一个事件,当某些操作(如下载开始、结束或出错)发生时,可以触发此事件并传递一个 DownMsg 消息。
  7. download 方法

    首先创建一个 DownMsg 对象并触发 doSendMsg 事件来通知下载开始。然后,创建了一个 FileDownloader 对象来执行实际的下载操作。

代码实现:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Gac
{
  
    public class DownLoadFile
    {
        public int ThreadNum = 3;
        List<Thread> list = new List<Thread>();
        public DownLoadFile()
        {
            doSendMsg += Change;
        }
        private void Change(DownMsg msg)
        {
            if (msg.Tag==DownStatus.Error||msg.Tag==DownStatus.End)
            {
                StartDown(1);
            }
        }
        public void AddDown(string DownUrl,string Dir, int Id = 0,string FileName="")
        {
            Thread tsk = new Thread(() =>
            {
                download(DownUrl, Dir, FileName,Id);
            });
            list.Add(tsk);
        }
        public void StartDown(int StartNum=3)
        {
            for (int i2 = 0; i2 < StartNum; i2++)
            {
                lock (list)
                {
                    for (int i = 0; i < list.Count; i++)
                    {
                        if (list[i].ThreadState == System.Threading.ThreadState.Unstarted || list[i].ThreadState == ThreadState.Suspended)
                        {
                            list[i].Start();
                            break;
                        }
                    }
                }
            }
            
        }
        public delegate void dlgSendMsg(DownMsg msg);
        public event dlgSendMsg doSendMsg;
        private void download(string path, string dir,string filename,int id = 0)
        {

            try
            {
                DownMsg msg = new DownMsg();
                msg.Id = id;
                msg.Tag = 0;
                doSendMsg(msg);
                FileDownloader loader = new FileDownloader(path, dir, filename, ThreadNum);
               //不过多赘述,省略...
            }
            catch (Exception ex)
            {
                DownMsg msg = new DownMsg();
                msg.Id = id;
                //不过多赘述,省略...
               
                Console.WriteLine(ex.Message);
            }
        }


    }
   
}

2.DownloadProgressListener

  1. 类成员变量private long presize = 0;:这个变量被初始化为0,但在给定的代码片段中并未使用。DownMsg downMsg = null;:这是一个 DownMsg 类型的实例,用于存储和更新与下载相关的状态信息。
  2. 构造函数public DownloadProgressListener(DownMsg downmsg):构造函数接受一个 DownMsg 类型的参数,并将其赋值给类的成员变量 downMsg
  3. 事件和委托public delegate void dlgSendMsg(DownMsg msg);:定义了一个名为 dlgSendMsg 的委托,该委托接受一个 DownMsg 类型的参数。public dlgSendMsg doSendMsg = null;:声明了一个 dlgSendMsg 类型的公开事件 doSendMsg,并将其初始化为 null。这个事件可以用于通知其他类关于下载进度的更新。
  4. OnDownloadSize 方法
    • 这个方法用于处理下载过程中的大小更新。它接受一个 long 类型的参数 size,表示当前已下载的字节数。
    • 方法内部首先检查 downMsg 是否为 null,但即使 downMsg 是 null,它也没有执行任何有意义的操作(它创建了一个局部变量 DownMsg downMsg,但没有将其赋值给类的成员变量)。
    • 接下来,它计算并更新下载速度 Speed、剩余时间 Surplus 和剩余时间信息 SurplusInfo
    • 它还会更新 downMsg 的 Size 属性以反映当前已下载的总量。
    • 如果已下载的字节数等于 downMsg 中的 Length 属性,表示下载已完成,它将设置相应的标签和状态信息。
    • 最后,如果 doSendMsg 事件不为 null,它将触发该事件,并传递更新后的 downMsg 对象作为参数,以通知其他类关于下载进度的更新。
  5. DownStatus 枚举:定义了一个名为 DownStatus 的枚举,它包含了表示下载过程中不同状态的常量,如开始、获取长度、下载中、完成和错误。
  6. DownMsg 类:定义了一个名为 DownMsg 的类,用于封装与下载相关的状态信息。该类包含多个私有字段,如 _Length(文件总长度)、_Size(已下载大小)和 _Tag(下载状态)等。尽管这些字段是私有的,但从给定的代码片段中无法看到相应的公共访问器(getter 和 setter)。这意味着外部类可能无法直接访问或修改这些字段的值(除非通过其他方式,如反射)。

代码实现:

using System;
using System.Collections.Generic;
using System.Text;

namespace Gac
{
    public class DownloadProgressListener : IDownloadProgressListener
    {
        private long presize=0;
        DownMsg downMsg = null;
        public DownloadProgressListener(DownMsg downmsg)
        {
            this.downMsg = downmsg;
            //this.id = id;
            //this.Length = Length;
        }
        public delegate void dlgSendMsg(DownMsg msg);
        public dlgSendMsg doSendMsg = null;
        public void OnDownloadSize(long size)
        {
            if (downMsg==null)
            {
                DownMsg downMsg = new DownMsg();
            }


            //下载速度
            if (downMsg.Size == 0)
            {
                downMsg.Speed = size;
            }
            else
            {
                downMsg.Speed = (float)(size - downMsg.Size);
                
            }
            if (downMsg.Speed == 0)
            {
                downMsg.Surplus = -1;
                downMsg.SurplusInfo = "未知";
            }
            else
            {
                downMsg.Surplus = ((downMsg.Length - downMsg.Size) / downMsg.Speed);
            }
            downMsg.Size = size; //下载总量
           
            if (size == downMsg.Length)
            {
                //下载完成
                downMsg.Tag = DownStatus.End;
                downMsg.SpeedInfo = "0 K";
                downMsg.SurplusInfo = "已完成";
            }
            else
            {
                //下载中
                downMsg.Tag = DownStatus.DownLoad;
                

            }
            
            
            if (doSendMsg != null) doSendMsg(downMsg);//通知具体调用者下载进度
        }
    }
    public enum DownStatus
    {
        Start,
        GetLength,
        DownLoad,
        End,
        Error
    }
    public class DownMsg
    {
        private int _Length = 0;
        private string _LengthInfo = "";
        private int _Id = 0;
        private DownStatus _Tag = 0;
        private long _Size = 0;
        private string _SizeInfo = "";
        private float _Speed = 0;
        private float _Surplus = 0;
        private string _SurplusInfo ="";
        private string _ErrMessage = "";
        private string _SpeedInfo = "";
        private double _Progress = 0;

        public int Length
        {
            get
            {
                return _Length;
            }

            set
            {
                _Length = value;
                LengthInfo = GetFileSize(value);
            }
        }

        public int Id
        {
            get
            {
                return _Id;
            }

            set
            {
                _Id = value;
            }
        }
        /// </summary>
        public DownStatus Tag
        {
            get
            {
                return _Tag;
            }

            set
            {
                _Tag = value;
            }
        }

        public long Size
        {
            get
            {
                return _Size;
            }

            set
            {
                _Size = value;
                SizeInfo = GetFileSize(value);
                if (Length >= value)
                {
                    Progress = Math.Round((double)value / Length * 100, 2);
                }
                else
                {
                    Progress = -1;
                }
            }
        }

        public float Speed
        {
        
        }

        //不过多久赘述,省略下方public代码...
    }
}

3.DownloadThread

这段代码定义了一个名为DownloadThread的类,它代表了用于多线程文件下载的单个线程。我会逐步分析这段代码,并解释每个部分的作用。

1.成员变量

  1. saveFilePath:保存下载文件的路径。
  2. downUrl:要下载文件的URL。
  3. block:通常用于指定每个线程下载的文件块大小(尽管在这段代码中它似乎被用作整个文件的总块数,而不是块大小)。
  4. threadId:线程的ID,用于标识不同的下载线程。
  5. downLength:已经下载的字节数(对于当前线程)。
  6. finish:一个布尔值,指示线程是否已完成下载。
  7. downloader:一个FileDownloader类型的对象,可能是用于协调多个DownloadThread的类。

2.构造函数

  • DownloadThread(FileDownloader downloader, string downUrl, string saveFile, long block, long downLength, int threadId):初始化DownloadThread对象的构造函数,它接受多个参数来设置对象的初始状态。

3.ThreadRun方法

if (downLength < block):检查是否已经下载了完整的文件块(对于当前线程)

代码实现:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Threading;

namespace Gac
{
    public class DownloadThread
    {
        private string saveFilePath;
        private string  downUrl;
        private long block;
        private int threadId = -1;
        private long downLength;
        private bool finish = false;
        private FileDownloader downloader;

        public DownloadThread(FileDownloader downloader, string downUrl, string saveFile, long block, long downLength, int threadId)
        {
            this.downUrl = downUrl;
            this.saveFilePath = saveFile;
            this.block = block;
            this.downloader = downloader;
            this.threadId = threadId;
            this.downLength = downLength;
        }


        public void ThreadRun()
        {
            //task
            Thread td = new Thread(new ThreadStart(() =>
            {
                 if (downLength < block)//未下载完成
                {
                    try
                    {
                        //省略...
                    }
                    catch (Exception e)
                    {
                        this.downLength = -1;
                        Console.WriteLine("Thread " + this.threadId + ":" + e.Message);
                    }
                } 
            }));
            td.IsBackground = true;
            td.Start();
        }
        /// <summary>
        /// 下载是否完成
        /// </summary>
        /// <returns></returns>
        public bool isFinish()
        {
            return finish;
        }
        /// <summary> 
        ///  已经下载的内容大小  
        /// </summary>
        /// <returns>如果返回值为-1,代表下载失败</returns>
        public long getDownLength()
        {
            return downLength;
        }

    }
}

4.FileDownloader

从以下的FileDownloader类代码片段中,可以看到这是一个用于多线程文件下载的类的部分实现。以下是对您提供的代码的分析:

  1. 成员变量
    • downloadSize:用于跟踪已下载的总数据量(但在此代码片段中并未完全使用)。
    • fileSize:用于存储要下载的文件的大小。
    • threads:一个DownloadThread类型的数组,用于存储和管理下载线程。
    • saveFile:保存文件的完整路径。
    • data:一个字典,用于存储每个线程下载的进度或位置(但在当前代码片段中仅用于更新位置,未看到如何用于进度追踪或合并文件)。
    • block:此变量在代码片段中未定义或使用,可能是一个用于表示单个线程下载块大小的变量,但在当前代码中未实现。
    • downloadUrl:要下载的文件的URL。
  2. 构造函数
    • 在构造函数中,首先处理文件名。如果未提供文件名,则从URL中提取文件名,并进行URI解码以处理可能的特殊字符或中文字符。
    • 检查文件保存目录是否存在,如果不存在则创建。
    • 初始化threads数组以存储下载线程。
    • 创建一个HttpWebRequest对象来设置HTTP请求的详细信息,如Referer、Method等。但此代码片段并未实际发送请求或获取响应。
  3. 方法
    • getThreadSize:返回线程数组的长度,即下载线程的数量。
    • getFileSize:返回fileSize变量,即要下载的文件的大小。但在此代码片段中,我们并未看到fileSize是如何被设置的。
    • append:此方法使用lock关键字来确保线程安全地更新downloadSize。然而,在当前的代码片段中,我们并未看到downloadSize在其他地方被使用或更新,因此这个方法目前看来并没有实际作用。
    • update:此方法更新data字典中指定线程ID的下载位置。如果线程ID不存在于字典中,则将其添加到字典中。
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using System.Net;

namespace Gac
{
    public class FileDownloader
    {
       
        private long downloadSize = 0;
      
        private long fileSize = 0;
       
        private DownloadThread[] threads;
    
        private string saveFile;
       
        public Dictionary<int, long> data = new Dictionary<int, long>();
      
        private long block;
       
        private String downloadUrl;
    
        public int getThreadSize()
        {
            return threads.Length;
        }
       
        public long getFileSize()
        {
            return fileSize;
        }
       
        public void append(long size)
        {
            lock (this)  //锁定同步..........
            {
                downloadSize += size;
            }

        }
       
        public void update(int threadId, long pos)
        {
            if (data.ContainsKey(threadId))
            {
                this.data[threadId] = pos;
            }
            else
            {
                this.data.Add(threadId, pos);
            }
        }

       
        public FileDownloader(string downloadUrl, string fileSaveDir,string filename="", int threadNum=3)
        {
            try
            {
                if (string.IsNullOrEmpty(filename))
                {
                    //省略...
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                throw new Exception("无法连接下载地址");
            }
        }

        public long download(IDownloadProgressListener listener)
        {
            try
            {
                using (FileStream fstream = new FileStream(this.saveFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
                {
                    if (this.fileSize > 0) fstream.SetLength(this.fileSize);
                    fstream.Close();
                }
                if (this.data.Count != this.threads.Length)
                {
                    this.data.Clear();
                    for (int i = 0; i < this.threads.Length; i++)
                    {
                        this.data.Add(i + 1, 0);//初始化每条线程已经下载的数据长度为0
                    }
                }
                for (int i = 0; i < this.threads.Length; i++)
                {//开启线程进行下载
                    long downLength = this.data[i + 1];
                    if (downLength < this.block && this.downloadSize < this.fileSize)
                    {
                        //判断线程是否已经完成下载,否则继续下载	
                    }
                    else
                    {
                        this.threads[i] = null;
                    }
                }
                bool notFinish = true;//下载未完成
                while (notFinish)
                {// 循环判断所有线程是否完成下载
                    Thread.Sleep(900);
                    notFinish = false;//假定全部线程下载完成
                    for (int i = 0; i < this.threads.Length; i++)
                    {
                        if (this.threads[i] != null && !this.threads[i].isFinish())
                        {//如果发现线程未完成下载
                            notFinish = true;//设置标志为下载没有完成
                            if (this.threads[i].getDownLength() == -1)
                            {//如果下载失败,再重新下载
                                this.threads[i] = new DownloadThread(this, downloadUrl, this.saveFile, this.block, this.data[i + 1], i + 1);
                                this.threads[i].ThreadRun();
                            }
                        }
                    }
                    if (listener != null)
                    {
                        listener.OnDownloadSize(this.downloadSize);//通知目前已经下载完成的数据长度
                        Console.WriteLine(this.downloadSize);
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                throw new Exception("下载文件失败");
            }
            return this.downloadSize;
        }
    }
}

5.IDownloadProgressListener

using System;
using System.Collections.Generic;
using System.Text;

namespace Gac
{
   public  interface  IDownloadProgressListener
    {
         void OnDownloadSize(long size);
    }
}

六、实验结果

七、实验小结

本次实验通过Windows Forms应用程序实现了文件的并发下载功能。通过DownLoadFile类管理下载任务,并使用多线程技术提高下载效率。用户点击按钮后,程序从指定文件中读取下载链接,并将任务添加到下载队列中。程序开始并发下载,并通过ListView控件实时更新下载进度和状态。

实验结果验证了并发下载技术的有效性,显著提高了文件下载的效率。用户可以通过界面直观地了解每个下载任务的进度和状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值