一、程序设计要求
- 能够在下载过程中显示进度信息(如总大小、已下载大小、进度、下载速度、剩余大小、剩余时间、状态、下载的网址等)。
- 支持从指定的URL下载文件。
- 支持多线程并发下载文件。
- 提供友好的用户界面(UI)来下载。
- 具有良好的可扩展性,能够方便地添加新功能或修改现有功能。
- 代码结构清晰,易于理解和维护。
二、程序设计思路
2.1用户界面(UI)设计
- 设计一个直观易用的图形用户界面,包含以下组件:
- 序号:下载文件的序号
- 文件名:下载的文件名称
- 总大小:所下载文件的总大小
- 已完成:显示已经完成的大小
- 进度:显示下载进度。
- 速度:显示下载速度。
- 剩余:显示剩余的大小。
- 时间:显示开始下载时间。
- 状态:显示当前下载的状态(如“正在下载”、“已暂停”、“已完成”等)。
- 网址:显示所下载的网站地址。
2.2下载流程设计
- 当用户点击下载按钮时,开始执行以下流程:
- 验证输入的URL是否有效。
- 发送HTTP GET请求到指定的URL,获取文件的元数据信息(包括文件总大小)。
- 根据获取到的文件总大小,初始化进度条和进度信息面板。
- 创建一个下载任务,开始接收服务器的响应数据,并将数据写入到本地的文件中。
- 在接收数据的过程中,实时更新进度条、已下载大小、下载速度、剩余大小等信息。
- 估算剩余时间(可以使用已下载数据量和平均下载速度来计算)。
- 当所有数据接收完毕后,标记下载状态为“已完成”,并关闭相关的下载资源。
2.3错误处理设计
- 在下载过程中,可能会遇到各种错误,如网络连接中断、下载路径有误、URL无效等。
- 当发生错误时,停止下载任务,并弹出错误信息。
三、窗体设计
根据设计思路设计窗体如下:
其中用到的主要控件及功能如下:
button:点击进行多线程下载任务。
listview:用于显示所下载文件的信息,将下载过程可视化。
四、代码实现
为了方便实现,将该程序分为4个大模块进行实现。
文件下载模块:主要实现连接下载地址和文件下载的处理
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using System.Net;
namespace Gac
{
public class FileDownloader
{
/// <summary>
/// 已下载文件长度
/// </summary>
private long downloadSize = 0;
/// <summary>
/// 原始文件长度
/// </summary>
private long fileSize = 0;
/// <summary>
/// 线程数
/// </summary>
private DownloadThread[] threads;
/// <summary>
/// 本地保存文件
/// </summary>
private string saveFile;
/// <summary>
/// 缓存各线程下载的长度
/// </summary>
public Dictionary<int, long> data = new Dictionary<int, long>();
/// <summary>
/// 每条线程下载的长度
/// </summary>
private long block;
/// <summary>
/// 下载路径
/// </summary>
private String downloadUrl;
/// <summary>
/// 获取线程数
/// </summary>
/// <returns> 获取线程数</returns>
public int getThreadSize()
{
return threads.Length;
}
/// <summary>
/// 获取文件大小
/// </summary>
/// <returns>获取文件大小</returns>
public long getFileSize()
{
return fileSize;
}
/// <summary>
/// 累计已下载大小
/// </summary>
/// <param name="size">累计已下载大小</param>
public void append(long size)
{
lock (this) //锁定同步..........
{
downloadSize += size;
}
}
/// <summary>
/// 更新指定线程最后下载的位置
/// </summary>
/// <param name="threadId">threadId 线程id</param>
/// <param name="pos">最后下载的位置</param>
public void update(int threadId, long pos)
{
if (data.ContainsKey(threadId))
{
this.data[threadId] = pos;
}
else
{
this.data.Add(threadId, pos);
}
}
/// <summary>
/// 构建下载准备,获取文件大小
/// </summary>
/// <param name="downloadUrl">下载路径</param>
/// <param name="fileSaveDir"> 文件保存目录</param>
/// <param name="threadNum">下载线程数</param>
public FileDownloader(string downloadUrl, string fileSaveDir,string filename="", int threadNum=3)
{
try
{
if (string.IsNullOrEmpty(filename))
{
filename = Uri.UnescapeDataString(Path.GetFileName(downloadUrl));//获取文件名称 uri 解码中文字符
}
//构建http 请求
this.downloadUrl = downloadUrl;
if (!Directory.Exists(fileSaveDir)) Directory.CreateDirectory(fileSaveDir);
this.threads = new DownloadThread[threadNum];
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(downloadUrl);
request.Referer = downloadUrl.ToString();
request.Method = "GET";
request.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.1124)";
request.ContentType = "application/octet-stream";
request.Accept = "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*";
request.Timeout = 20 * 1000;
request.AllowAutoRedirect = true;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode == HttpStatusCode.OK)
{
this.fileSize = response.ContentLength;//根据响应获取文件大小
if (this.fileSize <= 0) throw new Exception("获取文件大小失败");
if (filename.Length == 0) throw new Exception("获取文件名失败");
this.saveFile = Path.Combine(fileSaveDir, filename); //构建保存文件
//计算每条线程下载的数据长度
this.block = (this.fileSize % this.threads.Length) == 0 ? this.fileSize / this.threads.Length : this.fileSize / this.threads.Length + 1;
}
else
{
throw new Exception("服务器返回状态失败,StatusCode:" + response.StatusCode);
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
throw new Exception("无法连接下载地址");
}
}
/// <summary>
/// 开始下载文件
/// </summary>
/// <param name="listener">监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null</param>
/// <returns>已下载文件大小</returns>
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)
{//判断线程是否已经完成下载,否则继续下载 +
// Console.WriteLine("threads" + i.ToString() + ",下载块" + this.block.ToString() + " " + this.data[i + 1].ToString() + " " + downloadSize.ToString());
this.threads[i] = new DownloadThread(this, downloadUrl, this.saveFile, this.block, this.data[i + 1], i + 1);
this.threads[i].ThreadRun();
}
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;
}
}
}
多线程下载管理模块:主要实现多线程的管理,根据给定的线程数ThreadNum进行处理。
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);
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);
}
}
}
}
文件多线程下载处理模块:主要实现多线程的下载处理,根据给定的线程数ThreadNum进行处理,以同时下载指定线程数文件。
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
{
int startPos = (int)(block * (threadId - 1) + downLength);//开始位置
int endPos = (int)(block * threadId - 1);//结束位置
Console.WriteLine("Thread " + this.threadId + " start download from position " + startPos + " and endwith " + endPos);
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 = "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*";
request.Timeout = 10 * 1000;
request.AllowAutoRedirect = true;
request.AddRange(startPos, endPos);
//Console.WriteLine(request.Headers.ToString()); //输出构建的http 表头
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
WebResponse wb = request.GetResponse();
using (Stream _stream = wb.GetResponseStream())
{
byte[] buffer = new byte[1024 * 50]; //缓冲区大小
long offset = -1;
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)
{
//offset 实际下载流大小
downloader.append(offset); //更新已经下载当前总文件大小
threadfile.Write(buffer, 0, (int)offset);
downLength += offset; //设置当前线程已下载位置
downloader.update(this.threadId, downLength);
}
threadfile.Close(); //using 用完后可以自动释放..手动释放一遍.木有问题的(其实是多余的)
_stream.Close();
Console.WriteLine("Thread " + this.threadId + " download finish");
this.finish = true;
}
}
}
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;
}
}
}
下载过程监听模块:实现在下载过程中监听下载过程,处理数据以能够在下载过程中显示信息(如总大小、已下载大小、进度、下载速度、剩余大小、剩余时间、状态、下载的网址等)。
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Markup;
namespace Gac
{
public class DownloadProgressListener : IDownloadProgressListener
{
private long presize=0;
DownMsg downMsg = null;
public DownloadProgressListener(DownMsg downmsg)
{
this.downMsg = downmsg;
}
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
{
get
{
return _Speed;
}
set
{
_Speed = value;
SpeedInfo = GetFileSize(value);
}
}
public string SpeedInfo
{
get
{
return _SpeedInfo;
}
set
{
_SpeedInfo = value;
}
}
public float Surplus
{
get
{
return _Surplus;
}
set
{
_Surplus = value;
if (value>0)
{
SurplusInfo = GetDateName((int)Math.Round(value, 0));
}
}
}
public string ErrMessage
{
get
{
return _ErrMessage;
}
set
{
_ErrMessage = value;
}
}
public string SizeInfo
{
get
{
return _SizeInfo;
}
set
{
_SizeInfo = value;
}
}
public string LengthInfo
{
get
{
return _LengthInfo;
}
set
{
_LengthInfo = value;
}
}
public double Progress
{
get
{
return _Progress;
}
set
{
_Progress = value;
}
}
public string SurplusInfo
{
get
{
return _SurplusInfo;
}
set
{
_SurplusInfo = value;
}
}
private string GetFileSize(float Len)
{
float temp = Len;
string[] sizes = { "B", "KB", "MB", "GB" };
int order = 0;
while (temp >= 1024 && order + 1 < sizes.Length)
{
order++;
temp = temp / 1024;
}
return String.Format("{0:0.##} {1}", temp, sizes[order]);
}
private string GetDateName(int Second)
{
float temp = Second;
string suf = "秒";
if (Second>60)
{
suf = "分钟";
temp = temp / 60;
if (Second > 60)
{
suf = "小时";
temp = temp / 60;
if (Second > 24)
{
suf = "天";
temp = temp / 24;
if (Second > 30)
{
suf = "月";
temp = temp / 30;
if (Second > 12)
{
suf = "年";
temp = temp / 12;
}
}
}
}
}
return String.Format("{0:0} {1}", temp, suf);
}
}
}
最后,提供下载过程监听模块接口。
using System;
using System.Collections.Generic;
using System.Text;
namespace Gac
{
public interface IDownloadProgressListener
{
void OnDownloadSize(long size);
}
}
窗体设计代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using Gac;
namespace Demo
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
DownLoadFile dlf = new DownLoadFile();
private void btnTest_Click(object sender, EventArgs e)
{
string[] lines = File.ReadAllLines("C:\\Users\\k2699\\Desktop\\下载地址.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 = line[0];
string dir = @"C:\Users\k2699\Desktop";
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, filename);
}
}
dlf.StartDown();
}
private void Form1_Load(object sender, EventArgs e)
{
dlf.ThreadNum = 3;//线程数,不设置默认为3
dlf.doSendMsg += SendMsgHander;//下载过程处理事件
}
private void SendMsgHander(DownMsg msg)
{
switch (msg.Tag)
{
case DownStatus.Start:
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[8].Text = "开始下载";
listView1.Items[msg.Id].SubItems[7].Text = DateTime.Now.ToString();
});
break;
case DownStatus.GetLength:
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[8].Text = "连接成功";
});
break;
case DownStatus.End:
case DownStatus.DownLoad:
this.Invoke(new MethodInvoker(() =>
{
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[2].Text = msg.LengthInfo;
listView1.Items[msg.Id].SubItems[3].Text = msg.SizeInfo;
listView1.Items[msg.Id].SubItems[4].Text = msg.Progress.ToString() + "%";
listView1.Items[msg.Id].SubItems[5].Text = msg.SpeedInfo;
listView1.Items[msg.Id].SubItems[6].Text = msg.SurplusInfo;
if (msg.Tag == DownStatus.DownLoad)
{
listView1.Items[msg.Id].SubItems[8].Text = "下载中";
}
else
{
listView1.Items[msg.Id].SubItems[8].Text = "下载完成";
}
Application.DoEvents();
});
}));
break;
case DownStatus.Error:
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[6].Text = "失败";
listView1.Items[msg.Id].SubItems[8].Text = msg.ErrMessage;
Application.DoEvents();
});
break;
}
}
}
}
在窗体设计中,通过点击开始下载按钮,调用以上多线程下载模块进行文件下载,同时将下载信息更新到listview控件中,实现下载过程的监听。
五、效果演示
该程序设计通过读取指定的文件下载地址txt文件,进行下载任务。
我们先创建一个下载地址.txt文件,该文件中每一行代表需要下载的一个任务,第一项是文件名称,后面用"|"分割,输入下载地址。
例如,我创建以下文件进行下载演示,指定下载到桌面。
下载过程如下,因为线程数设置为3,所以一次可以同时进行3个文件的下载,当有一个下载完成,就会进行下一个文件的下载,保证3个文件的多线程下载。
可见文件成功下载并保存到了我在程序中的指定位置。
标签:string,C#,private,msg,using,多线程,public,下载 From: https://blog.csdn.net/weixin_46204349/article/details/139843945