WCF大文件斷點下載示例


  之前發過片段的內容,都沒給出完整的項目代碼,不少人來要,故整理下,做個完整的演示項目出來,花了4天的時間調試,真要命。另外如果是IIS承載的WCF服務,建議直接寫一個繼承IHttpHandler的類來實現,那樣效率更高,控制更精細,用WCF服務來做僅僅是在不使用IIS的情況下的妥協方法。注:必須安裝.NET4.0 以上版本

 

先簡單介紹下該程序實現的功能:

  一個服務端,使用WCF自承載,默認采用了REST模式,直接提供了HTTP下載,也可以開啟NET TCP等其它模式。HTTP下載時,IE是不支持斷點續傳的,下載軟件中,只有網絡傳送帶支持斷點續傳,其它的軟件都不支持,主要是我的WCF服務采用了流模式傳輸,客戶端軟件獲取不到要接收的數據總長度,以為不支持斷點續傳,故而直接不考慮續傳,但是網絡傳送帶就不同,它能夠繼續嘗試續傳請求,看服務端如何響應,因此只有網絡傳送帶支持斷點續傳。為了解決下載工具不支持斷點續傳的情況,我自己在客戶端里增加了一個HTTP下載方式,輸入下載地址,即可開始下載,中間暫停后還可以恢復,從斷點處恢復。另外提供直接程序下載方式,那個功能比較簡單,同時客戶端無法中斷下載,除非網絡異常造成的中斷,一旦中斷可以重試,繼續從斷點處續傳,但是手動中斷不可以,這個是WCF消息機制密封裝導致的,我們無法主動打斷消息的傳輸(文件下載就是在接收消息)。

 

這是服務端界面 

 

  點擊“開啟服務”后,按鈕變為“正在運行”,此時服務打開,服務監聽端口默認為12251,如果不想用這個端口,可以打開源碼重新編譯,在Form1里面有個baseAddress變量,修改那個即可,也可以設置到配置文件中,演示項目我就不搞那么麻煩了。

  服務開啟后,要先點擊“文件目錄” ,將當前提供文件下載服務的目錄設置下,那個當前目錄會顯示當前設置的位置。然后點擊“產生鏈接”,這是會出現文件列表,只有一層,未做遞歸處理。只有產生了鏈接后的文件,才能被下載到,否則都是非法訪問,不給與下載,保證了系統安全。點擊“復制選中鏈接”,你可以直接把下載鏈接復制到剪貼板,可以直接從IE下載試試效果了。

  那個HTTP幫助的鏈接,點擊后可以看到服務的詳細調用說明,如果要改變幫助,可以修改config文件。 

 

這是客戶端界面:

 

服務地址輸入后,點擊“獲取下載列表 ”,一旦獲取成功,服務地址將不可改變。此時直接用“下載選中文件”功能,則通過WCF的接口函數直接下載文件,和底層傳輸協議無關。如果先“復制選中鏈接”,然后“打開HTTP下載窗口”,則通過HTTP地址下載文件,此時是通過WCF提供的RESF服務進行的下載。

 

整個演示項目涉及到的知識點很多,故而做了很長時間,下面簡單說明下項目中涉及到的幾個要點。

一、消息的流封裝。

以前曾經寫過一篇隨筆,提到自定義文件流,現在考慮得更加成熟了,使用自定義讀取流,可以對任何流進行封裝,比如內存流,因為可能我們要傳輸的內容是要進行預處理的,一邊處理,一邊傳輸,這樣就必須對內存流進行封裝傳輸。  
自定義讀取流(只讀)
     ///   <summary>
    
///  自定義讀取流(只讀)
    
