引言:
在這個專題將為大家揭開下FTP這個協議的面紗,其實學習知識和生活中的例子都是很相通的,就拿這個專題來說,要了解FTP協議然后根據FTP協議實現一個文件下載器,就和和追MM是差不多的過程的,相信大家追MM都有自己的經驗的,我感覺大部分的過程肯定是——第一步: 先通過工作關系或者朋友關系等認識MM(認識FTP協議,知道FTP協議的是什么) ; 第二步: 當然了解MM有興趣愛好了(了解FTP協議有哪些命令和工作過程)第三步:如果對方是你的菜的話,那當然要采取追求的了(就好比用了解到的FTP協議來實現一個文件上傳下載器)。不過追MM好像對我來說還是比較難的了, 所以還是言歸正傳了,還是好好的學習我的代碼吧,回到本章的內容——FTP的協議。
(注:最近想好好改進下文章的幽默程度,所以文章中會盡量以有趣的生活中的例子來表述網絡編程的知識,希望可以讓大家在學習知識的同時也可以獲得樂趣,如果有什么地方理解不准確的還望大家多多指出。)
一、FTP協議的自我介紹
我們在上學的時候,同學們第一次開學的時候老師一般會組織大學到講台上進行自我介紹,讓同學都互相認識,同樣,如果對於沒有接觸過FTP協議的朋友來說,FTP協議的自我介紹當然也是不可避免的了,這樣我們才能進一步去了解FTP協議 “這位同學”了,之后才能和他成為好朋友,或者是好基友了。下面就開始FTP協議同學的自我介紹了, 大家熱烈歡迎。
FTP 協議同學: 大家好, 我的名字叫FTP,FTP是文件傳輸協議(File Transfer Protocol,FTP),我的工作就是負責將文件從一台計算機傳輸到另外一台計算機,並且我還可以保證傳輸的可靠性。我的工作流程可以通過下面的一張圖來表達:
從圖中大家應該可以明白我的工作過程了吧,我的工作過程是典型的C/S模式——我的客戶端(在本章實現的文件上傳下載器屬於客戶端)首先發起與我的服務器連接連接,告訴我的服務器說“我現在想和你聊聊天”, 然后我的服務器收到這個請求后給出回答——“聊天,當然可以了,我批准了”,客戶端收到這個信息后,就可以服務器之間就建立一條馬路或者是通道,然后我的客戶端好還想進一步了解下我的服務器,在發出一個說“我想要下載你上面的東西 或者是 我想上傳一些文件到你那里,想讓你幫我保管下,這樣我可以隨處都可以從你那里得到我上傳的資料的”, 我的服務器收到請求后,如果允許客戶端這么做的話就會回答說 “可以啊”(就像我們追女生一樣,建立好關系后,當然就要表白了,此時我們就說“我很喜歡你之類的話”,然后等待MM的回答,“可以啊” 這個答案都是我們希望聽到的答案的),我的客戶端聽到后非常開心,馬上選擇自己需要上傳的文件或者想從服務器下載的文件找到,上傳或者下載該文件的。 我還要補充一點,在訪問我的FTP服務器之前必須登錄,這樣我的服務器才認識你,才可能會搭理你的,登錄時就需要客戶端提供一個用戶名和密碼,提供了正確的用戶名和密碼后就可以和我的服務器進行聊天 和請求上傳或下載我服務器上的文件了; 然而我的某些服務器提供了一種匿名的方式,我的客戶端不需要提供用戶名和密碼就可以進行聊天了,其實匿名的方式和我聊天的本質是:提供服務的公司或機構在我的服務器上建立一個公用的賬戶,方便那些沒有提供用戶名和密碼的客戶端與我聊天。 上面就是我的自我介紹了,謝謝大家。
二、.Net 為實現我的客戶端提供了些什么?
可以說微軟真是一位雷鋒叔叔的,因為在他的.Net類庫中提供了很多類庫供我們使用,當然為實現我的客戶端也提供了一些類的支持的, 現在就看看這位好人幫我們提供了哪些類來對實現一個FTP客戶端程序的支持的。
這位好人通過命名空間System.Net下的FtpWebRequest類和FtpWebResponse類提供對實現FTP客戶端的支持。
2.1 FtpWebRequest類
該類是WebRequest類的派生類,FTPWebRequest類用於向服務器發出請求,告訴服務器說“我想和你聊天",如果要獲得FtpWebRequest的一個實例,則需要使用Create方法來創建實例,對於該類如何使用我在這里也就不一一列出來的, 大家可以查看MSDN的相關文檔來了解方法的使用,並且在本專題實現的程序中也會有所介紹的,下面給出MSDN中的一個鏈接的:
http://msdn.microsoft.com/zh-cn/library/8exfzxft.aspx
2.2 FtpWebResponse類
FTP客戶端既然發話了,服務器當然也要有所表示的了, 不要啞巴一樣不說話的,總要給個答復的,FtpWebResponse類就負責封裝FTP服務器對客戶端請求的回答的一個類。FTP客戶端通過GetResponse方法來獲得FtpWebResponse類的對象的,如果服務器回答說“我們可以聊天的”,這樣就說明他們倆就可以互相溝通了,就好比追MM的時候你問MM說“可以給電話號碼給我嗎?”,然后MM對你也有好感就告訴你一個號碼后,得到MM的號碼也就和MM建立了溝通的通道了,就好比服務器回答“我們可以聊天的”。之后客戶端和服務器就可以進行進一步的溝通(上傳文件到服務器或者要求服務器給些文件給客戶端),之后的過程就好比你可以通過電話號碼和MM進一步的交流,知道MM的有些什么性格和愛好的。下面提供一個MSDN中該類的使用鏈接,這里我就不一一介紹他的成員了,大家可以到MSDN中查看的,上面每個屬性和方法都有一個比較好的解釋,並且大家也可以通過下面實現的FTP客戶端程序進一步了解該類的使用:
http://msdn.microsoft.com/zh-cn/library/system.net.ftpwebresponse.aspx
三、如何實現一個FTP客戶端程序?——看完下面的介紹你就會知道了
通過FTP協議的自我介紹部分大家應該可以明白了FTP協議的工作過程的,然而一個FTP客戶端程序就是基於FTP協議的文件上傳下載器,通過這個程序大家可以對FTP服務器上的資料進行瀏覽、上傳和下載等操作的。
程序中主要模塊的代碼:
登錄模塊:

