C#通过FTP下载所有文件和子目录

时间:2022-05-21 05:48:53

General Info
I'm still in the process of learning C#. To help myself out, I'm trying to create a program that will automatically synchronise all of my local projects with a folder on my FTP server. This so that whether I'm at school or at home, I always have the same projects available to me.

一般信息我还在学习C#。为了帮助自己,我正在尝试创建一个程序,它将自动将我的所有本地项目与我的FTP服务器上的文件夹同步。这样无论我在学校还是在家,我总是可以使用相同的项目。

I know there are programs like Dropbox that already do this for me, but I figured creating something like that myself will teach me a lot along the way.

我知道像Dropbox这样的程序已经为我做了这个,但我想创造类似的东西,我会在一路上教给我很多东西。

The problem
My first step towards my goal was to just download all files, subdirectories and subfiles from my FTP server. I've managed to download all files from a directory with the code below. However, my code only lists the folder names and the files in the main directory. Subfolders and subfiles are never returned and never downloaded. Aside from that, the server returns a 550 error because I'm trying to download the folders as if they are files. I've been on this for 4+ hours now, but I just can't find anything on how to fix these problems and make it work. Therefor I'm hoping you guys will help me out :)

问题我迈向目标的第一步是从我的FTP服务器下载所有文件,子目录和子文件。我已设法从以下代码的目录下载所有文件。但是,我的代码只列出了主目录中的文件夹名称和文件。子文件夹和子文件永远不会返回,也永远不会下载。除此之外,服务器返回550错误,因为我正在尝试下载文件夹,就像它们是文件一样。我已经在这上面了4个多小时了,但我找不到任何关于如何解决这些问题并让它发挥作用的东西。因此,我希望你们能帮帮我:)

Code

public string[] GetFileList()
{
    string[] downloadFiles;
    StringBuilder result = new StringBuilder();
    WebResponse response = null;
    StreamReader reader = null;

    try
    {
        FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url);
        request.UseBinary = true;
        request.Method = WebRequestMethods.Ftp.ListDirectory;
        request.Credentials = new NetworkCredential(ftpUserName, ftpPassWord);
        request.KeepAlive = false;
        request.UsePassive = false;
        response = request.GetResponse();
        reader = new StreamReader(response.GetResponseStream());
        string line = reader.ReadLine();
        while (line != null)
        {
            result.Append(line);
            result.Append("\n");
            line = reader.ReadLine();
        }
        result.Remove(result.ToString().LastIndexOf('\n'), 1);
        return result.ToString().Split('\n');
    }
    catch (Exception ex)
    {
        if (reader != null)
        {
            reader.Close();
        }
        if (response != null)
        {
            response.Close();
        }
        downloadFiles = null;
        return downloadFiles;
    }
}

private void Download(string file)
{
    try
    {
        string uri = url + "/" + file;
        Uri serverUri = new Uri(uri);
        if (serverUri.Scheme != Uri.UriSchemeFtp)
        {
            return;
        }
        FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url + "/" + file);
        request.UseBinary = true;
        request.Method = WebRequestMethods.Ftp.DownloadFile;
        request.Credentials = new NetworkCredential(ftpUserName, ftpPassWord);
        request.KeepAlive = false;
        request.UsePassive = false;
        FtpWebResponse response = (FtpWebResponse)request.GetResponse();
        Stream responseStream = response.GetResponseStream();
        FileStream writeStream = new FileStream(localDestnDir + "\\" + file, FileMode.Create);                
        int Length = 2048;
        Byte[] buffer = new Byte[Length];
        int bytesRead = responseStream.Read(buffer, 0, Length);
        while (bytesRead > 0)
        {
            writeStream.Write(buffer, 0, bytesRead);
            bytesRead = responseStream.Read(buffer, 0, Length);
        }
        writeStream.Close();
        response.Close();
    }
    catch (WebException wEx)
    {
        MessageBox.Show(wEx.Message, "Download Error");
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Download Error");
    }
}

1 个解决方案

#1


24  

The FtpWebRequest does not have any explicit support for recursive file operations (including downloads). You have to implement the recursion yourself:

FtpWebRequest对递归文件操作(包括下载)没有任何明确的支持。你必须自己实现递归:

  • List the remote directory
  • 列出远程目录

  • Iterate the entries, downloading files and recursing into subdirectories (listing them again, etc.)
  • 迭代条目,下载文件并递归到子目录(再次列出它们等)

Tricky part is to identify files from subdirectories. There's no way to do that in a portable way with the FtpWebRequest. The FtpWebRequest unfortunately does not support the MLSD command, which is the only portable way to retrieve directory listing with file attributes in FTP protocol. See also Checking if object on FTP server is file or directory.

棘手的部分是识别子目录中的文件。使用FtpWebRequest以便携方式无法做到这一点。遗憾的是,FtpWebRequest不支持MLSD命令,这是在FTP协议中检索具有文件属性的目录列表的唯一可移植方式。另请参阅检查FTP服务器上的对象是文件还是目录。

Your options are:

