使用C#客戶端訪問FTP服務的一個解決方案

1、寫在前面

最近工做中遇到了一個場景,要用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協議下的上傳、下載、建立目錄、查詢目錄下文件列表等操做。工具

2、使用Serv-U創建本地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/

3、使用C#程序訪問FTP

通常來講,使用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

相關文章
相關標籤/搜索