KB-Connection Closed Exception of FtpWebRequest

幾天前我寫了一篇Post介紹如何用System.Net.FtpWebRequest開發一個支援續傳功能的FTP Client。

在專案中開始使用它來傳大檔時,卻發現不知FtpWebRequest是不是為了炫耀它的續傳功能,在花了半小時傳完一個400MB的ZIP檔之後,都會觸發一個"The underlying connection was closed: An unexpected error occurred on a receive."的Exception,但是檔案的大小正確,代表每一個Byte都順利傳回來了,但就是逼得我得Run第二次"續傳餘下的0 Byte",程式才會正常結束。

同樣的程式若下載的是5MB的ZIP檔,則不會發生問題。因為"Size Matters",所以我推斷可能可能與Timeout之類的屬性有關。雖然發現FtpWebRequest.Timeout官方文件寫錯了,預設值是100,000ms而不是無限大,但是測試發現它並不是導致問題的原因。

後來想到另一件事,傳統的FTP軟體的訊息視窗中總會看到定期送出NOOP指令來保持連線不被Server切斷(依據這篇文件,一般的FTP Server,只要5分鐘沒收到Control Connection傳來的任何訊息,就會中斷連線),而FtpWebRequest並沒有實做這類的機制。因此,只要下載時間超過FTP Server的Idle Timeout,在Stream.Close()會觸發檢查Connection的Logic,雖然資料已順利傳完,但由於Control Connection已經Idle過久被切了,於是拋出以下錯誤:

Error: The underlying connection was closed: An unexpected error occurred on a receive.
   at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
   at System.Net.FtpWebRequest.RequestCallback(Object obj)
   at System.Net.CommandStream.Abort(Exception e)
   at System.Net.CommandStream.CheckContinuePipeline()
   at System.Net.FtpWebRequest.DataStreamClosed(CloseExState closeState)
   at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
   at System.Net.FtpDataStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()

事到如今,要解決這個問題有幾種手段:

  1. 去找有實作NOOP Keep Alive機制的3rd Party .NET Component,如: Rebex FTP for .NET,但需考量採購成本
  2. 自己實作NOOP機制,但需要處理複雜的Control Connection與Data Connection問題,有陷入"重新發明輪子"迷思的疑慮,還不如去買現有元件
  3. 由於資料已順利傳完,實際上我們已不再需要Control Connection,而是Stream.Close()時所觸發的制式檢查,那麼忽略Connection已斷的事實又何妨?
    雖說將Exception納為正常流程有違反一般的效能準則,但進入Exception流程所多出的時間應該不及1ms,相較於超過5分鐘的下載時間,倒是可以被忽略。

偷懶的我決定用方法3),將先前Post程式的片段修改如下:

//2007-06-27 因為沒有NOOP的Keep Alive機制
//當下載時間超過FTP Server的Idel限制,會在Stream.Close()時
//發生以下錯誤:
//The underlying connection was closed: 
//An unexpected error occurred on a receive.
//若檔案已順利下載完成,則忽略此一WebException
bool bFinish = false;
 
try
{
    using (Stream stm = ftpResp.GetResponseStream())
    {
        //由於檔案頗大,因此分Block寫入
        byte[] buff = new byte[2048];
        int len = 0;
        //取得要下載的檔案大小
        long totalSize = ftpResp.ContentLength;
        while (fs.Length < totalSize)
        {
            len = stm.Read(buff, 0, buff.Length);
            fs.Write(buff, 0, len);
        }
        fs.Flush();
        //檔案完整傳完, bFinish=true
        bFinish = (fs.Length == totalSize);
        fs.Close();
        //下載時間過久時,stm.Close()會觸發Exception
        stm.Close();
    }
}
catch (System.Net.WebException we)
{
    //若未傳完才要觸發Exception
    if (!bFinish) throw we;
}
ftpResp.Close();
Published 27 June 2007 02:46 PM 由 Jeffrey
Filed under: ,


意見

# SGY said on 04 April, 2008 10:49 AM

long totalSize = ftpResp.ContentLength;

我用XP SP2 Pro +IIS 6(FTP)  VS2005 C# 2.0

測試  

    ftpResp.ContentLength;   回  -1  

導致

   while (fs.Length < totalSize)  離開無法下載

別人的Code  可以下載

