wcf综合运用之:大文件异步断点续传

在WCF下作大文件的上传,首先想到使用的就是Stream,这也是微软推荐的使用方式。处理流程是:首先把文件加载到内存中,加载完毕后传递数据。这种处理方式对小文件,值得推荐,比如几K,几十k的图片文件,文本文件对大文件就不适用,比如10G的电影,把10G的数据加载到缓存中再传递,这是不可想象的。这个时候我们想到的就是断点续传。由于数据量很大。会导致当前程序阻塞,所以采用异步发送的方式,以进度条显示出来,这也是本篇文章所要实现的功能. 另外,目前BasicHttpBinding, NetTcpBinding, 和NetNamedPipeBinding 支持流处理模型,其他的不支持,这也影响stream的使用。
       解释几个重要的概念以及实现的方式:
       1、断点续传:就是在上一次下载/上传断开的位置开始继续下载/上传。微软已经提供好了这样的方法: BinaryWriter 这个是二进制的写入器,看下面:

namespace System.IO
 {
     public class BinaryWriter : IDisposable
     {
         public virtual long Seek(int offset, SeekOrigin origin); //设置当前流中的位置,第一个参数表示偏移量,第二个参数表示偏移量的参考依据
        public virtual void Write(byte[] buffer);                       //把数据写入Seek方法设置的位置
    }
 }

2、异步线程:就是使用后台程序,不用阻塞当前线程,使用backgroundWorker组建,可以大大减少代码的编写量
     下面的操作都是与WCF相关的部分。首先我们要定义一个数据契约用来传递数据:

    [DataContract]
     public class FileInfo
     {
         //文件名
        [DataMember]
         public string Name { get; set; }

         //文件字节大小
        [DataMember]
         public long Length { get; set; }

         //文件的偏移量
        [DataMember]
         public long Offset { get; set; }

         //传递的字节数
        [DataMember]
         public byte[] Data { get; set; }

         //创建时间
        [DataMember]
         public DateTime CreateTime { get; set; }
     }

接着定义操作的契约:

    [ServiceContract]
     public interface IFilesLoad
     {
         [OperationContract]
         List<FileInfo> GetFilesList(); //获得以已经上传的文件列表
        [OperationContract]
         FileInfo GetFiles(string fileName); //根据文件名寻找文件是否存在,返回文件的字节长度
        [OperationContract]
         FileInfo UplodaFile(FileInfo file); //上传文件
    }

定义了契约,下面就要来实现契约,这里仅仅粘贴重要部分,在后面可以下载源代码

 public Fish.DataContracts.FileInfo UplodaFile(Fish.DataContracts.FileInfo file)
         {
             string filePath = System.Configuration.ConfigurationManager.AppSettings["filePath"] + "/" + file.Name;//获取文件的路径,已经保存的文件名
            FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate);//打开文件
            long offset = file.Offset;  //file.Offset 文件偏移位置,表示从这个位置开始进行后面的数据添加
            BinaryWriter writer = new BinaryWriter(fs);//初始化文件写入器
            writer.Seek((int)offset, SeekOrigin.Begin);//设置文件的写入位置
            writer.Write(file.Data);//写入数据
            file.Offset = fs.Length;//返回追加数据后的文件位置
            file.Data = null;
             writer.Close();
             fs.Close();
             return file;
         }

下面来进行服务端得WCF配置

  <system.serviceModel>
         <services>
           <!-- 文件断点续传 -->
           <service behaviorConfiguration="DefaultBehavior" name="Fish.ServiceImpl.FilesService">
             <endpoint address="" binding="basicHttpBinding" bindingConfiguration ="StreamedHTTP" contract="Fish.ServiceInterfaces.IFilesLoad"></endpoint>
             <host>
               <baseAddresses>
                 <add baseAddress="http://localhost:8080/Fish/FilesService"/>
               </baseAddresses>
             </host>
           </service>
           </services>

       <behaviors>
         <serviceBehaviors>
           <behavior name="DefaultBehavior">
             <serviceMetadata httpGetEnabled="true" />
             <serviceDebug includeExceptionDetailInFaults="true" />
           </behavior>
         </serviceBehaviors>
       </behaviors>
       <bindings>
         <basicHttpBinding>
           <binding name="StreamedHTTP" maxReceivedMessageSize="2000000000000" messageEncoding="Mtom" transferMode="Streamed">
             <readerQuotas maxArrayLength="20000000"/>
           </binding>
         </basicHttpBinding>
       </bindings>
   </system.serviceModel>

