最近工做中遇到了一個場景,要用C#客戶端訪問FTP服務器,並實現文件下載功能。以前我使用了一種很是簡單粗暴的方法,由於客戶端以前就用到了Xilium.CefGlue(能夠理解爲一個WebKit內核)來實現瀏覽網頁的功能,客戶的需求又僅停留在登陸FTP對部分壓縮包和doc文件進行下載,我索性直接建了個頁面,用這個WebKit內核實現對FTP進行訪問,效果和Chrome瀏覽器訪問FTP類似。html
不過,這個方法有下面三個缺點:sql
一、Xilium.CefGlue類庫佔用的空間很大,若是就爲了實現客戶端訪問FTP服務器,放入一個WebKit內核,平白增長了幾十MB的空間佔用,是很是不划算的。數組
二、Xilium.CefGlue打開FTP相似Chrome的打開方式,遇到txt、sql等擴展名的文件時,會直接在瀏覽器中打開,遇到pdf擴展名的文件時,會使用相關插件打開(或因無相關處理工具而進入錯誤頁)。遇到其餘擴展名的文件時,如exe、rar、zip、doc等,纔會提示下載。瀏覽器
三、沒法知足許多用戶定製化的需求(雖然內核是開源的,但你敢改麼?)。服務器
因此說,使用C#客戶端訪問FTP服務器,最好的辦法仍是本身寫一套工具類,實現FTP協議下的上傳、下載、建立目錄、查詢目錄下文件列表等操做。工具
使用Serv-U工具能夠在本機自建一個FTP服務,方法以下:測試
一、安裝Serv-U並註冊(試用版可使用30天,我用的版本是10.3.0.1)this
二、找到「新建域」按鈕,新建一個FTP服務加密
三、新建域嚮導第一步:創建FTP域名,填寫說明信息spa
四、新建域嚮導第二步:設置各協議端口號,通常來講使用默認端口號便可
四、新建域嚮導第三步:也使用默認設置
五、新建域嚮導第四步:設置密碼加密模式,選擇「使用服務器設置」
六、Serv-U詢問是否要創建用戶,點擊「是」便可
七、創建用戶嚮導第一步:設置用戶登陸ID爲tsybius
八、創建用戶嚮導第二步:設置密碼,這裏設置爲123456
九、創建用戶嚮導第三步:設置用戶登陸FTP後看到的根目錄
十、創建用戶嚮導第四步:設置訪問權限,有隻讀訪問和徹底訪問兩種,這裏我選擇了徹底訪問
十一、FTP創建完畢,在瀏覽器地址欄(或資源管理器地址欄)輸入下面地址便可登陸FTP:
ftp://tsybius:123456@localhost/
通常來講,使用C#程序訪問FTP,只須要支持如下幾個功能就足夠了:
一、給定FTP下某一目錄地址,獲取該地址下全部的文件和目錄及它們的詳細信息
二、向FTP上傳文件
三、從FTP下載文件
四、其餘輔助功能(如刷新等)
咱們要實現的功能能夠參考Xftp,即XShell打開的FTP訪問工具
C#中調用FTP的方法是類似的,如獲取指定目錄下全部文件的詳細信息能夠寫成:
using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; namespace FTPManager { class FtpHelper { public static FtpFileInfo[] GetFtpFileInfos(string ftpPath, string userName, string passWord) { LinkedList<FtpFileInfo> linkedList = new LinkedList<FtpFileInfo>(); var reqFtp = (FtpWebRequest)WebRequest.Create(new Uri(ftpPath)); reqFtp.UsePassive = false; reqFtp.UseBinary = true; //reqFTP.EnableSsl = true;//加密方式傳送數據 FTP 服務器要支持 reqFtp.Credentials = new NetworkCredential(userName, passWord); reqFtp.Method = WebRequestMethods.Ftp.ListDirectoryDetails; var response = (FtpWebResponse)reqFtp.GetResponse(); var reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8); string fileDetail = reader.ReadLine(); while (fileDetail != null) { linkedList.AddLast(new FtpFileInfo(fileDetail)); fileDetail = reader.ReadLine(); } reader.Close(); response.Close(); return linkedList.ToArray(); } } }
其中FtpFileInfo是我設計的一個用於管理FTP文件信息的類。下面貼出的代碼只是一個很是簡陋的版本,並無通過多少測試,不過可被看作一個解決問題的思路:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace FTPManager { public class FtpFileInfo { public string UnixFileType { get; set; } public string Permission { get; set; } public string NumberOfHardLinks { get; set; } public string Owner { get; set; } public string Group { get; set; } public string Size { get; set; } public string LastModifiedDate { get; set; } public string FileName { get; set; } public string FileDetail { get; set; } public FtpFileInfo(string fileDetail) { this.FileDetail = fileDetail; int counter = 1; string[] propertyBlocks = fileDetail.Split(' '); foreach (string propertyBlock in propertyBlocks) { switch (counter) { case 1: { //unix file types & permissions if (string.IsNullOrWhiteSpace(propertyBlock)) { continue; } else { if (propertyBlock.Length == 10) { UnixFileType = propertyBlock[0].ToString(); Permission = propertyBlock.Substring(1); } counter++; } } break; case 2: { //number of hard links if (string.IsNullOrWhiteSpace(propertyBlock)) { continue; } else { NumberOfHardLinks = propertyBlock; counter++; } } break; case 3: { //owner if (string.IsNullOrWhiteSpace(propertyBlock)) { continue; } else { Owner = propertyBlock; counter++; } } break; case 4: { //group if (string.IsNullOrWhiteSpace(propertyBlock)) { continue; } else { Group = propertyBlock; counter++; } } break; case 5: { //size if (string.IsNullOrWhiteSpace(propertyBlock)) { continue; } else { Size = propertyBlock; counter++; } } break; case 6: case 7: case 8: { //last-modified date if (string.IsNullOrWhiteSpace(propertyBlock)) { continue; } else { LastModifiedDate += propertyBlock + " "; counter++; } } break; case 9: { //file name if (string.IsNullOrWhiteSpace(propertyBlock)) { FileName += " "; } else { FileName += propertyBlock; } } break; } } LastModifiedDate = LastModifiedDate.Trim(); FileName = FileName.Trim(); } } }
根據reqFtp.Method的不一樣,返回的流內容也會不一樣,咱們須要對返回流的內容進行解析。reqFtp.Method一共支持如下幾種枚舉類型:
// 摘要: // 表示可與 FTP 請求一塊兒使用的 FTP 協議方法的類型。沒法繼承此類。 public static class Ftp { // 摘要: // 表示要用於將文件追加到 FTP 服務器上的現有文件的 FTP APPE 協議方法。 public const string AppendFile = "APPE"; // // 摘要: // 表示要用於刪除 FTP 服務器上的文件的 FTP DELE 協議方法。 public const string DeleteFile = "DELE"; // // 摘要: // 表示要用於從 FTP 服務器下載文件的 FTP RETR 協議方法。 public const string DownloadFile = "RETR"; // // 摘要: // 表示要用於從 FTP 服務器上的文件檢索日期時間戳的 FTP MDTM 協議方法。 public const string GetDateTimestamp = "MDTM"; // // 摘要: // 表示要用於檢索 FTP 服務器上的文件大小的 FTP SIZE 協議方法。 public const string GetFileSize = "SIZE"; // // 摘要: // 表示獲取 FTP 服務器上的文件的簡短列表的 FTP NLIST 協議方法。 public const string ListDirectory = "NLST"; // // 摘要: // 表示獲取 FTP 服務器上的文件的詳細列表的 FTP LIST 協議方法。 public const string ListDirectoryDetails = "LIST"; // // 摘要: // 表示在 FTP 服務器上建立目錄的 FTP MKD 協議方法。 public const string MakeDirectory = "MKD"; // // 摘要: // 表示打印當前工做目錄的名稱的 FTP PWD 協議方法。 public const string PrintWorkingDirectory = "PWD"; // // 摘要: // 表示移除目錄的 FTP RMD 協議方法。 public const string RemoveDirectory = "RMD"; // // 摘要: // 表示重命名目錄的 FTP RENAME 協議方法。 public const string Rename = "RENAME"; // // 摘要: // 表示將文件上載到 FTP 服務器的 FTP STOR 協議方法。 public const string UploadFile = "STOR"; // // 摘要: // 表示將具備惟一名稱的文件上載到 FTP 服務器的 FTP STOU 協議方法。 public const string UploadFileWithUniqueName = "STOU"; }
更詳細的說明可參考MSDN頁面:https://msdn.microsoft.com/zh-cn/library/ms144320.aspx
前面代碼中使用到的是WebRequestMethods.Ftp.ListDirectoryDetails,所以返回流的內容爲
返回的內容和Linux中命令「ls -l」是同樣的,從左到右依次是:
Unix file types(Unix文件類型)、permissions(各用戶權限)、number of hard links(硬鏈接數)、owner(全部者)、group(所屬組)、size(文件大小)、last-modified date(文件最後更改時間)、filename(文件名)
關於Linux中ls命令的細節,能夠參考維基百科頁面:https://en.wikipedia.org/wiki/Ls
在主窗體下,寫以下代碼便可調用咱們剛纔實現的FtpHelper.GetFtpFileInfos:
private void FormMain_Load(object sender, EventArgs e) { try { FtpFileInfo[] ftpFileInfos = FtpHelper.GetFtpFileInfos("ftp://localhost/", "tsybius", "123456"); dgvFileList.DataSource = ftpFileInfos; } catch (Exception ex) { MessageBox.Show(ex.Message); } }
(其中dgvFileList爲一個DataGridView控件)
代碼執行效果以下:
上面代碼應注意之處有:
一、DataGridView的相關樣式設定這裏再也不贅述,我手動添加了四列,併爲每列設置了DataPropertyName與FtpFileInfo字段相對應。
二、能夠看出直接顯示在DataGridView上的內容並不適合人閱讀,文件類型、文件大小能夠經過本身寫兩個繼承自DataGridViewTextBoxColumn的類來實現使人舒服一些的顯示。
三、上面的例子中,咱們把數組傳入DataGridView的DataSource,這樣作有一個弊端是不能點擊各列列頭對數據進行排序。若是但願對數據排序,可將結果集轉換成DataTable格式。
另附上我在網上找的幾段C#訪問FTP的代碼,可供參考:
http://blog.csdn.net/chr23899/article/details/41787863
http://www.cnblogs.com/wang726zq/archive/2012/07/30/ftp.html
END