///   </summary>
     internal  class CusStreamReader : Stream
    {
         long _endPosition; // 結束位置
        Stream innerStream;
         ///   <summary>
        
///  參數為當前流的斷點
        
///   </summary>
         public  event Action< long> Reading;

         ///   <summary>
        
///  直接使用原始流。
        
///   </summary>
        
///   <param name="stream"> 原始流 </param>
         public CusStreamReader(Stream stream)
        {
             this.innerStream = stream;
            _endPosition = stream.Length;
        }
         ///   <summary>
        
///  使用流當前位置,指定長度初始化自定義流
        
///   </summary>
        
///   <param name="stream"> 原始流 </param>
        
///   <param name="count"> 使用長度 </param>
         public CusStreamReader(Stream stream,  long count)
        {
             this.innerStream = stream;
            _endPosition = stream.Position + count;
             if (_endPosition > stream.Length)
                _endPosition = stream.Length;
        }
         ///   <summary>
        
///  指定初始位置、長度初始化自定義流
        
///   </summary>
        
///   <param name="stream"> 原始流 </param>
        
///   <param name="offset"> 初始位置 </param>
        
///   <param name="count"> 使用長度 </param>
         public CusStreamReader(Stream stream,  long offset,  long count)
        {
            stream.Position = offset > stream.Length ? stream.Length : offset;
             this.innerStream = stream;
            _endPosition = offset + count;
             if (_endPosition > stream.Length)
                _endPosition = stream.Length;
        }
         ///   <summary>
        
///  從自定義流讀取指定長度到array,但是不超過初始化時設定的長度。
        
///   </summary>
        
///   <returns> 讀取的字節數 </returns>
         public  override  int Read( byte[] array,  int offset,  int count)
        {
             int readcount =  0;
             if (Position + count >  this._endPosition)
                readcount = innerStream.Read(array, offset, ( int)( this._endPosition - Position));
             else
                readcount = innerStream.Read(array, offset, count);
             if (Reading !=  null)
                Reading(Position);
             return readcount;
        }
         ///   <summary>
        
///  從自定義流讀取一個字節,但是不超過初始化時設定的長度。
        
///   </summary>
        
///   <returns> 讀取的字節,未找到則返回-1 </returns>
         public  override  int ReadByte()
        {
             if (Position >=  this._endPosition)
                 return - 1;
             else
                 return  base.ReadByte();
        }

         public  override  bool CanRead
        {
             get {  return innerStream.CanRead; }
        }

         public  override  bool CanSeek
        {
             get {  return  false; }
        }

         public  override  bool CanWrite
        {
             get {  return  false; }
        }

         public  override  void Flush()
        {
             throw  new NotImplementedException();
        }

         ///   <summary>
        
///  自定義流剩余長度。
        
///   </summary>
         public  override  long Length
        {
             get {  return _endPosition - innerStream.Position; }
        }

         ///   <summary>
        
///  自定義流位置,返回原始流的位置
        
///   </summary>
         public  override  long Position
        {
             get
            {
                 return innerStream.Position;
            }
             set
            {
                 throw  new NotImplementedException();
            }
        }

         public  override  long Seek( long offset, SeekOrigin origin)
        {
             throw  new NotImplementedException();
        }

         public  override  void SetLength( long value)
        {
             throw  new NotImplementedException();
        }

         public  override  void Write( byte[] buffer,  int offset,  int count)
        {
             throw  new NotImplementedException();
        }
    }

 

調用代碼
CusStreamReader fs =  new CusStreamReader( new FileStream(file.filepath, FileMode.Open, FileAccess.Read, FileShare.Read), offset, count);
fs.Reading += (t) =>
{
     // 限速代碼,實際使用時可以去掉,或者精確控制
    Thread.Sleep( 300);
    Console.WriteLine(t);
};

這里要特別說明的是這個Reading事件,非常重要。我們既可以利用它來限速,也可以用它來顯示傳輸的狀態,顯示當前傳了多少字節了。

 

二、異常處理 。

對於非法請求,必須返回錯誤異常,而WCF默認的錯誤異常處理和HTTP的不一樣,如果用HttpWebRequest來請求下載,錯誤異常是不能直接捕獲到的,查閱了相關資料后發現,要捕獲類型為WebException的異常,然后特殊處理。.NET4.0里面,可以直接拋出WebFaultException<T>類型的異常,這樣HttpWebRequest請求時可以很方便處理異常信息,但是這又帶來一個問題,如果同時支持ServiceModel里訪問,這個WebFaultException異常將獲取不到內容,只能得到錯誤代碼如(404),顯然這樣是不行的,反復調試后發現,服務端應該拋出FaultException異常,這樣ServiceModel里訪問可以直接捕獲到異常,HttpWebRequest也可以分析出錯誤信息,核心代碼如下:

WEB請求時的異常捕獲
try
{

}
catch (WebException ex)
{
     var errResp = ex.Response  as HttpWebResponse;
     using ( var stream = errResp.GetResponseStream())
    {
         string message =  null;
         using ( var sr =  new StreamReader(stream))
        {
            message = sr.ReadToEnd();
             var match = Regex.Match(message,  " (?is)(?<=<Text[^>]*>).*(?=</Text>) ");
             if (match.Success)
            {
                message = match.Value;
            }
        }
        MessageBox.Show(message);
    }
}

 

其它的就不多說了,自己看代碼,雖然只是演示項目,但是涉及到的知識點很多。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM