C#之WCF服务实现大文件上传(分布式开发)第一集

此项目主要实现:在服务器端上传大文件

使用的技术点:
文件流读写、进度条、多线程、WCF服务接口的实现和回调
文件服务接口
3个功能:
1.文件准备上传
2.文件传输(上传按钮)
3.文件传输终止
1.搭建客户端和服务器端环境:
本项目使用VS2017开发,废话不说直接干。
首先创建服务器端CRMServer:
在这里插入图片描述
在这里插入图片描述
然后我们把生成的服务文件删除,以及App.Config中server信息删除。

2.添加文件模型类FileModel:封装上传文件的具体信息

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;
namespace CRMServer
{
    /// <summary>
    /// 文件模型类:用来封装上传文件的信息
    /// 文件传输的特点:
    //不是一下全部传输完毕,而是按指定的大小(比如100kb/秒)上传多次
    /// </summary>

 [Serializable]//序列化处理
 [DataContract]//数据契约
  public  class FileModel
    {

        /// <summary>
        /// 文件编号(GUID)--W唯一标识
        /// </summary>
        [DataMember]
        public string FieldId { get; set; }

        /// <summary>
        /// 文件名称
        /// </summary>
        [DataMember]
        public string FileName { get; set; }

        /// <summary>
        /// 文件全路径
        /// </summary>
        [DataMember]
        public string FileFullPath { get; set; }

        /// <summary>
        /// 文件总大小(以字节为单位)
        /// </summary>
        [DataMember]
        public long FileSize { get; set; }

        /// <summary>
        /// 字符数据(每次传输的大小):
        /// 可能会添加一下属性数据(添加的数据),真正上传的数据+人为添加的数据
        /// </summary>
        [DataMember]
        public byte[] FileBytes { get; set; }

        /// <summary>
        ///每次传输的长度 
        /// </summary>
        [DataMember]
        public int FileLength { get; set; }

        /// <summary>
        /// 文件后缀名称
        /// </summary>
        [DataMember]
        public string FileSuffixName { get; set; }
    }
}

自定义文件服务接口并添加方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace CRMServer
{
    // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IFileService”。

    /// <summary>
    /// 文件服务接口
    /// </summary>
    [ServiceContract(CallbackContract =typeof(ICallback))]//回调契约
    public interface IFileService
    {   
        /// <summary>
        /// 文件上传(准备)
        /// </summary>
        [OperationContract(IsOneWay =true)] 
        void FileUpload(FileModel fileModel);

        /// <summary>
        /// 文件传输
        /// </summary>
        [OperationContract(IsOneWay =true)]
        void FileTransfer(FileModel fileModel);

        /// <summary>
        /// 文件终止(取消上传   )
        /// </summary>
        [OperationContract(IsOneWay =true)]
        void FileStop(FileModel fileModel);
    }

    public interface ICallback {//回调接口

        /// <summary>
        /// 已上传文件的大小
        /// </summary>
        /// <param name="fileSize"></param>
        [OperationContract(IsOneWay =true)]
        void ToFileSize(long fileSize);
    }
}

实现服务类FileService:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace CRMServer
{
    // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的类名“FileService”。
    public class FileService : IFileService
    {
        
        //保存上传的文件列表
        Dictionary<string, FileStream> dict = new Dictionary<string, FileStream>();

        /// <summary>
        /// 文件上传
        /// </summary>
        /// <param name="fileModel"></param>
        public void FileUpload(FileModel fileModel)
        {
            try {
                string path = "F:/FileUpload/" + fileModel.FileName;
                FileStream fileStream = new FileStream(path, FileMode.Create);
                dict.Add(fileModel.FieldId, fileStream);
            }
            catch (Exception ex) {
                Console.WriteLine(ex.Message+"--->"+ex.StackTrace);
            } 
        }
        /// <summary>
        /// 文件传输:多次按指定的大小进行传输:
        /// 在客户端循环处理,实现多次传输数据到服务端-->客户端可随时停止上传
        /// </summary>
        /// <param name="fileModel"></param>
        public void FileTransfer(FileModel fileModel)
        {
            try {
                string fileId = fileModel.FieldId;
                FileStream fileStream = dict[fileId];
                fileStream.Write(fileModel.FileBytes, 0, fileModel.FileLength);//实现:写入到服务器端的磁盘文件中
                 //回调客户端,通知写入成功
                ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>();
                callback.ToFileSize(fileModel.FileBytes.Length);//调用回调方法,通知客户端
                                                                //fileStream.Length:每传输一次,Length递增一次
                if (fileStream.Length >= fileModel.FileSize)
                {//校验是否已全部传输完毕
                 //自动关闭文件
                    if (fileStream!=null) {
                        fileStream.Close();//关闭流资源
                    } 
                    dict.Remove(fileModel.FieldId);//从服务器端移除文件
                }
            }
            catch (Exception ex) {
                Console.WriteLine(ex.Message+"-->"+ex.StackTrace);
            }
        }
        /// <summary>
        /// 文件终止(取消上传):手动关闭
        /// </summary>
        /// <param name="fileModel"></param>
        public void FileStop(FileModel fileModel)
        {
            try {
                string fileId = fileModel.FieldId;
                FileStream fs = dict[fileId];
                if (fs != null)
                {
                    fs.Close();
                }
                dict.Remove(fileId);
            }
            catch (Exception ex) {
                Console.WriteLine(ex.Message+"--->"+ex.StackTrace);
            } 
        }    
    }
}

