对于网站开发来说,下载文件一般是比较非常容易的,但是对于上传文件来说,上传大文件是比较困难的,比如上传几百M或几个G的文件。但是对于客户端来说,实现大文件的上传是比较容易的。由于本人在工作中遇到大文件上传的情景比较多,所以就决定写一个 Demo 总结一下客户端实现大文件上传和下载的技术代码,以便后续需要使用时,能够快速找到并提高工作效率。

本篇博客的 Demo 采用基于 .NET5 开发的 Asp.net Core 网站作为服务端提供 Http 接口。客户端采用 WinForm 开发,通过调用 Http 接口实现文件的上传和下载。之所以选择 Asp.net Core 网站作为接口服务端的开发,是因为它的部署比较灵活,可以采用 Windows Service 服务进行部署,开发的时候可以采用控制台进行测试。有关将控制台程序直接安装成 Windows Service 的方法,可以参考我之前编写的博客。在本博客的最后会提供源代码的下载。


一、搭建接口服务

新建一个基于 .NET5 的 Asp.net Core 网站,appsettings.json 配置文件内容如下:

{
  //web访问端口
  "WebPort": "http://*:9527",

  //文件上传的目录名称,是网站下的一个目录,该目录必须要存在
  "UploadPath": "UploadFiles"
}

建议网站采用控制台进行启动,这样开发调试比较方便。然后新建一个 CommonBLL 类,用来读取配置文件:

using Microsoft.Extensions.Configuration;
using System;
using System.IO;

namespace ServerDemo.Models
{
    public static class CommonBLL
    {
        /// <summary>
        /// 获取配置信息
        /// </summary>
        public static IConfiguration Config;

        /// <summary>
        /// 文件上传的根路径(在本 Demo 中,要求上传的文件必须在网站的目录下)
        /// </summary>
        public static string uploadRootPath;

        //静态构造函数
        static CommonBLL()
        {
            //Environment.CurrentDirectory 代表网站程序的根目录

            Config = (new ConfigurationBuilder())
                .SetBasePath(Environment.CurrentDirectory)
                .AddJsonFile("appsettings.json", false, true).Build();

            //为了确保文件上传的目录存在,所以在启动时就自动创建
            uploadRootPath = Path.Combine(Environment.CurrentDirectory, Config["UploadPath"]);
            if (Directory.Exists(uploadRootPath) == false)
            {
                Directory.CreateDirectory(uploadRootPath);
            }
        }

    }
}

本博客的 Demo 要求上传的文件,都保存在网站下面的一个固定的文件夹中,并在网站启动时会自动判断该文件夹是否存在,如果不存在的话,就提前创建出来,方便后续实用该文件夹保存所有上传的文件。

然后在网站启动时,使用配置文件中所配置的端口(如果不配置的话,网站默认使用 5000 端口)。

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using ServerDemo.Models;

namespace ServerDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    //采用 appsettings.json 配置文件中自定义的端口启动 http 服务
                    webBuilder.UseUrls(CommonBLL.Config["WebPort"]).UseStartup<Startup>();
                });
    }
}

由于本网站只是提供 Http 接口,因此在 ConfigureServices 中只需要使用 services.AddControllers() 即可。

另外上传和下载文件,Asp.net Core 默认情况下是异步的,我们最好让它也支持同步的方式,具体细节如下:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;

namespace ServerDemo
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            //这里只提供接口就可以了,不需要页面
            services.AddControllers();

            //允许同步 IO 调用
            services.Configure<KestrelServerOptions>(options =>
            {
                //如果部署在非 iis 上的话,使用的是 kestrel 提供 http 服务
                //比如以控制台启动,或者部署成 WindowsService,或者在 linux 上部署
                options.AllowSynchronousIO = true;
            });

            services.Configure<IISServerOptions>(options =>
            {
                //如果部署在 iis 上,使用的是 iis 引擎
                options.AllowSynchronousIO = true;
            });
        }


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseDeveloperExceptionPage();
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=API}/{action=Index}");
            });
        }
    }
}

最后开发一个 APIController 提供 Http 接口即可,内容如下:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders;
using Newtonsoft.Json;
using ServerDemo.Models;
using System;
using System.Data;
using System.IO;
using System.Text;

