最近手上的案子需要以FTP方式取回數百MB到上GB的ZIP檔案,過去我的做法會一個登入及下載的Script,作為FTP command line utility的輸入來源,再以Shell方式啟動,如以下的例子:

   1:  //產生FTP Script檔案
   2:  string ftpScriptFile = Server.MapPath("TempFolder\\ftp.script");
   3:  Process p = new Process();
   4:  StreamWriter sw = 
   5:      new StreamWriter(ftpScriptFile);
   6:  sw.WriteLine("username");
   7:  sw.WriteLine("password");
   8:  sw.WriteLine("bin");
   9:  sw.WriteLine("cd /download");
  10:  sw.WriteLine("get some.zip");
  11:  sw.WriteLine("quit");
  12:  sw.Close();
  13:  //呼叫FTP command line utility
  14:  p.StartInfo = new ProcessStartInfo("cmd.exe", 
  15:  "/c ftp -s:\"" + ftpScriptFile + "\" ftp.darkthread.net");
  16:  //不顯示FTP Cmd視窗
  17:  p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
  18:  p.Start();
  19:  p.WaitForExit();
  20:  //狡兔死 走狗烹 Script檔刪除
  21:  File.Delete(ftpScriptFile);

 這個方法不算漂亮,但是簡單耐用,也不太會出錯。

之前瞄過.NET 2.0的新功能,知道它多了支援FTP的內建物件--System.Net.FtpWebRequest,更讚的是,它居然還可以支援斷線續傳的功能,對於這次可能破GB的大檔下載來說,真是一整個酷呀!

不囉嗦! 程式範例如下:

   1:  private void test()
   2:  {
   3:      //宣告FTP連線
   4:      FtpWebRequest ftpReq = (FtpWebRequest)
   5:          WebRequest.Create("ftp://ftp.darkthread.net/download/some.zip");
   6:      //下載檔案
   7:      ftpReq.Method = WebRequestMethods.Ftp.DownloadFile;
   8:      //指定Username/Password
   9:      ftpReq.Credentials = new NetworkCredential("username", "password");
  10:      //指定BIN模式
  11:      ftpReq.UseBinary = true;
  12:      string zipFile = "c:\\temp\some.zip";
  13:      //支援續傳功能
  14:      FileInfo fi = new FileInfo(zipFile);
  15:      FileStream fs = null;
  16:      //檢測檔案是否存在
  17:      if (fi.Exists)
  18:      {
  19:          //檔案若存在,由剛才的中斷點繼續
  20:          ftpReq.ContentOffset = fi.Length;
  21:          fs = new FileStream(zipFile, FileMode.Append, FileAccess.Write);
  22:      }
  23:      else //檔案不存在時重新建立新檔案
  24:          fs = new FileStream(zipFile, FileMode.Create, FileAccess.Write);
  25:      //建立FTP連線
  26:      FtpWebResponse ftpResp = (FtpWebResponse)ftpReq.GetResponse();
  27:      //取得下載用的Stream物件
  28:      using (Stream stm = ftpResp.GetResponseStream())
  29:      {
  30:          //由於檔案龐大,以Block方式多批寫入
  31:          byte[] buff = new byte[2048];
  32:          int len = 0;
  33:          while (fs.Length < ftpResp.ContentLength)
  34:          {
  35:              //取得資料長度
  36:              len = stm.Read(buff, 0, buff.Length);
  37:              fs.Write(buff, 0, len);
  38:          }
  39:          stm.Close();
  40:   
  41:      }
  42:      fs.Flush();
  43:      fs.Close();
  44:  }

Update @ 2007-06-27
以上的程式碼在下載大型檔案時會因超過FTP Server Control Connection Timeout而發生Connection Closed Exception,詳情請看這裡


Comments

# by SGY

Good Job!

# by FLY

您好: 測試此段程式時會掛在此處 //建立FTP連線 26: FtpWebResponse ftpResp = (FtpWebResponse)ftpReq.GetResponse(); 訊息:基礎連接已關閉: 接收時發生未預期的錯誤。 用的是"220 Serv-U FTP Server v6.1 for WinSock ready...\r\n"

# by Jeffrey

to FLY, 是在傳大檔案時才會發生還是任何檔案都不OK,我自己有遇到IIS FTP在傳大檔案時有同樣的錯誤訊息(見文末的連結),而Google的結果似乎也有人回報這類問題只發生在某些FTP Server上,我推測跟FTP Server的行為或回應的訊息格式有關。如果要查個水落石出,似乎只能靠Ethereal或Microsoft Network Monitor這類工具吧。

# by FLY

Jeffrey: 是所有檔案都不OK,在FTP LOG上發現以下訊息 dos command直接連線是可以的。FTP上的log只有三行.. (061326) Connected to 168.0.0.1(Local address 10.1.1.1) (061326) IP-Name: COMP006.UUU.COM (061326) Closing connection 好像連登入都沒有,就被踢出來了。

# by SoWn

請問一下~ 不知道您有沒有試過 FTP 上頭是中文檔名的 Case 因為我試了很久, 始終無法成功, 似乎中文檔名連結會找不到咧 感謝

# by Jeffrey

to SoWn, 想問一下, FTP Server的平台是UNIX嗎?

# by Corey

請問依下,因為本範例都是一次單傳一個檔案, 如果一次需要上完10000~100000個檔案時,當然習慣都會將功能做一個Function來呼叫。 每次呼叫都必須連線FTP一次,個人連線差不多10000~20000之間FTP就會連線失敗。 有沒有辦法可以只要FTP連線一次,所有檔案上傳完畢後在斷線就可以。 因為檔案數量過大,所以中間有可能FTP斷線,可否有連線狀態來判斷,斷線後重新連線的機制。

# by Jeffrey

to Corey,依你描述的情境,若你是用單一迴圈跑完兩萬個檔案上傳(沒有用多執行緒),連線失敗應與是否從頭到尾維持一條連線無關(依我對FTP原理的了解,登入下指令是一條連線,下載上傳時本來就會每次另開一條連線)。我建議可以在呼叫Function中加上try catch,一但發現連線失敗,就重新呼叫Function上傳剛才失敗的檔案,應該能處理大多數網路傳輸失敗的情境。

# by Brent

請問,用System.Net.FtpWebRequest的方法,有辦法確認下載資料的完整性嗎?我曾試過下載一個5mb的檔案兩次,再讓這兩個資料進行字元比對的動作,很常發現資料不完全相等的問題。

# by Jeffrey

to Brent, 不完全相等是其中一個檔尾有缺,還是中間資料有缺?

# by Csepola

請問是否可簡單示範一下上傳支援續傳的sample ? 網路上鮮少有這方面的討論

Post a comment


43 - 25 =