Windows程序作业三:文件并发下载

目录

一、作业内容

二、作业设计界面和项目结构

三、实验代码

3.1  Form1.cs

这段代码是一个C#方法,用于处理一个按钮(名为btnTest)的点击事件。以下是代码的详细分析:

3.1.2  这段代码涉及到一个WinForms应用程序中的文件下载功能,特别是与下载状态更新和用户界面(UI)线程之间的交互

3.2  DownLoadFile.cs

3.3  DownloadThread.cs

这段代码定义了一个名为DownloadThread的类,它代表一个用于文件下载的线程。以下是对这段代码的分析:

3.4  FileDownloader.cs

3.5 IDownloadProgressListener.cs

四、作业结果

五、作业小结


一、作业内容

实现文件并发下载,利用C#编写代码,能够从文本文件中读取文件下载链接和文件名,该次作业文件名命名为软件下载1.txt,并将下载内容下载至D:\test,下载界面可以反映出文件名、文件总大小、文件下载速度、下载进度、下载时间、下载状态、下载网址等。可以通过多线程的方式进行文件并发下载,界面可实时观看下载速度、状态和进度。

二、作业设计界面和项目结构

三、实验代码

3.1  Form1.cs

这段代码是一个C#方法,用于处理一个按钮(名为btnTest)的点击事件。以下是代码的详细分析:

文件读取:string[] lines = File.ReadAllLines("软件下载1.txt");
这行代码从名为"软件下载1.txt"的文件中读取所有行,并将它们存储在一个字符串数组中。

代码定义了一个目标目录dir,其值为"D:\test"。
更新ListView
使用ListViewItem的构造函数创建一个新的列表项,其中包含多个子项(字符串数组)。这些子项包括:当前项在ListView中的索引(加1,以便从1开始计数)文件名(从path中提取)
七个固定的字符串或日期时间值(例如,"0", "0%", DateTime.Now.ToString(), "等待中"等)
原始的文件路径(从line数组的第二个元素获取)
使用listView1.Items.Add方法将新创建的ListViewItem添加到listView1控件中,并获取新项的索引值。将新项的索引值存储在id变量中。
添加下载任务
dlf.AddDown(path, dir, id, id.ToString());
这行代码调用一个名为dlf的对象的AddDown方法,并传递四个参数:要下载的文件的路径、目标目录、下载任务的ID(整数形式)和下载任务的ID(字符串形式)。注意,这里使用id和id.ToString()作为两个不同的参数,可能是为了方法内部的不同需求(尽管从代码片段中我们不能确定AddDown方法的确切实现)。
开始下载:
dlf.StartDown();
最后,这行代码调用dlf对象的StartDown方法,开始执行之前添加的所有下载任务。

DownLoadFile dlf = new DownLoadFile();
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.1.2  这段代码涉及到一个WinForms应用程序中的文件下载功能,特别是与下载状态更新和用户界面(UI)线程之间的交互

Form1_Load 方法:
在窗体加载时,设置dlf对象的ThreadNum属性为3,表示同时进行的下载线程数。如果不设置,则默认为3。
订阅dlf对象的doSendMsg事件,当下载过程中有消息发送时,会调用SendMsgHander方法。

private void Form1_Load(object sender, EventArgs e)
{
    dlf.ThreadNum = 3;//线程数,不设置默认为3
    dlf.doSendMsg += SendMsgHander;//下载过程处理事件
}

SendMsgHander 方法
这是一个事件处理程序,用于处理来自dlf对象的下载消息。
它接受一个DownMsg类型的参数msg,该参数包含了下载相关的状态信息。
使用switch语句根据msg.Tag的值(即下载状态)来更新ListView中的相应项。如:下载、连接成功、下载中、下载完成等。
状态更新
对于每种状态(如Start, GetLength, DownLoad, End, Error),代码通过调用this.Invoke方法来确保UI更新在UI线程上执行

// 下载消息处理事件处理器
private void SendMsgHander(DownMsg msg) // 处理下载状态更新
{
    // 根据下载状态更新ListView控件中的项
    switch (msg.Tag)
    {
        case DownStatus.Start:
            // 开始下载
            this.Invoke((MethodInvoker)delegate ()
            {
                listView1.Items[msg.Id].SubItems[8].Text = "开始下载";
                // ... 省略其他UI更新代码
            });
            break;
        // ... 省略其他case处理代码
    }
}

3.2  DownLoadFile.cs

