TIPS-Implement a Resumable FTP Download with .NET 2.0
11 |
最近手上的案子需要以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 ? 網路上鮮少有這方面的討論