WCF实现大文件上传

一.文件服务接口

1.文件上传

2.文件传输(上传按钮)

3.文件传输停止

服务地址:

在客端添加服务器引用,从而实现客户端调用服务器的功能。

二.契约

服务契约[ServiceContract]:定义服务器这边的功能。

操作契约[OperationContract]:简单的说,就是指定服务器的功能方法是否可以被客户端调用,如果服务器方法未添加操作契约,则无法被客户端那边调用。

数据契约[DataContract]:和操作契约类似,只不过操作契约作用的对象是方法,数据契约作用的对象是基本类型数据,主要是数据成员或者属性。

回调契约CallbackContract:比如说,客户端完成一个功能,需要告诉服务端,这就需要向服务器回调一个消息,此时就需要定义一个回调契约(也就是一个接口)。

三.创建项目

首先,新建一个【WCF服务库】,然后在CRMServer中把系统自己添加的IService和Service删除,再添加一个【WCF服务】,最后添加一个窗体应用程序,作为客户端,如下:

服务器端生成的配置文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <!-- 部署服务库项目时,必须将配置文件的内容添加到 
  主机的 app.config 文件中。System.Configuration 不支持库的配置文件。-->
  <system.serviceModel>
    <services>
      <service name="CRMServer.FileService">
        <!--双工通信:客户端既可以访问服务器,服务器也可以回调信息给客户端-->
        <endpoint address="" binding="wsDualHttpBinding" contract="CRMServer.IFileService">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <!--元数据-->
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <!--这个节点的地址就是客户端那边要使用的-->
            <add baseAddress="http://localhost:8733/Design_Time_Addresses/CRMServer/FileService/" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- 为避免泄漏元数据信息,
          请在部署前将以下值设置为 false -->
          <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>
          <!-- 要接收故障异常详细信息以进行调试,
          请将以下值设置为 true。在部署前设置为 false 
            以避免泄漏异常信息-->
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

</configuration>

上面更改为双工通信wsDualHttpBinding。

 下面还要让客户端能够调用服务器的方法,所以需要添加服务引用,过程如下:

第一次添加服务引用,需要先运行一下服务端,将服务端设置为启动项,然后复制元数据地址

 

 然后在客户端【引用】----》【添加服务引用】,拷贝元数据地址,转到

 

 

添加成功后就可以关闭。然后在项目结构中就可以看到服务引用:

 

查看服务引用,可以看到服务端那边所有的方法等,这样就可以实现客户端和服务器之间的交流了。

 

 

最后总的项目结构如下:

 

 

(1)FileModel.cs

封装文件数据模块

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace CRMServer
{
    [Serializable]//允许以流的文件进行传输
    [DataContract]//数据契约
    public  class FileModel
    {
        [DataMember]
        /// <summary>
        /// 文件标识
        /// </summary>
        public string FileId { get; set; }

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

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

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

        [DataMember]
        /// <summary>
        /// 每次传输的文件byte[]
        /// </summary>
        public byte[] FileBytes { get; set; }

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

        [DataMember]
        /// <summary>
        /// 文件扩展名
        /// </summary>
        public string FileExtName { get; set; }
    }
}

(2)IFileService.cs文件

服务契约接口,该接口定义了服务的功能:上传文件,文件传输,取消文件传输。

服务契约回调接口ICallBack:客户端调用服务器方法后需要回调消息给服务器,这时就需要回调接口。  [ServiceContract(CallbackContract=typeof(ICallBack))]表示指定

回调接口为ICallBack。

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

namespace CRMServer
{
    /// <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);
    }

    /// <summary>
    /// 回调契约接口,在客户端实现接口
    /// </summary>
    public interface ICallBack
    {
        /// <summary>
        /// 回调已经传输的文件的大小
        /// </summary>
        /// <param name="length"></param>
         [OperationContract(IsOneWay = true)]
        void ToFileSize(long length);
    }

}

(3)FileService.cs

该类实现了IFileService接口,大文件传输的过程中,受带宽影响,一个大文件可能会分为多个部分上传,也就是会上传多次,这里建立一个字典,将每部分的文件流保存

下来,以备后面使用。

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

namespace CRMServer
{
    public class FileService : IFileService
    {
        //保存很多个传输的文件,因为有的文件可能要多次才能传完
        public Dictionary<string ,FileStream> dic=new Dictionary<string,FileStream>();
        #region 实现IFileService接口
        /// <summary>
        /// 文件上传
        /// </summary>
        /// <param name="fileModel"></param>
        public void FileUpLoad(FileModel fileModel)
        {
            try
            {
                //写到e盘的files文件夹下
                FileStream filestream = new FileStream("e:/files/" + fileModel.FileName, FileMode.Create);
                dic.Add(fileModel.FileId, filestream);
            }
            catch (Exception)
            {
                throw;
            }
        }
        /// <summary>
        /// 文件传输
        /// </summary>
        /// <param name="fileModel"></param>
        public void FileTransfer(FileModel fileModel)
        {
            try
            {
                //添加到字典
                FileStream filestream = dic[fileModel.FileId];
                //写入到磁盘中
                filestream.Write(fileModel.FileBytes, 0, fileModel.FileCount);
                //写入成功,告诉客户端写入好了,回调
                //OperationContext:操作上下文,全局文件对象,获取当前客户端和服务器的交流通道,通过这个通道调用服务器端的方法
                ICallBack callback = OperationContext.Current.GetCallbackChannel<ICallBack>();
                callback.ToFileSize(filestream.Length);
                //假设10000kB,传了1000次,如果上传完了
                if (filestream.Length >= fileModel.FileSize)
                {
                    filestream.Close();
                    dic.Remove(fileModel.FileId);
                }
            }
            catch (Exception)
            {
               
            }
          
        }