#region 登錄模塊的實現 // 登錄服務器事件 private void btnlogin_Click(object sender, EventArgs e) { if (tbxServerIp.Text == string.Empty) { MessageBox.Show("請先填寫服務器IP地址", "提示"); return; } ftpUristring = "ftp://" + tbxServerIp.Text; networkCredential = new NetworkCredential(tbxUsername.Text, tbxPassword.Text); if (ShowFtpFileAndDirectory() == true) { btnlogin.Enabled = false; btnlogout.Enabled = true; lstbxFtpResources.Enabled = true; lstbxFtpState.Enabled = true; tbxServerIp.Enabled = false; if (chkbxAnonymous.Checked == false) { tbxUsername.Enabled = false; tbxPassword.Enabled = false; chkbxAnonymous.Enabled = false; } else { chkbxAnonymous.Enabled = false; } tbxloginmessage.Text = "登錄成功"; btnUpload.Enabled = true; btndownload.Enabled = true; btnDelete.Enabled = true; } else { lstbxFtpState.Enabled = true; tbxloginmessage.Text = "登錄失敗"; } } // 顯示資源列表 private bool ShowFtpFileAndDirectory() { lstbxFtpResources.Items.Clear(); string uri = string.Empty; if (currentDir == "/") { uri = ftpUristring; } else { uri = ftpUristring + currentDir; } string[] urifield = uri.Split(' '); uri = urifield[0]; FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.ListDirectoryDetails); // 獲得服務器返回的響應信息 FtpWebResponse response = GetFtpResponse(request); if (response == null) { return false; } lstbxFtpState.Items.Add("連接成功,服務器返回的是:"+response.StatusCode+" "+response.StatusDescription); // 讀取網絡流數據 Stream stream = response.GetResponseStream(); StreamReader streamReader = new StreamReader(stream,Encoding.Default); lstbxFtpState.Items.Add("獲取響應流...."); string s = streamReader.ReadToEnd(); streamReader.Close(); stream.Close(); response.Close(); lstbxFtpState.Items.Add("傳輸完成"); // 處理並顯示文件目錄列表 string[] ftpdir = s.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); lstbxFtpResources.Items.Add("↑返回上層目錄"); int length = 0; for (int i = 0; i < ftpdir.Length; i++) { if (ftpdir[i].EndsWith(".")) { length = ftpdir[i].Length - 2; break; } } for (int i = 0; i < ftpdir.Length; i++) { s = ftpdir[i]; int index = s.LastIndexOf('\t'); if (index == -1) { if (length < s.Length) { index = length; } else { continue; } } string name = s.Substring(index + 1); if (name == "." || name == "..") { continue; } // 判斷是否為目錄,在名稱前加"目錄"來表示 if (s[0] == 'd' || (s.ToLower()).Contains("<dir>")) { string[] namefield = name.Split(' '); int namefieldlength = namefield.Length; string dirname; dirname = namefield[namefieldlength - 1]; // 對齊 dirname = dirname.PadRight(34,' '); name = dirname; // 顯示目錄 lstbxFtpResources.Items.Add("[目錄]" + name); } } for (int i = 0; i < ftpdir.Length; i++) { s = ftpdir[i]; int index = s.LastIndexOf('\t'); if (index == -1) { if (length < s.Length) { index = length; } else { continue; } } string name = s.Substring(index + 1); if (name == "." || name == "..") { continue; } // 判斷是否為文件 if (!(s[0] == 'd' || (s.ToLower()).Contains("<dir>"))) { string[] namefield = name.Split(' '); int namefieldlength = namefield.Length; string filename; filename = namefield[namefieldlength - 1]; // 對齊 filename = filename.PadRight(34, ' '); name = filename; // 顯示文件 lstbxFtpResources.Items.Add(name); } } return true; } // 注銷事件 private void btnlogout_Click(object sender, EventArgs e) { btnlogin.Enabled = true; btnlogout.Enabled = false; tbxServerIp.Enabled = true; tbxServerIp.SelectAll(); tbxServerIp.Focus(); chkbxAnonymous.Enabled = true; if (chkbxAnonymous.Checked == false) { tbxUsername.Enabled = true; tbxPassword.Enabled = true; } tbxloginmessage.Text = "你已經退出了。"; lstbxFtpResources.Items.Clear(); lstbxFtpResources.Enabled = false; lstbxFtpState.Items.Clear(); lstbxFtpState.Enabled = false; btnUpload.Enabled = false; btndownload.Enabled = false; btnDelete.Enabled = false; } #endregion
對FTP服務器操作模塊(本程序中實現下載、上傳和刪除的功能):

