WCF大檔案斷點下載樣本

來源:互聯網
上載者:User
完整項目下載:http://files.cnblogs.com/qldsrx/FilesManager.rar

  之前發過片段的內容,都沒給出完整的項目代碼,不少人來要,故整理下,做個完整的示範項目出來,花了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);
    }
}

 

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.