成员变量:
ThreadNum:定义默认下载线程数为3。
list:一个线程列表,用于存储创建的下载线程。
构造函数:
在构造函数中,将Change方法绑定到doSendMsg事件。这意味着每当doSendMsg事件被触发时,Change方法会被调用。
Change方法:
当下载任务遇到错误或结束时(DownStatus.Error或DownStatus.End),该方法会尝试启动一个新的下载任务(通过调用StartDown(1))。
AddDown方法:
该方法用于向list中添加一个新的下载线程。线程执行的是download方法,并传入相关参数。
StartDown方法:
该方法尝试启动最多StartNum个未启动或挂起的线程。
使用lock关键字确保在遍历和修改list时线程安全。
事件和委托:
定义了dlgSendMsg委托和doSendMsg事件,用于在下载过程中发送消息。
注释掉的代码行(//public event doSendMsg;和//public dlgSendMsg doSendMsg = null;)
download方法:
这是实际执行下载操作的私有方法。
创建了一个DownMsg对象,并通过doSendMsg事件发送了初始消息
创建了FileDownloader对象来执行实际的下载操作,并传递了相关参数。
创建一个DownloadProgressListener对象来监听下载进度,并将doSendMsg事件绑定到该监听器的相应事件上。

public class DownLoadFile
{
    // 默认线程数量
    public int ThreadNum = 3;
    // 用于存储所有下载线程的列表
    List<Thread> list = new List<Thread>();

    // 构造函数,订阅消息发送事件
    public DownLoadFile()
    {
        doSendMsg += Change; // 注册事件处理
    }

    // 下载任务添加方法
    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 == ThreadState.Unstarted || list[i].ThreadState == ThreadState.Suspended)
                    {
                        list[i].Start(); // 启动线程
                        break;
                    }
                }
            }
        }
    }

    // 下载状态更新事件委托
    public delegate void dlgSendMsg(DownMsg msg);
    // 下载状态更新事件
    public event dlgSendMsg doSendMsg;

    // 下载状态变化处理方法
    private void Change(DownMsg msg)
    {
        if (msg.Tag == DownStatus.Error || msg.Tag == DownStatus.End)
        {
            StartDown(1); // 可能的错误处理或重新下载逻辑
        }
    }

    // 私有下载方法
    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);
            loader.data.Clear();
            msg.Tag = DownStatus.Start;
            msg.Length = (int)loader.getFileSize();
            doSendMsg(msg); // 发送文件大小获取消息

            // 创建下载进度监听器并设置回调
            DownloadProgressListener linstenter = new DownloadProgressListener(msg);
            linstenter.doSendMsg = new DownloadProgressListener.dlgSendMsg(doSendMsg);
            loader.download(linstenter); // 开始下载文件
        }
        catch (Exception ex)
        {
            // 异常处理,发送错误消息
            DownMsg msg = new DownMsg();
            msg.Id = id;
            msg.Length = 0;
            msg.Tag = DownStatus.Error;
            msg.ErrMessage = ex.Message;
            doSendMsg(msg);

            Console.WriteLine(ex.Message); // 打印异常信息到控制台
        }
    }
}

3.3  DownloadThread.cs

这段代码定义了一个名为DownloadThread的类,它代表一个用于文件下载的线程。以下是对这段代码的分析:

成员变量:
saveFilePath:保存文件的路径。
downUrl:下载文件的URL。
block:每个线程应该下载的文件块大小
threadId:线程的ID,用于标识不同的下载线程。
downLength:已经下载的长度(可能表示从文件开始到当前线程应该开始下载的位置)。
finish:一个标志,表示线程是否已完成下载。
downloader:FileDownloader对象的引用,但从给定的代码片段中不清楚其具体用途,因为该对象在DownloadThread类中没有被进一步使用。
构造函数:
构造函数接收必要的参数来初始化下载线程的状态,包括下载URL、保存文件的路径、每个线程应下载的块大小、已下载的长度以及线程的ID。

ThreadRun方法:
这个方法定义了一个内部线程来执行实际的下载任务。
在内部线程中,它首先检查是否还有未下载的数据(即downLength < block)。
然后,它计算当前线程应该开始和结束下载的位置。
接下来,它创建一个HttpWebRequest对象来发送GET请求到指定的URL,并设置了一些HTTP头信息。

    // 定义一个下载线程类,用于多线程下载中的单个线程
public class DownloadThread
{
    // 下载文件的保存路径
    private string saveFilePath;
    // 文件的下载URL
    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()
    {
        // 创建一个新的线程来执行下载任务
        Thread td = new Thread(new ThreadStart(() =>
        {
            // 如果还有未下载的数据块
            if (downLength < block)
            {
                try
                {
                    // 计算当前线程的下载范围
                    int startPos = (int)(block * (threadId - 1) + downLength);
                    int endPos = (int)(block * threadId - 1);

                    // 输出当前线程的下载范围
                    Console.WriteLine($"Thread {this.threadId} start download from position {startPos} and end with {endPos}");

                    // 创建HTTP请求
                    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(downUrl);
                    request.Referer = downUrl.ToString();
                    request.Method = "GET";
                    // 设置请求的用户代理,模拟浏览器请求
                    request.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.1124)";
                    // 设置请求的其他属性
                    request.AllowAutoRedirect = false;
                    request.ContentType = "application/octet-stream";
                    request.Accept = "*/*";
                    request.Timeout = 10 * 1000;
                    request.AllowAutoRedirect = true;
                    // 设置请求的范围,实现断点续传
                    request.AddRange(startPos, endPos);

                    // 获取HTTP响应
                    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                    // 使用响应流
                    using (Stream _stream = response.GetResponseStream())
                    {
                        // 定义缓冲区
                        byte[] buffer = new byte[1024 * 50];
                        long offset;

                        // 打开文件流,准备写入数据
                        using (Stream threadfile = new FileStream(this.saveFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                        {
                            // 移动文件流的指针到正确的起始位置
                            threadfile.Seek(startPos, SeekOrigin.Begin);

                            // 读取数据并写入文件
                            while ((offset = _stream.Read(buffer, 0, buffer.Length)) != 0)
                            {
                                // 更新已下载的数据量
                                downloader.append(offset);
                                // 写入数据到文件
                                threadfile.Write(buffer, 0, (int)offset);
                                // 更新当前线程的下载进度
                                downLength += offset;
                                // 通知下载器更新线程的下载位置
                                downloader.update(this.threadId, downLength);
                            }

                            // 关闭流资源
                            threadfile.Close();
                            _stream.Close();
                        }

                        // 下载完成,输出日志信息
                        Console.WriteLine($"Thread {this.threadId} download finish");
                        this.finish = true;
                    }
                }
                catch (Exception e)
                {
                    // 异常处理,记录错误信息,并标记下载长度为-1表示失败
                    this.downLength = -1;
                    Console.WriteLine($"Thread {this.threadId}:{e.Message}");
                }
            }
        }));
        td.IsBackground = true; // 设置为后台线程,主程序结束时此线程也会结束
        td.Start(); // 启动线程
    }

    // 判断下载是否完成
    public bool isFinish()
    {
        return finish;
    }

    // 获取当前线程已下载的数据长度,失败时返回-1
    public long getDownLength()
    {
        return downLength;
    }
}

3.4  FileDownloader.cs

// FileDownloader类负责管理文件下载的核心逻辑
public class FileDownloader
{
    // 已下载的文件长度
    private long downloadSize = 0;
    // 原始文件长度
    private long fileSize = 0;
    // 下载线程数组
    private DownloadThread[] threads;
    // 本地保存文件路径
    private string saveFile;
    // 缓存各线程下载的长度,键为线程ID
    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))
        {
            data[threadId] = pos;
        }
        else
        {
            data.Add(threadId, pos);
        }
    }

    // 构造函数,初始化下载器
    public FileDownloader(string downloadUrl, string fileSaveDir, string filename = "", int threadNum = 3)
    {
        try
        {
            // 省略了URI解码和文件目录创建的代码...

            // 创建HTTP请求以获取文件大小
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(downloadUrl);
            // 省略了请求头设置的代码...

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    // 获取文件大小
                    this.fileSize = response.ContentLength;
                    // 省略了保存文件路径设置的代码...

                    // 计算每条线程下载的数据长度
                    this.block = (this.fileSize % threadNum == 0) ? 
                        this.fileSize / threadNum : 
                        this.fileSize / threadNum + 1;
                }
                else
                {
                    throw new Exception("服务器返回状态失败");
                }
            }
        }
        catch (Exception e)
        {
            // 异常处理逻辑
        }
    }

    // 开始下载文件
    public long download(IDownloadProgressListener listener)
    {
        try
        {
            // 省略了文件流设置的代码...

            // 初始化线程下载数据长度字典
            if (data.Count != threads.Length)
            {
                data.Clear();
                for (int i = 0; i < threads.Length; i++)
                {
                    data.Add(i + 1, 0);
                }
            }

            // 开启线程进行下载
            for (int i = 0; i < threads.Length; i++)
            {
                // 省略了线程创建和启动的代码...
            }

            // 循环判断所有线程是否完成下载
            bool notFinish = true;
            while (notFinish)
            {
                // 省略了线程状态检查和进度更新的代码...
            }
        }
        catch (Exception e)
        {
            // 异常处理逻辑
        }
        return this.downloadSize;
    }
}

3.5 IDownloadProgressListener.cs

public interface IDownloadProgressListener
    {
        /// <summary>
        /// 当下载的字节大小发生变化时调用此方法。
        /// 这个方法允许实现类根据下载的进度执行相应的操作。
        /// </summary>
        /// <param name="size">当前已下载的字节数。</param>
        void OnDownloadSize(long size);
    }

四、作业结果

五、作业小结

本次作业实现文件并发下载,利用C#编写代码,下载界面可以反映出文件名、文件总大小、文件下载速度、下载进度、下载时间、下载状态、下载网址等。可以通过多线程的方式进行文件并发下载,界面可实时观看下载速度、状态和进度。

作业目的为验证多线程下载是否能够有效提高文件下载的速度。理解和实现多线程下载的基本机制。作业过程为编写文件读取、更新ListView、添加下载任务、开始下载和状态更新代码等。
项目提供了操作界面,允许用户监控管理下载任务。该项目核心逻辑部分封装了多线程下载的具体实现。总体来说需要花更多时间来学习研究。

  • 30
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值