#region 對文件的操作模塊實現 // 上傳文件到服務器事件 private void btnUpload_Click(object sender, EventArgs e) { // 選擇要上傳的文件 OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.FileName = openFileDialog.FileNames.ToString(); openFileDialog.Filter = "所有文件(*.*)|*.*"; if (openFileDialog.ShowDialog() != DialogResult.OK) { return; } FileInfo fileinfo = new FileInfo(openFileDialog.FileName); try { string uri = GetUriString(fileinfo.Name); FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.UploadFile); request.ContentLength = fileinfo.Length; int buflength = 8196; byte[] buffer = new byte[buflength]; FileStream filestream = fileinfo.OpenRead(); Stream responseStream = request.GetRequestStream(); lstbxFtpState.Items.Add("打開上傳流,文件上傳中..."); int contenlength = filestream.Read(buffer, 0, buflength); while (contenlength != 0) { responseStream.Write(buffer, 0, contenlength); contenlength = filestream.Read(buffer, 0, buflength); } responseStream.Close(); filestream.Close(); FtpWebResponse response = GetFtpResponse(request); if (response == null) { lstbxFtpState.Items.Add("服務器未響應..."); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; return; } lstbxFtpState.Items.Add("上傳完畢,服務器返回:" + response.StatusCode + " " + response.StatusDescription); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; MessageBox.Show("上傳成功!"); // 上傳成功后,立即刷新服務器目錄列表 ShowFtpFileAndDirectory(); } catch (WebException ex) { lstbxFtpState.Items.Add("上傳發生錯誤,返回信息為:" + ex.Status); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; MessageBox.Show(ex.Message, "上傳失敗"); } } private string GetUriString(string filename) { string uri = string.Empty; if (currentDir.EndsWith("/")) { uri = ftpUristring + currentDir + filename; } else { uri = ftpUristring + currentDir + "/" + filename; } return uri; } // 從服務器上下載文件到本地事件 private void btndownload_Click(object sender, EventArgs e) { string fileName = GetSelectedFile(); if (fileName.Length == 0) { MessageBox.Show("請選擇要下載的文件!","提示"); return; } // 選擇保存文件的位置 SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.FileName = fileName; saveFileDialog.Filter = "所有文件(*.*)|(*.*)"; if (saveFileDialog.ShowDialog() != DialogResult.OK) { return; } string filePath = saveFileDialog.FileName; try { string uri = GetUriString(fileName); FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.DownloadFile); FtpWebResponse response = GetFtpResponse(request); if (response == null) { lstbxFtpState.Items.Add("服務器未響應..."); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; return; } Stream responseStream = response.GetResponseStream(); FileStream filestream = File.Create(filePath); int buflength = 8196; byte[] buffer = new byte[buflength]; int bytesRead =1; lstbxFtpState.Items.Add("打開下載通道,文件下載中..."); while (bytesRead != 0) { bytesRead = responseStream.Read(buffer, 0, buflength); filestream.Write(buffer, 0, bytesRead); } responseStream.Close(); filestream.Close(); lstbxFtpState.Items.Add("下載完畢,服務器返回:" + response.StatusCode + " " + response.StatusDescription); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; MessageBox.Show("下載完成!"); } catch (WebException ex) { lstbxFtpState.Items.Add("發生錯誤,返回狀態為:" + ex.Status); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; MessageBox.Show(ex.Message, "下載失敗"); } } // 獲得選擇的文件 // 如果選擇的是目錄或者是返回上層目錄,則返回null private string GetSelectedFile() { string filename = string.Empty; if (!(lstbxFtpResources.SelectedIndex == -1 || lstbxFtpResources.SelectedItem.ToString().Substring(0, 4) == "[目錄]")) { string[] namefield = lstbxFtpResources.SelectedItem.ToString().Split(' '); filename = namefield[0]; } return filename; } // 刪除服務器文件事件 private void btnDelete_Click(object sender, EventArgs e) { string filename = GetSelectedFile(); if (filename.Length == 0) { MessageBox.Show("請選擇要刪除的文件!", "提示"); return; } try { string uri = GetUriString(filename); if (MessageBox.Show("確定要刪除文件 " + filename + " 嗎?", "確認文件刪除", MessageBoxButtons.YesNo) == DialogResult.Yes) { FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.DeleteFile); FtpWebResponse response = GetFtpResponse(request); if (response == null) { lstbxFtpState.Items.Add("服務器未響應..."); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; return; } lstbxFtpState.Items.Add("文件刪除成功,服務器返回:" + response.StatusCode + " " + response.StatusDescription); ShowFtpFileAndDirectory(); } else { return; } } catch (WebException ex) { lstbxFtpState.Items.Add("發生錯誤,返回狀態為:" + ex.Status); lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1; MessageBox.Show(ex.Message, "刪除失敗"); } } #endregion
由於程序的演示效果需要結合下一專題介紹的FTP服務器,具體的演示效果大家可以查看——專題十二:實現一個簡單的FTP服務器,下面就列出程序的主界面截圖:
四、小結
這個專題的介紹就到這里的,在下一個專題將和大家介紹下如何實現一個FTP服務器,這樣再加上本專題制作的FTP文件上傳下載器就可以形成一個完整的軟件套件,自己實現FTP文件上傳下載器訪問自己實現的FTP服務器將會讓大家覺得很很有趣的, 想趕快體驗下這樣的一種樂趣嗎?那就趕快下載本專題的源碼來親身體驗下吧。通過希望通過本專題讓大家對FTP協議不再陌生,並且做Asp.net開發的朋友,文件的上傳和下載是一個公共模塊的,然后Asp.net中的文件上傳和下載只是通過瀏覽器向HTTP服務器發送HTTP命令,來告訴HTTP服務器說“我想和你對話”,“我想要你上面的某某文件”以及“我想上傳一個文件到你的上面去”等等的對話,這個系列完成之后,我也會和大家總結下網絡編程的知識的。
最后提供下源碼下載地址:http://files.cnblogs.com/zhili/FTPUpDownloader.zip