namespace ServerDemo.Controllers
{
    public class APIController : Controller
    {

        public IActionResult Index()
        {
            return Content(
               $"Asp.Net Core Web 正在运行... {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
        }

        /// <summary>
        /// 将文件大小转换成可读性好的格式
        /// </summary>
        private string GetSize(long size)
        {
            long k = 1024;
            long m = k * 1024;
            long g = m * 1024;
            long t = g * 1024;

            if (size < k)
            {
                return size.ToString() + "b";
            }
            else if (size < m)
            {
                return ((size * 1.00) / k).ToString("F2").Replace(".00", "") + "KB";
            }
            else if (size < g)
            {
                return ((size * 1.00) / m).ToString("F2").Replace(".00", "") + "MB";
            }
            else if (size < t)
            {
                return ((size * 1.00) / g).ToString("F2").Replace(".00", "") + "GB";
            }
            else
            {
                return ((size * 1.00) / t).ToString("F2").Replace(".00", "") + "TB";
            }
        }

        /// <summary>
        /// 获取一个路径下所有的文件
        /// </summary>
        public string GetAllFile()
        {
            DataTable dt = new DataTable("server");
            dt.Columns.Add("FileName");
            dt.Columns.Add("UpdateTime");
            dt.Columns.Add("DataSize");

            string[] fileArr = Directory.GetFiles(CommonBLL.uploadRootPath);
            if (fileArr.Length > 0)
            {
                FileInfo fi;
                foreach (string file in fileArr)
                {
                    fi = new FileInfo(file);
                    dt.Rows.Add(Path.GetFileName(file),
                       fi.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss"), GetSize(fi.Length));
                }
            }

            return JsonConvert.SerializeObject(dt);
        }

        /// <summary>
        /// 获取文件的字节总数
        /// </summary>
        public string GetFileSize()
        {
            string data = string.Empty;
            using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
            {
                data = sr.ReadToEnd();
            }

            dynamic obj = JsonConvert.DeserializeObject(data);
            string filename = obj.f;
            string path = Path.Combine(CommonBLL.uploadRootPath, filename);
            if (System.IO.File.Exists(path))
            {
                FileInfo fi = new FileInfo(path);
                return fi.Length.ToString();
            }
            else
            {
                return "0";
            }
        }

        /// <summary>
        /// 分批获取文件的字节码
        /// </summary>
        public string GetFileByte()
        {
            string data = string.Empty;
            using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
            {
                data = sr.ReadToEnd();
            }

            dynamic obj = JsonConvert.DeserializeObject(data);
            string filename = obj.f;
            int sizeB = obj.sb;
            long sequence = obj.sq;
            int end = obj.ef;

            try
            {
                string path = Path.Combine(CommonBLL.uploadRootPath, filename);

                //以文件的全路径对应的字符串和文件打开模式来初始化FileStream文件流实例
                using (FileStream SplitFileStream = new FileStream(path, FileMode.Open))
                {
                    //每次分割读取的最大数据
                    SplitFileStream.Seek(sizeB * sequence, SeekOrigin.Current);

                    if (end == 1)
                    {
                        FileInfo fi = new FileInfo(path);
                        long totalsize = fi.Length;
                        int lastsize = (int)(totalsize - sizeB * sequence);
                        byte[] TempBytes = new byte[lastsize];
                        SplitFileStream.Read(TempBytes, 0, lastsize);
                        return Convert.ToBase64String(TempBytes);
                    }
                    else
                    {
                        byte[] TempBytes = new byte[sizeB];
                        //从大文件中读取指定大小数据
                        SplitFileStream.Read(TempBytes, 0, (int)sizeB);
                        return Convert.ToBase64String(TempBytes);
                    }
                }
            }
            catch
            {
                throw;
            }
        }

        /// <summary>
        /// 获取文件的所有字节码
        /// </summary>
        public string GetFileByteAll()
        {
            string data = string.Empty;
            using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
            {
                data = sr.ReadToEnd();
            }

            dynamic obj = JsonConvert.DeserializeObject(data);
            string filename = obj.f;
            string path = Path.Combine(CommonBLL.uploadRootPath, filename);

            using (FileStream SplitFileStream = new FileStream(path, FileMode.Open))
            {
                SplitFileStream.Seek(0, SeekOrigin.Current);
                FileInfo fi = new FileInfo(path);
                int totalsize = (int)fi.Length;
                byte[] TempBytes = new byte[totalsize];
                SplitFileStream.Read(TempBytes, 0, totalsize);
                return Convert.ToBase64String(TempBytes);
            }
        }

        /// <summary>
        /// 分批上传文件
        /// </summary>
        public void UpLoadFileByte()
        {
            string data = string.Empty;
            using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
            {
                data = sr.ReadToEnd();
            }

            dynamic obj = JsonConvert.DeserializeObject(data);
            string filename = obj.f;
            byte[] fileBlock = obj.fb;
            int start = obj.flag;

            string path = Path.Combine(CommonBLL.uploadRootPath, filename);

            if (start == 1)
            {
                using (FileStream TempStream = new FileStream(path, FileMode.Create))
                {
                    TempStream.Write(fileBlock, 0, fileBlock.Length);
                }
            }
            else
            {
                using (FileStream TempStream = new FileStream(path, FileMode.Append))
                {
                    TempStream.Write(fileBlock, 0, fileBlock.Length);
                }
            }
        }

        /// <summary>
        /// 上传文件
        /// </summary>
        public void UpLoadFileByteAll()
        {
            string data = string.Empty;
            using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
            {
                data = sr.ReadToEnd();
            }

            dynamic obj = JsonConvert.DeserializeObject(data);
            string filename = obj.f;
            byte[] fileBlock = obj.fb;

            string path = Path.Combine(CommonBLL.uploadRootPath, filename);

            string dir = Path.GetDirectoryName(path);
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }

            using (FileStream TempStream = new FileStream(path, FileMode.Create))
            {
                TempStream.Write(fileBlock, 0, fileBlock.Length);
            }
        }

        /// <summary>
        /// 删除文件
        /// </summary>
        public void DeleteFile()
        {
            string data = string.Empty;
            using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
            {
                data = sr.ReadToEnd();
            }

            dynamic obj = JsonConvert.DeserializeObject(data);
            string filename = obj.f;

            string path = Path.Combine(CommonBLL.uploadRootPath, filename);
            if (System.IO.File.Exists(path))
            {
                System.IO.File.Delete(path);
            }
        }

        /// <summary>
        /// 敲链接下载单个文件
        /// </summary>
        public IActionResult DownLoadFile(string fileName)
        {
            var provider = new PhysicalFileProvider(CommonBLL.uploadRootPath);
            var fileInfo = provider.GetFileInfo(fileName);
            var readStream = fileInfo.CreateReadStream();
            return File(readStream, "application/octet-stream", fileName);
        }
    }
}

到此为止,Asp.net Core 服务端网站已经搭建完毕。需要注意的是:我们上面所提供的接口,都是读取提交过来的 body 中的 Json 数据。这就要求客户端在调用接口的时候,要使用 Post 请求并提交 Json 数据。因此无论是客户端和服务端,都需要通过 Nuget 安装 Newtonsoft.json 包。


二、搭建客户端

客户端采用 WinForm 进行开发,实现对服务器上的文件进行上传、下载、删除和获取文件列表功能。

对于客户端来说,只需要配置要请求的接口地址即可:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <!--路径不要以斜线结尾-->
        <add key="ServerUrl" value="http://localhost:9527/api"/>
    </appSettings>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>

然后我们先提前创建一个 CommonBLL 类,采用 WebClient 发送 Post 请求实现每一个接口方法的调用封装,具体细节如下:

using Newtonsoft.Json;
using System;
using System.Configuration;
using System.Data;
using System.Net;
using System.Text;
using System.Windows.Forms;

namespace ClientDemo
{
    public static class CommonBLL
    {
        //封装了一些信息弹框,方便在 WinForm 的界面中使用
        public static void MessageAlert(string str)
        {
            MessageBox.Show(str, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        }
        public static bool MessageConfirm(string str)
        {
            return MessageBox.Show(str, "提示信息",
                   MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK;
        }
        public static void MessageSuccess(string str)
        {
            MessageBox.Show(str, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
        }
        public static void MessageError(string str)
        {
            MessageBox.Show(str, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Hand);
        }


        public static WebClient CreateWebClient()
        {
            WebClient wc = new WebClient();
            wc.Encoding = Encoding.UTF8;
            wc.Headers.Add("Content-Type", "application/json");
            return wc;
        }

        //读取配置节中的接口地址
        private static string serverurl = ConfigurationManager.AppSettings["ServerUrl"];


        //下面这些方法是对网站提供的每个接口的调用封装

        public static DataTable GetAllFile()
        {
            using (WebClient wc = CreateWebClient())
            {
                string result = wc.UploadString(serverurl + "/GetAllFile", string.Empty);
                if (!string.IsNullOrWhiteSpace(result))
                {
                    DataTable dt = JsonConvert.DeserializeObject<DataTable>(result);
                    return dt;
                }
                else
                {
                    return null;
                }
            }
        }

        public static long GetFileSize(string filename)
        {
            using (WebClient wc = CreateWebClient())
            {
                var model = new { f = filename };
                string json = JsonConvert.SerializeObject(model);
                string result = wc.UploadString(serverurl + "/GetFileSize", json);
                return long.Parse(result);
            }
        }

        public static byte[] GetFileByte(string filename, int sizeB, int sequence, bool end)
        {
            int endflag = end ? 1 : 0;
            using (WebClient wc = CreateWebClient())
            {
                var model = new { f = filename, sb = sizeB, sq = sequence, ef = endflag };
                string json = JsonConvert.SerializeObject(model);
                string result = wc.UploadString(serverurl + "/GetFileByte", json);
                return Convert.FromBase64String(result);
            }
        }

        public static byte[] GetFileByte(string filename)
        {
            using (WebClient wc = CreateWebClient())
            {
                var model = new { f = filename };
                string json = JsonConvert.SerializeObject(model);
                string result = wc.UploadString(serverurl + "/GetFileByteAll", json);
                return Convert.FromBase64String(result);
            }
        }

        public static void UpLoadFileByte(string filename, byte[] fileBlock, bool start)
        {
            int startflag = start ? 1 : 0;
            using (WebClient wc = CreateWebClient())
            {
                var model = new { f = filename, fb = fileBlock, flag = startflag };
                string json = JsonConvert.SerializeObject(model);
                wc.UploadString(serverurl + "/UpLoadFileByte", json);
            }
        }

        public static void UpLoadFileByte(string filename, byte[] fileBlock)
        {
            using (WebClient wc = CreateWebClient())
            {
                var model = new { f = filename, fb = fileBlock };
                string json = JsonConvert.SerializeObject(model);
                wc.UploadString(serverurl + "/UpLoadFileByteAll", json);
            }
        }

        public static void DeleteFile(string filename)
        {
            using (WebClient wc = CreateWebClient())
            {
                var model = new { f = filename };
                string json = JsonConvert.SerializeObject(model);
                wc.UploadString(serverurl + "/DeleteFile", json);
            }
        }
    }
}

下面只列出上传文件和下载文件的代码,具体的逻辑就是:当上传或者下载的文件小于等于 2M 的话,就直接上传或下载,如果大于 2M 的话,就分批次进行上传或下载,每批次上传或下载的数据量为 2M ,具体细节如下:

//自己开发的用于显示进度条的窗体
private ProgressForm pf;

/// <summary>
/// 上传文件
/// </summary>
private void btnUpload_Click(object sender, EventArgs e)
{
    OpenFileDialog ofd = new OpenFileDialog();
    ofd.Title = "上传单个文件";
    if (ofd.ShowDialog() == DialogResult.OK)
    {
        //每次需要上传的字节数 2M
        long num = 2097152L;
        //获取所选择的文件全路径名
        string fullname = ofd.FileName;
        //获取文件名(仅仅是文件名,包含扩展名)
        string fname = Path.GetFileName(fullname);

        FileInfo fileInfo = new FileInfo(fullname);
        //获取要上传的文件的总字节数大小
        long flen = fileInfo.Length;

        if (flen <= num)
        {
            //如果字节数小于等于 2M ,则直接上传文件

            byte[] array = new byte[flen];
            using (FileStream fileStream = new FileStream(fullname, FileMode.Open))
            {
                fileStream.Read(array, 0, (int)flen);
            }
            CommonBLL.UpLoadFileByte(fname, array);
            CommonBLL.MessageSuccess("文件上传成功");
            BindData(fname);
        }
        else
        {
            //如果字节数大于 2M ,则分批次上传,每次上传 2M

            int blocknum = (int)(flen / num);
            if (flen % num != 0L)
            {
                blocknum++;
            }
            pf = new ProgressForm();
            pf.TopMost = true;
            pf.ProgressMaxValue = blocknum;
            pf.ProgressValue = 0;
            pf.Show();

            bwUpSingBigFile = new BackgroundWorker();
            bwUpSingBigFile.WorkerReportsProgress = true;
            bwUpSingBigFile.DoWork +=
                new DoWorkEventHandler(this.bwUpSingBigFile_DoWork);
            bwUpSingBigFile.ProgressChanged +=
                new ProgressChangedEventHandler(this.bwUpSingBigFile_ProgressChanged);
            bwUpSingBigFile.RunWorkerCompleted +=
                new RunWorkerCompletedEventHandler(this.bwUpSingBigFile_RunWorkerCompleted);

            Dictionary<string, object> dicparam = new Dictionary<string, object>();
            dicparam.Add("LocalFileName", fullname); //该参数是本地文件名,这里参数是全路径名
            dicparam.Add("ServerFileName", fname); //在服务器上保存的文件名称,该参数仅仅是文件名
            dicparam.Add("TotalFlieSize", flen);
            dicparam.Add("eachBlockSize", num);
            dicparam.Add("FileBlockCount", blocknum);
            this.bwUpSingBigFile.RunWorkerAsync(dicparam);
        }
    }
}


#region 上传文件后台线程

private BackgroundWorker bwUpSingBigFile;

private void bwUpSingBigFile_DoWork(object sender, DoWorkEventArgs e)
{
    Dictionary<string, object> dicparam = e.Argument as Dictionary<string, object>;
    //该参数是本地文件名,这里参数是全路径名
    string clientfile = dicparam["LocalFileName"].ToString();
    //在服务器上保存的文件名称,该参数仅仅是文件名
    string serverfile = dicparam["ServerFileName"].ToString();
    long totalsize = long.Parse(dicparam["TotalFlieSize"].ToString());
    int eachsize = int.Parse(dicparam["eachBlockSize"].ToString());
    int blocknum = int.Parse(dicparam["FileBlockCount"].ToString());
    using (FileStream fileStream = new FileStream(clientfile, FileMode.Open))
    {
        for (int i = 0; i < blocknum; i++)
        {
            if (i == 0)
            {
                byte[] array = new byte[eachsize];
                fileStream.Read(array, 0, eachsize);
                CommonBLL.UpLoadFileByte(serverfile, array, true);
            }
            else
            {
                if (i == blocknum - 1)
                {
                    int leftnum = (int)(totalsize - eachsize * i);
                    byte[] array = new byte[leftnum];
                    fileStream.Read(array, 0, leftnum);
                    CommonBLL.UpLoadFileByte(serverfile, array, false);
                }
                else
                {
                    byte[] array = new byte[eachsize];
                    fileStream.Read(array, 0, eachsize);
                    CommonBLL.UpLoadFileByte(serverfile, array, false);
                }
            }
            bwUpSingBigFile.ReportProgress(i + 1);
        }
    }

    e.Result = serverfile; //要上传的文件名,不是全路径名
}

private void bwUpSingBigFile_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    int progressPercentage = e.ProgressPercentage;
    pf.ProgressValue = progressPercentage;
}

private void bwUpSingBigFile_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    pf.Close();

    if (e.Error != null)
    {
        CommonBLL.MessageError(e.Error.Message);
    }
    else
    {
        CommonBLL.MessageSuccess("上传成功");
        BindData(e.Result.ToString());
    }
}

#endregion


/// <summary>
/// 下载文件
/// </summary>
private void tsmDownload_Click(object sender, EventArgs e)
{
    //获取要下载的文件名
    string serverFileName = dgvFile.SelectedRows[0].Cells["FileName"].Value.ToString();

    SaveFileDialog sfd = new SaveFileDialog();
    sfd.FileName = serverFileName;
    if (sfd.ShowDialog() == DialogResult.OK)
    {
        //要保存的文件名(全路径名)
        string localFileName = sfd.FileName;

        //每次下载的字节数 2M
        long num = 2097152L;

        //获取要下载的文件的总字节数大小
        long fileSize = CommonBLL.GetFileSize(serverFileName);

        if (fileSize <= num)
        {
            //所下载的文件小于等于 2M ,则直接下载

            byte[] fileByte = CommonBLL.GetFileByte(serverFileName);
            using (FileStream fileStream = new FileStream(localFileName, FileMode.Create))
            {
                fileStream.Write(fileByte, 0, fileByte.Length);
            }
            CommonBLL.MessageSuccess("文件下载成功");
        }
        else
        {
            //所下载的文件大于 2M ,则分批次下载,每次下载 2M

            int maxnum = (int)(fileSize / num);
            if (fileSize % num != 0L)
            {
                maxnum++;
            }

            pf = new ProgressForm();
            pf.TopMost = true;
            pf.ProgressMaxValue = maxnum;
            pf.ProgressValue = 0;
            pf.Show();

            bwDownSingBigFile = new BackgroundWorker();
            bwDownSingBigFile.WorkerReportsProgress = true;
            bwDownSingBigFile.DoWork +=
                new DoWorkEventHandler(bwDownSingBigFile_DoWork);
            bwDownSingBigFile.ProgressChanged +=
                new ProgressChangedEventHandler(bwDownSingBigFile_ProgressChanged);
            bwDownSingBigFile.RunWorkerCompleted +=
                new RunWorkerCompletedEventHandler(bwDownSingBigFile_RunWorkerCompleted);

            Dictionary<string, object> dicparam = new Dictionary<string, object>();
            dicparam.Add("ServerFileName", serverFileName); //服务器上的文件名,仅仅是文件名
            dicparam.Add("SaveFileName", localFileName); //要保存到本地的文件名,全路径名
            dicparam.Add("FileBlockCount", maxnum);
            dicparam.Add("eachBlockSize", num);
            bwDownSingBigFile.RunWorkerAsync(dicparam);
        }
    }
}


#region 下载单个大文件

private BackgroundWorker bwDownSingBigFile;

private void bwDownSingBigFile_DoWork(object sender, DoWorkEventArgs e)
{
    Dictionary<string, object> dicparam = e.Argument as Dictionary<string, object>;
    //要保存到本地的文件名,全路径名
    string localFileName = dicparam["SaveFileName"].ToString();
    //服务器上的文件名,仅仅是文件名
    string serverFileName = dicparam["ServerFileName"].ToString();
    int num = int.Parse(dicparam["FileBlockCount"].ToString());
    int sizeB = int.Parse(dicparam["eachBlockSize"].ToString());
    using (FileStream fileStream = new FileStream(localFileName, FileMode.Create))
    {
        for (int i = 0; i < num; i++)
        {
            byte[] fileByte;
            if (i == num - 1)
            {
                fileByte = CommonBLL.GetFileByte(serverFileName, sizeB, i, true);
            }
            else
            {
                fileByte = CommonBLL.GetFileByte(serverFileName, sizeB, i, false);
            }
            fileStream.Write(fileByte, 0, fileByte.Length);
            bwDownSingBigFile.ReportProgress(i + 1);
        }
    }
}

private void bwDownSingBigFile_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    int progressPercentage = e.ProgressPercentage;
    pf.ProgressValue = progressPercentage;
}

private void bwDownSingBigFile_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    pf.Close();
    if (e.Error != null)
    {
        CommonBLL.MessageError(e.Error.Message);
    }
    else
    {
        CommonBLL.MessageSuccess("下载成功");
    }
}

#endregion

大于大文件的上传和下载,采用的是 BackgroundWorker 异步线程处理的,因为它自带了一些事件比较好用。

DoWork 事件用于异步处理业务逻辑,在本 Demo 上实现异步后台处理文件的上传和下载。

ProgressChanged 事件用于更新进度条,对于大文件的上传和下载,有进度条的话,体验会好很多。

RunWorkerCompleted 事件用于异步后台业务处理完成后的回调,使我们能够在文件上传和下载完成后给予用户一些提示信息。


到此为止,关键核心点已经介绍完毕,详细的细节请参看源代码吧。