首页 > 编程语言 >C#设计:实现文件的多线程下载

C#设计:实现文件的多线程下载

时间:2024-06-20 23:29:13浏览次数:27  
标签:string C# private msg using 多线程 public 下载

一、程序设计要求

  1. 能够在下载过程中显示进度信息(如总大小、已下载大小、进度、下载速度、剩余大小、剩余时间、状态、下载的网址等)。
  2. 支持从指定的URL下载文件。
  3. 支持多线程并发下载文件。
  4. 提供友好的用户界面(UI)来下载。
  5. 具有良好的可扩展性,能够方便地添加新功能或修改现有功能。
  6. 代码结构清晰,易于理解和维护。

二、程序设计思路

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

相关文章

  • LeetCode热题100-第2题
    题目:49.字母异位词分组-力扣(LeetCode)给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。字母异位词 是由重新排列源单词的所有字母得到的一个新单词。示例1:输入:strs=["eat","tea","tan","ate","nat","bat"]输出:[["bat"],["......
  • Chip组件的用法
    文章目录概念介绍使用方法示例代码其它类Chip我们在上一章回中介绍了SliderWidget相关的内容,本章回中将介绍如何使用ChipWidget.闲话休提,让我们一起TalkFlutter吧。概念介绍本文中提到的ChipWidget在屏幕上显示一小块区域,类似悬浮的按钮,通常在网页类操作中比......
  • session、cookies、tonken以及JWT的定义以及区别
    session概述Session用于记录用户的状态。Session指的是一段时间内,单个客户端与Web服务器的一连串相关的交互过程。在一个Session中,客户可能会多次请求访问同一个资源,也有可能请求访问各种不同的服务器资源。Session是由服务器端创建的原理Session会为每一次会话分配一个S......
  • TCP报文详解
    简介其实协议的形式就是结构化的数据,TCP协议也是,它的报头格式如下TCP报头,实际上是一个结构化的数据,也就是一个结构体。例如:structtcp_hdr{unsignedintstc_port:16;unsignedintdesc_port:16;unsignedintseq;unsignedintack_seq;....}......
  • 你不知道的 CSS 之包含块
    你不知道的CSS之包含块一说到CSS盒模型,这是很多小伙伴耳熟能详的知识,甚至有的小伙伴还能说出border-box和content-box这两种盒模型的区别。但是一说到CSS包含块,有的小伙伴就懵圈了,什么是包含块?好像从来没有听说过这玩意儿。好吧,如果你对包含块的知识一无所知,那么系......
  • dockerfile 指令 ADD 和 COPY 的区别
     Dockerfile中的ADD和COPY指令都用于将文件或目录从构建上下文复制到Docker镜像中,但它们之间存在一些关键差异: 基本复制功能:COPY:纯粹地将从构建上下文(即执行dockerbuild命令时指定的目录及其子目录)中的文件或目录复制到镜像中指定的路径。ADD:除了具......
  • [C]可变参量,debugprint函数
    造冰箱的大熊猫@cnblogs2024/6/201、可变参量#include<stdio.h>#include<stdarg.h>voiddebugprint(constchar*format,...){va_listargs;va_start(args,format);printf(format,args);va_end(args);}intmain(){debugprint("Hell......
  • 序列化和反序列化pickle和json 模块
    importpicklehello='helloworld'data=pickle.dumps(hello)#pickle.dumps把任意对象序列化成一个bytes(字节数)print(data)data1=pickle.loads(data)#pickle.loads将字节数反序列化print(data1)importjsondata={'hello':123,'nihao':'word&......
  • 请编写一个函数fun,它的功能是:比较两个字符串的长度,(不得调用C语言提供的求字符串长度
    /*请编写一个函数fun,它的功能是:比较两个字符串的长度,(不得调用C语言提供的求字符串长度的函数)函数返回较长的字符串。若两个字符串长度相同,则返回第一个字符串。*/#include<stdio.h>char*fun(char*buff,char*str){intbuff_len=0,str_len=0;while(bu......
  • golang interface guard 技术(接口守护)
     Go代码的接口守卫(interfaceguards)技术,通常用于库的开发,以确保类型符合预期的接口。 下面示例的Interfaceguards确保App类型实现了caddy.App、caddy.Provisioner和caddy.Validator接口。具体来说,它通过将(*App)(nil)转换为这三个接口的类型,并将其赋值给匿名变量......