        /// <summary>
        /// 文件关闭,取消传输
        /// </summary>
        /// <param name="fileModel"></param>
        public void FileStop(FileModel fileModel)
        {
            try
            {
                FileStream filestream = dic[fileModel.FileId];
                filestream.Close(); 
                dic.Remove(fileModel.FileId);
            }
            catch (Exception)
            {
                throw;
            }
        }
        #endregion
    }
}

(4)FileServiceCallBack.cs

该类实现了服务器回调接口,在这里创建了一个委托变量,在ToFileSize方法中调用该委托,在FileUpLoadForm窗体注册委托,实现了在FileUpLoadForm中实现

ToFileSize方法,相当于在FileUpLoadForm中调用ToFileSize方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CRMClient.Service;

namespace CRMClient
{
    public class FileServiceCallBack:IFileServiceCallback
    {
        //定义一个委托事件(+=注册)
        public event Action<long> ToFileSizeCallBack;

        #region 实现服务端的契约回调接口
        /// <summary>
        /// 回传传输的文件大小
        /// 实际的实现方法将会和委托关联(注册)
        /// </summary>
        /// <param name="length">文件传输了多少</param>
        public void ToFileSize(long length)
        {
            ToFileSizeCallBack(length);
        }
        #endregion
    }
}

(5)MainForm窗体

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CRMClient
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void btn_Browser_Click(object sender, EventArgs e)
        {
            OpenFileDialog sfd = new OpenFileDialog();
            if(sfd.ShowDialog()==DialogResult.OK)
            {
                txtFilePath.Text = sfd.FileName;
            }
        }

        private void bnt_UpLoad_Click(object sender, EventArgs e)
        {
          FileUpLoadForm fileUpLoadForm = new FileUpLoadForm(txtFilePath.Text);
            //注册回调(实现FileUpLoadForm的回调),弹出一个文件上传成功的提示框。
          fileUpLoadForm.CloseFormCallBack += fileUpLoadForm_CloseFormCallBack;
          fileUpLoadForm.Show();
        }

        void fileUpLoadForm_CloseFormCallBack(Service.FileModel obj)
        {
            MessageBox.Show(obj.FileName+"已上传完毕");
        }

        
    }
}

(6)FileUpLoadForm窗体

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

namespace CRMClient
{
    public partial class FileUpLoadForm : Form
    {
        FileModel fileModel ;

        FileServiceClient fileServiceClient;//服务端和客户端的对接对象