3.创建客户端CRMClient WinForm项目:
在这里插入图片描述
添加窗体MainForm作为主页面:
在这里插入图片描述
两个Button:浏览和上传按钮,以及一个TextBox文本框:获取上传文件的路径。
对应的底层代码:

using System;
using System.Windows.Forms;
using CRMClient.FileServer;
namespace CRMClient
{
    public partial class MainForm : Form
    {
        string fileName;//文件路径
        public MainForm()
        {
            InitializeComponent();
            this.ShowIcon = false;
        }

        /// <summary>
        /// 浏览按钮点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnBrowse_Click(object sender, EventArgs e)
        {
            OpenFile();
        }


        /// <summary>
        /// 文本框点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void txtUploadFile_Click(object sender, EventArgs e)
        {
            OpenFile();
        }

        /// <summary>
        /// 上传按钮点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnUploadFile_Click(object sender, EventArgs e)
        {
            FileUploadForm fileUploadForm = new FileUploadForm(fileName);
            fileUploadForm.CloseFormAction += FileUploadForm_CloseFormAction;
            fileUploadForm.TopMost = true;
            fileUploadForm.Show();
        }

        /// <summary>
        /// 关闭上传窗体时的回调函数
        /// </summary>
        /// <param name="obj"></param>
        private void FileUploadForm_CloseFormAction(FileModel fileModel)
        {
            if (fileModel!=null) {
                MessageBox.Show($"{fileModel.FileName}文件上传成功!");
                this.Close();
            }
        }
        /// <summary>
        /// 文件上传对话框:
        /// </summary>
        private void OpenFile() {
          OpenFileDialog ofd = new OpenFileDialog();
          ofd.Filter = "JPEG文件|*.jpeg*|JPG文件|*.jpg*|PNG文件|*.png*|GIF文件|*.gif*|BMP文件|*.bmp*|文本文件|*.txt*|文档文件|*.doc*|所有类型文件|*.*";//限制上传的文件类型
          DialogResult result= ofd.ShowDialog();
          if (result==DialogResult.OK) {//确认选中文件
             fileName= ofd.FileName;//全路径
             this.txtUploadFile.Text = fileName;
            //string name=Path.GetFileNameWithoutExtension(fileName);
            //string extensionName = Path.GetExtension(fileName);
            //string uploadedFileName = name + extensionName;
            }
        }
    }
}

主页面实现的功能比较简单,就是选择上传的文件,然后打开文件上传FileUploadForm页面,等文件上传成功,得到回馈信息。
添加文件上传FileUploadForm窗体:显示文件上传的进度
在这里插入图片描述
页面上的控件:4个label,Progressbar进度条以及取消上传按钮。
底层代码:

using CRMClient.FileServer;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.ServiceModel;

namespace CRMClient
{
    public partial class FileUploadForm : Form
    {
        string filePath;
        FileModel fileModel;
        FileServiceClient fsc;
        BackgroundWorker bw;//线程对象
        public FileUploadForm()
        {
            InitializeComponent();
        }

        public FileUploadForm(string filePath)
        {
            InitializeComponent();
            this.filePath = filePath;
            bw = new BackgroundWorker();//创建线程对象
            //上传文件
            UploadFile();
        }

        string fileSizeValStr;
        private void FileUploadForm_Load(object sender, EventArgs e)
        {
            //b ->MB:默认为long类型(长整形),如果文件比较小-->0MB
            //实际上并不为0(不是空文件)-->带小数位的数据类型(double)
            if (fileModel != null)
            {
                double fileSizeDouble = Convert.ToDouble(fileModel.FileSize);
                double fileSize = fileSizeDouble / 1024 / 1024;//MB
                double fileSizeVal = Math.Round(fileSize, 2);
                fileSizeValStr = fileSizeVal.ToString() + " MB";
                this.lblFileSize.Text = fileSizeValStr;
                this.pbFileUpload.Maximum = 100;
                this.pbFileUpload.Minimum = 0;
            }

            //使用单独的线程去处理进度条:
            //如果直接在当前页面所在线程(主线程)直接处理,整个页面会卡死,无法对当前页面进行任何其他操作
            this.bw.WorkerReportsProgress = true;
            this.bw.WorkerSupportsCancellation = true;
            //多线程控件(3个方法)
            this.bw.DoWork += Bw_DoWork;//开启线程触发
            this.bw.ProgressChanged += Bw_ProgressChanged;//辅助线程任务执行时触发
            this.bw.RunWorkerCompleted += Bw_RunWorkerCompleted;//任务结束时触发
            this.bw.RunWorkerAsync();//开启任务
        }