这里最要的是设置maxReceivedMessageSize, messageEncoding。比较重要的是设置为Mtom,可以提高30%的效率,这是wcf特意为大文件提供的。下面看客户端代码:

  <system.serviceModel>
         <services>
           <!-- 文件断点续传 -->
           <service behaviorConfiguration="DefaultBehavior" name="Fish.ServiceImpl.FilesService">
             <endpoint address="" binding="basicHttpBinding" bindingConfiguration ="StreamedHTTP" contract="Fish.ServiceInterfaces.IFilesLoad"></endpoint>
             <host>
               <baseAddresses>
                 <add baseAddress="http://localhost:8080/Fish/FilesService"/>
               </baseAddresses>
             </host>
           </service>
           </services>

       <behaviors>
         <serviceBehaviors>
           <behavior name="DefaultBehavior">
             <serviceMetadata httpGetEnabled="true" />
             <serviceDebug includeExceptionDetailInFaults="true" />
           </behavior>
         </serviceBehaviors>
       </behaviors>
       <bindings>
         <basicHttpBinding>
           <binding name="StreamedHTTP" maxReceivedMessageSize="2000000000000" messageEncoding="Mtom" transferMode="Streamed">
             <readerQuotas maxArrayLength="20000000"/>
           </binding>
         </basicHttpBinding>
       </bindings>
   </system.serviceModel>

这里最要的是设置maxReceivedMessageSize, messageEncoding。比较重要的是设置为Mtom,可以提高30%的效率,这是wcf特意为大文件提供的。下面看客户端代码:

            var fileManger = Common.ServiceBroker.FindService<IFilesLoad>(); //创建WCF代理
            string localPath = e.Argument as string;    
             string fileName = localPath.Substring(localPath.LastIndexOf('\\') + 1);//获得文件本地文件地址
            int maxSiz = 1024 * 100;  //设置每次传100k                              
             FileStream stream = System.IO.File.OpenRead(localPath);    //读取本地文件
            Fish.DataContracts.FileInfo file = fileManger.GetFiles(fileName);   //更加文件名,查询服务中是否存在该文件
            if (file == null)     //表示文件不存在
            {
                 file = new Fish.DataContracts.FileInfo();
                 file.Offset = 0; //设置文件从开始位置进行数据传递
            }
             file.Name = fileName;
             file.Length = stream.Length;
             if (file.Length == file.Offset) //如果文件的长度等于文件的偏移量,说明文件已经上传完成
            {
                 MessageBox.Show("该文件已经在服务器中,不用上传!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                 return;
             }
             else
             {
                 while (file.Length != file.Offset)  //循环的读取文件,上传,直到文件的长度等于文件的偏移量
                {
                     file.Data = new byte[file.Length - file.Offset <= maxSiz ? file.Length - file.Offset : maxSiz]; //设置传递的数据的大小
                    stream.Position = file.Offset; //设置本地文件数据的读取位置
                    stream.Read(file.Data, 0, file.Data.Length);//把数据写入到file.Data中
                    file = fileManger.UplodaFile(file);     //上传

                    e.Result = file.Offset;
                     (sender as BackgroundWorker).ReportProgress((int)(((double)file.Offset / (double)((long)file.Length)) * 100), file.Offset);
                     if (this.backgroundWorker1.CancellationPending)
                         return;
                 }
             }
             stream.Close();
             Common.ServiceBroker.DisposeService<IFilesLoad>(fileManger);  //关闭wcf

     最后是最后运行的效果:


    代码地址:/Files/wanqiming/Upload.rar

posted @ 2013-03-06 11:43  therockthe  阅读(580)  评论(0)    收藏  举报