public void Download( )

       {

           FtpWebRequest reqFTP;

           try

           {

               FileStream outputStream = new FileStream(@"c:\aa.wmv", FileMode.Create);

               reqFTP = (FtpWebRequest)FtpWebRequest.Create(new Uri("127.0.0.1/.../Amazing_Caves_1080.wmv"));

               reqFTP.Method = WebRequestMethods.Ftp.DownloadFile;

               reqFTP.UseBinary = true;

               reqFTP.Credentials = new NetworkCredential("rb", "rb");

               FtpWebResponse response = (FtpWebResponse)reqFTP.GetResponse();

               Stream ftpStream = response.GetResponseStream();

               long cl = response.ContentLength;

               int bufferSize = 2048;

               int readCount;

               byte[] buffer = new byte[bufferSize];

               readCount = ftpStream.Read(buffer, 0, bufferSize);

               while (readCount > 0)

               {

                   outputStream.Write(buffer, 0, readCount);

                   readCount = ftpStream.Read(buffer, 0, bufferSize);

               }

               ftpStream.Close();

               outputStream.Close();

               response.Close();

           }

           catch (Exception ex)

           {

               Console.WriteLine(ex.Message);

           }

       }

問題是為什麼會回-1 ?

# Jeffrey said on 04 April, 2008 06:45 PM

To SGY,

依據MSDN Library裡的說明(http://tinyurl.com/6znqtq),"當 FTP 伺服器傳回回應資料流時,ContentLength 屬性會包含資料流中的位元組數。如果回應中未傳回任何資料,或伺服器未傳送內容長度資訊,則 ContentLength 會傳回 -1。"

我想IIS裡的FTP Server應該不致不支援ContentLength,而其他的Code可以跑,應該也不是回應中未傳回任何資料。我懷疑會不會這反而是IIS FTP Server為了配合*.wmv可串流播放的特性才故意做的調整。

建議你做個測試,用同樣的Code改下載一般的txt, zip,是否ContentLength仍等於-1? 如果有結果也讓大家知一下

# SGY said on 05 April, 2008 12:15 AM

我用另一個URL (對方不是IISFTP)    ftpsv.cwb.gov.tw/.../W002.txt

可回傳正確長度

本機  :驗測結果  如xml File還是回-1  

解決:

可能策略需改變

先取得   WebRequestMethods.Ftp.GetFileSize;

可回傳正確FileSize

再作   WebRequestMethods.Ftp.DownloadFile;

另Rebex FTP for .NET  Demo FTP 似乎都很正常的運作(來源在我的本機,我懷疑它也是先要FileSize,不然ProcessBar 應該無法計算進度)

# SGY said on 06 April, 2008 09:38 AM

(IIS 6)我下載不會有此例外  (下載約30min)

catch (System.Net.WebException we){    

//若未傳完才要觸發Exception  

if (!bFinish) throw we;

}

(IIS 6)另一定要先要  FileSize , 不然會回-1

如上是我驗測後的結果

還有一點  ,FTP 連線會共用 不管你下多少Request. 關掉AP FTP 的 Session 才會斷

不知板主用的TEST  FTP Server是那個?

關於續傳 :

我會先下載至Tmp file

Check SERVER  FileDateTime  

與Local FileDateTime

與Size

不同才會下載

下載會在更改LocalFile Lastwrite DateTime

至於(temp-->LocalFile)File Copy  若目標File 在使用時Copy File會失敗

失敗 後:採用MovefileEX  API(Reboot 會自動班你更版)  <---不知.NET 是否有相對的API

這樣fileUpdate就很完善了

此為個人見解

# Jeffrey said on 06 April, 2008 10:15 PM

To SGY, 謝謝你提供這麼詳盡的測試心得。

我當初測試的FTP Server是廠商提供資訊下載的FTP主機,看起來像是UNIX。由你我的測試結果來看,看來各家(甚至各版本)FTP Server的行為差異頗大(若這是個大困擾,或許一些適應力強的元件就值得花錢買了),晚點有時間我再來研究一下IIS FTP不能傳回ContentLength的原因。謝謝囉!

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 

請輸入以上的數字:

【請注意】意見送出後可能需要幾分鐘才會出現在網頁上,請耐心等候。

搜尋

Go

<June 2007>
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567
 
RSS
最新回應


BlogLook Score and Rank

Syndication