        /// <summary>
        /// 开启线程任务(文件准备上传)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Bw_DoWork(object sender, DoWorkEventArgs e)
        {
            using (FileStream fs = new FileStream(fileModel.FileFullPath, FileMode.Open))
            {
                while (fs.Position < fs.Length)
                {//未传输完毕
                    if (this.bw.CancellationPending)//判断是否取消上传
                    {
                        this.bw.ReportProgress(0, null);//回滚进度条
                        e.Cancel = true;//取消事件
                        return;//退出当前方法
                    }
                    //循环上传(比如:每次上传10kb)
                    byte[] bufferArray = new byte[10240];//每次读取的缓存数组
                    int count = fs.Read(bufferArray, 0, bufferArray.Length);//每次写入到缓冲区的字节数
                    fileModel.FileBytes = bufferArray;
                    fileModel.FileLength = count;
                    fsc.FileTransfer(fileModel);//长连接循环发送
                }
            }
        }

        /// <summary>
        /// 线程更改时触发
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //int progressPercentage= e.ProgressPercentage;
            //int maxXiMum = this.pbFileUpload.Maximum;
            //更新进度条:有可能 100(最大值) progressPercentage有可能为101
            //this.pbFileUpload.Value = progressPercentage>maxXiMum?maxXiMum:progressPercentage;
            this.pbFileUpload.Value = e.ProgressPercentage;
            if (e.UserState != null)
            {
                this.lblUploadedFileSize.Text = e.UserState.ToString() + " MB";//实时文件大小:MB
            }

        }

        /// <summary>
        /// 线程结束时触发:当没有动作触发
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        public event Action<FileModel> CloseFormAction;//定义关闭当前页面的委托事件
        private void Bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (this.pbFileUpload.Value == this.pbFileUpload.Maximum)
            {//传输完毕
                if (CloseFormAction != null)
                {
                    CloseFormAction(this.fileModel);//调用委托事件
                }
                this.Close();//关闭当前窗体
            }
        }

        private void UploadFile()
        {
            //使用FileInfo包装上传的文件
            FileInfo fileInfo = new FileInfo(filePath);
            //封装文件模型对象
            fileModel = new FileModel();
            fileModel.FieldId = Guid.NewGuid().ToString();
            fileModel.FileName = fileInfo.Name;
            fileModel.FileSize = fileInfo.Length;
            fileModel.FileSuffixName = fileInfo.Extension;
            fileModel.FileFullPath = fileInfo.FullName;

            FileServerCallback fileServerCallback = new FileServerCallback();
            fileServerCallback.ToFileSizeCallback += FileServerCallback_ToFileSizeCallback;//注册委托事件
            //创建服务器端代理对象
            fsc = new FileServiceClient(new InstanceContext(fileServerCallback));
            fsc.FileUpload(fileModel);//和服务器端进行对接

        }

        /// <summary>
        /// 获取已上传文件大小的委托事件的回调函数
        /// 修改进度条
        /// </summary>
        /// <param name="obj"></param>
        private void FileServerCallback_ToFileSizeCallback(long fileSize)
        {
            //步长:每步走多少字节数
            int stepSize = (int)(this.fileModel.FileSize / this.pbFileUpload.Maximum);
            if (fileSize > stepSize * this.pbFileUpload.Value)
            {//判断时候已走到头
                int result = this.pbFileUpload.Value + 1;//进度条加1
                                                         //long sizeMB = fileSize / 1024 / 1024;
                double fileSizeDouble = Convert.ToDouble(fileSize);
                double sizeDoubleMB = fileSizeDouble / 1024 / 1024;//MB
                double fileSizeVal = Math.Round(sizeDoubleMB, 2);
                if (this.bw.IsBusy)
                {//校验此线程是否还在运行
                    this.bw.ReportProgress(result, fileSizeVal);//手动触发bw线程的ProgressChanged事件
                }
            }
        }

        /// <summary>
        /// 手动取消上传
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCancell_Click(object sender, EventArgs e)
        {
            this.bw.CancelAsync();//取消操作:关闭线程
            this.fsc.FileStop(this.fileModel);//通知服务器端关闭和移除文件流
            this.Close();
        }
    }
}

