WCF实现大文件上传
阅读原文时间:2023年07月11日阅读:3

一.文件服务接口

1.文件上传

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

3.文件传输停止

服务地址:

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

二.契约

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

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

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

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

三.创建项目

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

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




上面更改为双工通信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]
///

/// 文件标识 ///
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
{
///

/// 服务契约 ///
[ServiceContract(CallbackContract=typeof(ICallBack))]
public interface IFileService
{
/// /// 上传文件 ///
[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 dic=new Dictionary();
#region 实现IFileService接口
///

/// 文件上传 ///
///
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;
}
}
/// /// 文件传输 ///
///
public void FileTransfer(FileModel fileModel)
{
try
{
//添加到字典
FileStream filestream = dic[fileModel.FileId];
//写入到磁盘中
filestream.Write(fileModel.FileBytes, 0, fileModel.FileCount);
//写入成功,告诉客户端写入好了,回调
//OperationContext:操作上下文,全局文件对象,获取当前客户端和服务器的交流通道,通过这个通道调用服务器端的方法
ICallBack callback = OperationContext.Current.GetCallbackChannel();
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 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