WCF实现大文件上传

2022-12-24,,

一.文件服务接口

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

WCF实现大文件上传的相关教程结束。

《WCF实现大文件上传.doc》

下载本文的Word格式文档,以方便收藏与打印。