        BackgroundWorker bw = new BackgroundWorker();
        public FileUpLoadForm()
        {
            InitializeComponent();

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filePath">文件路径</param>
        public FileUpLoadForm(string filePath)
        {
            InitializeComponent();
            //文件信息
            FileInfo fileInfo = new FileInfo(filePath);
            fileModel = new FileModel();
            fileModel.FileId = Guid.NewGuid().ToString();//生成36位唯一标识
            fileModel.FileName = fileInfo.Name;
            fileModel.FileExtName = fileInfo.Extension;
            fileModel.FileSize = fileInfo.Length;
            fileModel.FullName = fileInfo.FullName;
            //实现回调
            FileServiceCallBack fileServiceCallBack=new FileServiceCallBack();
            //注册回调,实现ToFileSize方法
            fileServiceCallBack.ToFileSizeCallBack += fileServiceCallBack_ToFileSizeCallBack;
            //客户端和服务端进行对接(调用服务端的方法),FileServiceClient其实就是服务端的 FileService,这里系统自动加了个Client后缀
            fileServiceClient = new FileServiceClient(new InstanceContext(fileServiceCallBack));
           //调用服务端的方法,上传文件
            fileServiceClient.FileUpLoad(fileModel);
           
        }

        /// <summary>
        /// 修改进度条信息(通知回调)
        /// </summary>
        /// <param name="positionSize">已经传输的文件大小</param>
        void fileServiceCallBack_ToFileSizeCallBack(long positionSize)
        {
            //步长,进度条走一步相当于多少个字节
            int stepSize = (int)(this.fileModel.FileSize / this.PrograssBar.Maximum);
            //未传输完
            if (positionSize > stepSize * PrograssBar.Value)
            {
                //进度条走了多少步
                int result = this.PrograssBar.Value + 1;
                long SizeMb = positionSize / 1024 / 1024;//传输了几MB
                //如果线程未结束
                if (this.bw.IsBusy)
                {
                    //报告进度条进度
                    this.bw.ReportProgress(result, SizeMb);//手动触发bw_ProgressChanged方法
                }
            }
        }

        private void FileUpLoadForm_Load(object sender, EventArgs e)
        {
            this.txtTotalSize.Text = (fileModel.FileSize / 1024 / 1024).ToString()+"MB";//将字节b单位转换为MB
            this.PrograssBar.Minimum = 0;
            this.PrograssBar.Maximum = 100;
            this.bw.WorkerReportsProgress = true;//报告进度条的更新,不设置可能导致bw_ProgressChanged不触发    
            this.bw.WorkerSupportsCancellation = true;//是否支持取消线程
            //多线程控件,三个方法
            bw.DoWork += bw_DoWork;//开启线程触发
            bw.ProgressChanged+=bw_ProgressChanged;//辅助线程执行的时候触发
            bw.RunWorkerCompleted+=bw_RunWorkerCompleted;//任务结束的时候触发
            //开始任务,执行bw_DoWork
            this.bw.RunWorkerAsync();
        }
//这里创建一个委托变量,用于在FileUpLoadForm文件上传成功后,可以向MainForm弹出一个提示文件已经上传成功的提示框。
public event Action<FileModel> CloseFormCallBack; private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if(this.PrograssBar.Value>=this.PrograssBar.Maximum) { this.Close();//关闭 //并通知文件上传窗体已经成功上传(回调),回调的实现在MainForm实现 CloseFormCallBack(fileModel); } } private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.PrograssBar.Value = e.ProgressPercentage > 100 ? 100 : e.ProgressPercentage;//显示进度条百分比 if (e.UserState!=null) { this.txtBytesSize.Text = e.UserState.ToString();//实时文件大小 } } void bw_DoWork(object sender, DoWorkEventArgs e) { using(FileStream fileStream = new FileStream(fileModel.FullName,FileMode.Open))//打开文件 { //当文件还未传完 while(fileStream.Position<fileStream.Length) { //如果终止上传 if (this.bw.CancellationPending) { this.bw.ReportProgress(0, null);//设置进度条为0 e.Cancel = true;//取消事件 return; } //循环上传,每次10kb byte[] bytes=new byte[10240]; //循环读取文件内容,返回每次读取的实际大小 int count= fileStream.Read(bytes,0,bytes.Length); fileModel.FileBytes = bytes; fileModel.FileCount = count; fileServiceClient.FileTransfer(fileModel);//循环发送 } } } //取消上传 private void btn_Cancel_Click(object sender, EventArgs e) { this.bw.CancelAsync();//取消后,bw.CancellationPending=true this.fileServiceClient.FileStop(this.fileModel); this.Close(); } } }

上面的测试是在同一台电脑上进行的,也就是服务器和客户端是运行在同一台电脑上的,那如果要实现在两台电脑运行,一台运行服务器,一台运行客户端,可不可以

呢?

这里就要注意,由于WCF服务程序是不能独立运行的,他必须依托宿主才能运行,这个宿主可以是一个窗体程序或者控制台程序,也可以是IIS服务程序。

怎么实现客户端和服务器分别在不同的电脑也能通信?需要做一些配置:

一台电脑作为服务器,其端口是唯一的,即时两个不同的程序,其服务器的端口也是唯一的,操作系统默认为WCF服务分配的端口是8733,给我们提供了一个测试主机。

如果你改成其他的端口是使用不了WCF测试服务的,那么你如果想改成其他端口,并且还要能够实现不同电脑之间也可以通信测试,需要做以下配置:

第一步:设置出站入站端口

 

 勾选程序,下一步--------》勾选所有程序,下一步--------》......

 

 

出站:别人访问你,设置出站端口,就可以让被人访问你的电脑

入站:你访问别人,设置入站端口,就可以让别人的电脑被你访问

第二步:勾选这三个,点击确定,就会安装相应的服务。

接下来怎么实现WCF程序的寄宿?

我们在刚刚的项目新建一个窗体项目CRMMain,并添加CRMServer的引用:

 

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

namespace CRMMain
{
    public partial class MianServer : Form
    {
        public MianServer()
        {
            InitializeComponent();
        }
        ServiceHost host;
        private void btn_Start_Click(object sender, EventArgs e)
        {
            Uri uri = new Uri("http://localhost:8733/Design_Time_Addresses/CRMServer/FileService/");
             host = new ServiceHost(typeof(FileService),uri);//指定服务和服务地址
            //指定元数据地址和对接接口
            host.AddServiceEndpoint(typeof(IFileService),new WSDualHttpBinding(),"");
            host.Open();//开启服务
            MessageBox.Show("服务已经开启");
        }

        private void btn_Close_Click(object sender, EventArgs e)
        {
            host.Close();
            MessageBox.Show("服务已经关闭");
        }
    }
}

 然后设置A=CRMMain为启动项,运行程序,点击开启服务,WCF服务就运行了。然后再运行客户端CRMClient就可以实现客户端和服务器分别以不同程序运行了。

源码下载链接:https://pan.baidu.com/s/1tp5a__5UCykAVGsvvYTMjQ     提取码:ts7s

 

posted @ 2021-05-30 11:02  WellMandala  阅读(674)  评论(0编辑  收藏  举报