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