这里我们对整体的客户端与服务端交互处理,做个概述:
客户端(前台):在主页面用户点击’浏览’按钮或者文本框,选择需要上传的文件,
点击’上传’按钮跳转到"文件上传页面"。
实例化FileUploadForm窗体对象时:
添加方法:UploadFile,根据传入的文件路径参数,使用文件包装类FileInfo处理,获取具体的文件信息,然后封装成FileModel模型对象,使用 FileServiceClient服务器端代理对象,调用服务器端FileUpload方法上传文件,此方法在服务器磁盘中仅仅创建了一个空文件.

    string path = "F:/FileUpload/" + fileModel.FileName;
    FileStream fileStream = new FileStream(path, FileMode.Create);

我们在页面加载时,通过一个单独的线程对象BackgroundWorker去处理文件的具体传输。

private void FileUploadForm_Load(object sender, EventArgs e)
        {
           .........................................................
           .........................................................
           .........................................................
	       .........................................................
            //使用单独的线程去处理进度条:
            //如果直接在当前页面所在线程(主线程)直接处理,整个页面会卡死,无法对当前页面进行任何其他操作
            this.bw.WorkerReportsProgress = true;
            this.bw.WorkerSupportsCancellation = true;
            //多线程控件(3个方法)
            this.bw.DoWork += Bw_DoWork;//开启线程触发
            this.bw.ProgressChanged += Bw_ProgressChanged;//辅助线程任务执行时触发
            this.bw.RunWorkerCompleted += Bw_RunWorkerCompleted;//任务结束时触发
            this.bw.RunWorkerAsync();//开启任务:执行DoWork方法
        }
 		/// <summary>
        /// 开启线程任务(文件准备上传)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Bw_DoWork(object sender, DoWorkEventArgs e)
        {

		 .........................................................
         .........................................................
         .........................................................
	       .........................................................

		}

	 	/// <summary>
        /// 线程更改时触发
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
		 .........................................................
         .........................................................
         .........................................................
	     .........................................................
		}

 		/// <summary>
        /// 线程结束时触发:当没有动作触发更改时,进度结束时
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
     private void Bw_RunWorkerCompleted(object sender,RunWorkerCompletedEventArg e)
        {
		.........................................................
         .........................................................
         .........................................................
	     .........................................................

		}	

手动去触发BackgroundWorker的ProgressChanged:
我们这边是在服务器每传输一次,进行回调到客户端时,去手动触发此事件,更新进度条.

/// <summary>
        /// 获取已上传文件大小的委托事件的回调函数
        /// 修改进度条
        /// </summary>
        /// <param name="obj"></param>
        private void FileServerCallback_ToFileSizeCallback(long fileSize)
        { 
        	.........................................................
        	 .........................................................
             .........................................................
                if (this.bw.IsBusy)
                {//校验此线程是否还在运行
                    this.bw.ReportProgress(result, fileSizeVal);//手动触发bw线程的ProgressChanged事件
                }
            }
 this.bw.ReportProgress(result, fileSizeVal);

注意:ReportProgress方法的两个参数,将会传给触发的ProgressChanged事件的
ProgressChangedEventArgs参数对象,

#region 程序集 System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\System.dll
#endregion

namespace System.ComponentModel
{
    //
    // 摘要:
    //     为 System.ComponentModel.BackgroundWorker.ProgressChanged 事件提供数据。
    public class ProgressChangedEventArgs : EventArgs
    {
        //
        // 摘要:
        //     初始化 System.ComponentModel.ProgressChangedEventArgs 类的新实例。
        //
        // 参数:
        //   progressPercentage:
        //     已完成的异步任务的百分比。
        //
        //   userState:
        //     唯一的用户状态。
        public ProgressChangedEventArgs(int progressPercentage, object userState);

        //
        // 摘要:
        //     获取异步任务进度百分比。
        //
        // 返回结果:
        //     指示异步任务进度的百分比值。
        [SRDescriptionAttribute("Async_ProgressChangedEventArgs_ProgressPercentage")]
        public int ProgressPercentage { get; }
        //
        // 摘要:
        //     获取唯一的用户状态。
        //
        // 返回结果:
        //     一个唯一 System.Object ,该值指示用户状态。
        [SRDescriptionAttribute("Async_ProgressChangedEventArgs_UserState")]
        public object UserState { get; }
    }
}

4.启动服务器端,在客户端引用服务,测试在客户端调用服务器端的功能。
启动以及添加服务引用请参考上个项目,这里就不阐述了,地址:测试调用服务器端操作
这里我们看一下如何,在客户端引用服务后,查看生成的服务器端代理类的名称以及所位于的nameSpace?
我们双击引用的服务,会打开对象浏览器对话框:
在这里插入图片描述
我么会发现代理类名称:服务名称+Client
回调接口的名称也改变了,具体的更改需要如此操作查看。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值