你的选择是:

  • Do a operation on a file name that is certain to fail for file and succeeds for directories (or vice versa). I.e. you can try to download the "name". If that succeeds, it's a file, if that fails, it's a directory.
  • 对文件名执行操作,该文件名肯定会对文件失败并对目录成功(反之亦然)。即你可以尝试下载“名称”。如果成功,它是一个文件,如果失败,它就是一个目录。

  • You may be lucky and in your specific case, you can tell a file from a directory by a file name (i.e. all your files have an extension, while subdirectories do not)
  • 您可能很幸运,在您的特定情况下,您可以通过文件名告诉目录中的文件(即所有文件都有扩展名,而子目录没有)

  • You use a long directory listing (LIST command = ListDirectoryDetails method) and try to parse a server-specific listing. Many FTP servers use *nix-style listing, where you identify a directory by the d at the very beginning of the entry. But many servers use a different format. The following example uses this approach (assuming the *nix format)
  • 您使用长目录列表(LIST命令= ListDirectoryDe​​tails方法)并尝试解析特定于服务器的列表。许多FTP服务器使用* nix样式列表,您可以在条目的最开头通过d标识目录。但是许多服务器使用不同的格式。以下示例使用此方法(假设为* nix格式)

void DownloadFtpDirectory(string url, NetworkCredential credentials, string localPath)
{
    FtpWebRequest listRequest = (FtpWebRequest)WebRequest.Create(url);
    listRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
    listRequest.Credentials = credentials;

    List<string> lines = new List<string>();

    using (FtpWebResponse listResponse = (FtpWebResponse)listRequest.GetResponse())
    using (Stream listStream = listResponse.GetResponseStream())
    using (StreamReader listReader = new StreamReader(listStream))
    {
        while (!listReader.EndOfStream)
        {
            lines.Add(listReader.ReadLine());
        }
    }

    foreach (string line in lines)
    {
        string[] tokens =
            line.Split(new[] { ' ' }, 9, StringSplitOptions.RemoveEmptyEntries);
        string name = tokens[8];
        string permissions = tokens[0];

        string localFilePath = Path.Combine(localPath, name);
        string fileUrl = url + name;

        if (permissions[0] == 'd')
        {
            if (!Directory.Exists(localFilePath))
            {
                Directory.CreateDirectory(localFilePath);
            }

            DownloadFtpDirectory(fileUrl + "/", credentials, localFilePath);
        }
        else
        {
            FtpWebRequest downloadRequest = (FtpWebRequest)WebRequest.Create(fileUrl);
            downloadRequest.Method = WebRequestMethods.Ftp.DownloadFile;
            downloadRequest.Credentials = credentials;

            using (FtpWebResponse downloadResponse =
                      (FtpWebResponse)downloadRequest.GetResponse())
            using (Stream sourceStream = downloadResponse.GetResponseStream())
            using (Stream targetStream = File.Create(localFilePath))
            {
                byte[] buffer = new byte[10240];
                int read;
                while ((read = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    targetStream.Write(buffer, 0, read);
                }
            }
        }
    }
}

Use the function like:

使用如下功能:

NetworkCredential credentials = new NetworkCredential("user", "mypassword");
string url = "ftp://ftp.example.com/directory/to/download/";
DownloadFtpDirectory(url, credentials, @"C:\target\directory");

If you want to avoid troubles with parsing the server-specific directory listing formats, use a 3rd party library that supports the MLSD command and/or parsing various LIST listing formats; and recursive downloads.

如果要避免解析特定于服务器的目录列表格式的麻烦,请使用支持MLSD命令和/或解析各种LIST列表格式的第三方库;和递归下载。

For example with WinSCP .NET assembly you can download whole directory with a single call to the Session.GetFiles:

例如,使用WinSCP .NET程序集,只需调用Session.GetFiles即可下载整个目录:

// Setup session options
SessionOptions sessionOptions = new SessionOptions
{
    Protocol = Protocol.Ftp,
    HostName = "ftp.example.com",
    UserName = "user",
    Password = "mypassword",
};

using (Session session = new Session())
{
    // Connect
    session.Open(sessionOptions);

    // Download files
    session.GetFiles("/directory/to/download/*", @"C:\target\directory\*").Check();
}

Internally, WinSCP uses the MLSD command, if supported by the server. If not, it uses the LIST command and supports dozens of different listing formats.

在内部,如果服务器支持,WinSCP使用MLSD命令。如果没有,它使用LIST命令并支持许多不同的列表格式。

The Session.GetFiles method is recursive by default.

默认情况下,Session.GetFiles方法是递归的。

(I'm the author of WinSCP)

(我是WinSCP的作者)

#1


24  

The FtpWebRequest does not have any explicit support for recursive file operations (including downloads). You have to implement the recursion yourself:

FtpWebRequest对递归文件操作(包括下载)没有任何明确的支持。你必须自己实现递归:

  • List the remote directory
  • 列出远程目录

  • Iterate the entries, downloading files and recursing into subdirectories (listing them again, etc.)
  • 迭代条目,下载文件并递归到子目录(再次列出它们等)

Tricky part is to identify files from subdirectories. There's no way to do that in a portable way with the FtpWebRequest. The FtpWebRequest unfortunately does not support the MLSD command, which is the only portable way to retrieve directory listing with file attributes in FTP protocol. See also Checking if object on FTP server is file or directory.

棘手的部分是识别子目录中的文件。使用FtpWebRequest以便携方式无法做到这一点。遗憾的是,FtpWebRequest不支持MLSD命令,这是在FTP协议中检索具有文件属性的目录列表的唯一可移植方式。另请参阅检查FTP服务器上的对象是文件还是目录。

Your options are:

你的选择是:

  • Do a operation on a file name that is certain to fail for file and succeeds for directories (or vice versa). I.e. you can try to download the "name". If that succeeds, it's a file, if that fails, it's a directory.
  • 对文件名执行操作,该文件名肯定会对文件失败并对目录成功(反之亦然)。即你可以尝试下载“名称”。如果成功,它是一个文件,如果失败,它就是一个目录。

  • You may be lucky and in your specific case, you can tell a file from a directory by a file name (i.e. all your files have an extension, while subdirectories do not)
  • 您可能很幸运,在您的特定情况下,您可以通过文件名告诉目录中的文件(即所有文件都有扩展名,而子目录没有)

  • You use a long directory listing (LIST command = ListDirectoryDetails method) and try to parse a server-specific listing. Many FTP servers use *nix-style listing, where you identify a directory by the d at the very beginning of the entry. But many servers use a different format. The following example uses this approach (assuming the *nix format)
  • 您使用长目录列表(LIST命令= ListDirectoryDe​​tails方法)并尝试解析特定于服务器的列表。许多FTP服务器使用* nix样式列表,您可以在条目的最开头通过d标识目录。但是许多服务器使用不同的格式。以下示例使用此方法(假设为* nix格式)

void DownloadFtpDirectory(string url, NetworkCredential credentials, string localPath)
{
    FtpWebRequest listRequest = (FtpWebRequest)WebRequest.Create(url);
    listRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
    listRequest.Credentials = credentials;

    List<string> lines = new List<string>();

    using (FtpWebResponse listResponse = (FtpWebResponse)listRequest.GetResponse())
    using (Stream listStream = listResponse.GetResponseStream())
    using (StreamReader listReader = new StreamReader(listStream))
    {
        while (!listReader.EndOfStream)
        {
            lines.Add(listReader.ReadLine());
        }
    }

    foreach (string line in lines)
    {
        string[] tokens =
            line.Split(new[] { ' ' }, 9, StringSplitOptions.RemoveEmptyEntries);
        string name = tokens[8];
        string permissions = tokens[0];

        string localFilePath = Path.Combine(localPath, name);
        string fileUrl = url + name;

        if (permissions[0] == 'd')
        {
            if (!Directory.Exists(localFilePath))
            {
                Directory.CreateDirectory(localFilePath);
            }

            DownloadFtpDirectory(fileUrl + "/", credentials, localFilePath);
        }
        else
        {
            FtpWebRequest downloadRequest = (FtpWebRequest)WebRequest.Create(fileUrl);
            downloadRequest.Method = WebRequestMethods.Ftp.DownloadFile;
            downloadRequest.Credentials = credentials;

            using (FtpWebResponse downloadResponse =
                      (FtpWebResponse)downloadRequest.GetResponse())
            using (Stream sourceStream = downloadResponse.GetResponseStream())
            using (Stream targetStream = File.Create(localFilePath))
            {
                byte[] buffer = new byte[10240];
                int read;
                while ((read = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    targetStream.Write(buffer, 0, read);
                }
            }
        }
    }
}

Use the function like:

使用如下功能:

NetworkCredential credentials = new NetworkCredential("user", "mypassword");
string url = "ftp://ftp.example.com/directory/to/download/";
DownloadFtpDirectory(url, credentials, @"C:\target\directory");

If you want to avoid troubles with parsing the server-specific directory listing formats, use a 3rd party library that supports the MLSD command and/or parsing various LIST listing formats; and recursive downloads.

如果要避免解析特定于服务器的目录列表格式的麻烦,请使用支持MLSD命令和/或解析各种LIST列表格式的第三方库;和递归下载。

For example with WinSCP .NET assembly you can download whole directory with a single call to the Session.GetFiles:

例如,使用WinSCP .NET程序集,只需调用Session.GetFiles即可下载整个目录:

// Setup session options
SessionOptions sessionOptions = new SessionOptions
{
    Protocol = Protocol.Ftp,
    HostName = "ftp.example.com",
    UserName = "user",
    Password = "mypassword",
};

using (Session session = new Session())
{
    // Connect
    session.Open(sessionOptions);

    // Download files
    session.GetFiles("/directory/to/download/*", @"C:\target\directory\*").Check();
}

Internally, WinSCP uses the MLSD command, if supported by the server. If not, it uses the LIST command and supports dozens of different listing formats.

在内部,如果服务器支持,WinSCP使用MLSD命令。如果没有,它使用LIST命令并支持许多不同的列表格式。

The Session.GetFiles method is recursive by default.

默认情况下,Session.GetFiles方法是递归的。

(I'm the author of WinSCP)

(我是WinSCP的作者)