65.9K
CodeProject 正在变化。 阅读更多。
Home

一个用于.NET 2.0的FTP客户端库

2005年10月18日

CPOL

6分钟阅读

viewsIcon

3876899

downloadIcon

37742

为.NET 2.0中的FtpRequest提供了有用的封装。

引言

更新

随着Codeplex即将关闭,我已经将源代码移植到了一个GitHub仓库

.NET 1.x框架的一个令人恼火的遗漏是缺乏对FTP的支持。可以通过各种库(有些是免费的,有些是商业的)来弥补这一不足。然而,随着Visual Studio 2005和.NET框架2.0的发布,FTP功能得到了欢迎的引入。

除了增加FTP支持外,Microsoft还将Web、邮件和FTP请求的支持从System.Web移到了System.Net中,这是一个更合理的做法。

但是仍然存在一个问题:FTP支持实际上并不是一个FTP客户端,它只是在FtpWebRequest中支持该协议,就像HttpWebRequest支持Web请求一样。没有“下载文件”或“获取目录列表”的功能——你仍然需要自己来解决这个问题。

这就是我希望我的FTPclient库能派上用场的地方。它不是一个功能齐全、全面的客户端,但它提供了所有最常用的功能,并且可以作为基础,如果你需要的话,可以添加任何缺失的功能。

背景

我假设你已经安装了.NET 2.0或其某个Beta版本。这个库是在VS2005的beta 2版本上编写的,所以如果你有更新的版本或已发布的版本,可能需要进行一些更改。如果任何框架更改破坏了代码,我会尽量更新它。

我编写这个库是为了支持我自己的应用程序,该应用程序需要将文件上传和下载到供应商的FTP服务器:该服务器运行在Linux上,但我也在Windows NT和XP自带的Microsoft FTP服务器上进行了测试。

FTPClient设计

FTPclient的设计是无状态运行的,类似于Web请求的工作方式。它不会保持连接打开,而是每次请求都会连接、执行请求的操作,然后断开连接。

这确实意味着它非常适合单次操作,但如果要在执行多个请求时保持连接打开,那么在性能方面就不是理想的。然而,如果有人愿意花时间,这个库可以被改编成这样运行。

FtpWebRequest基础

任何类型的FTP请求都可以分解为六个步骤:

  • 为URL创建一个Web请求。
  • 设置登录凭据(用户名、密码)。
  • 设置所需选项和要执行的操作。
  • 上传所需数据(某些操作不使用)。
  • 下载数据或结果(同样,某些操作不使用)。
  • 关闭请求(和连接)。

虽然这看起来足够简单,但有几个问题可能会让你措手不及(它们也曾让我措手不及!)。一个问题是FtpWebRequest可以通过KeepAlive属性支持连接,该属性默认设置为True。在我的类中,它被关闭了,以便在命令完成后关闭每个连接。

一个例子:下载文件

以下是使用FtpWebRequest下载文件的步骤示例:

'Values to use
Const localFile As String = "C:\myfile.bin"
Const remoteFile As String = "/pub/myftpfile.bin"
Const host As String = "ftp://ftp.myhost.com"
Const username As String = "myuserid"
Const password As String = "mypassword"

'1. Create a request: must be in ftp://hostname format, 
'   not just ftp.myhost.com
Dim URI As String = host & remoteFile
Dim ftp As System.Net.FtpWebRequest = _
    CType(FtpWebRequest.Create(URI), FtpWebRequest)

'2. Set credentials
ftp.Credentials = New _
    System.Net.NetworkCredential(username, password)

'3. Settings and action
ftp.KeepAlive = False
'we want a binary transfer, not textual data
ftp.UseBinary = True
'Define the action required (in this case, download a file)
ftp.Method = System.Net.WebRequestMethods.Ftp.DownloadFile

'4. If we were using a method that uploads data e.g. UploadFile
'   we would open the ftp.GetRequestStream here an send the data

'5. Get the response to the Ftp request and the associated stream
Using response As System.Net.FtpWebResponse = _
      CType(ftp.GetResponse, System.Net.FtpWebResponse)
  Using responseStream As IO.Stream = response.GetResponseStream
    'loop to read & write to file
    Using fs As New IO.FileStream(localFile, IO.FileMode.Create)
      Dim buffer(2047) As Byte
      Dim read As Integer = 0
      Do
        read = responseStream.Read(buffer, 0, buffer.Length)
        fs.Write(buffer, 0, read)
      Loop Until read = 0 'see Note(1)
      responseStream.Close()
      fs.Flush()
      fs.Close()
    End Using
    responseStream.Close()
  End Using
  response.Close()
End Using

'6. Done! the Close happens because ftp goes out of scope
'   There is no .Close or .Dispose for FtpWebRequest

注意 (1):我发现使用Loop Until read < buffer.Size不起作用,因为有时远程服务器返回的数据小于缓冲区大小,并且有可能在到达流的末尾之前就满足此条件。我发现read = 0似乎只在流结束时发生。

在这个特定的例子中,对于任何类型的FTP操作,步骤1和2都会以同样的方式重复,所以我将它们放入一个可以重用的函数中。步骤3很大程度上取决于你将执行的操作,上传或下载的类型也是如此,但我创建了一个通用的函数GetResponseString,它可以读取文本响应(例如目录列表)。此代码也缺少任何错误处理。

使用FtpClient

要使用FtpClient,请创建一个对象的新实例,定义主机、用户名和密码。

Dim myFtp As New FtpClient(hostname, username, password)

获取FTP服务器的/pub目录的目录列表:

Dim fullList As FtpDirectory = myFtp.GetDirectoryDetail("/pub/")

要确定其中哪些是文件,请使用GetFiles函数。

Dim filesOnly As FtpDirectory = fullList.GetFiles()

下载或上传文件 - 一个简单的例子:

myFtp.Download("/pub/myfile.bin", "C:\myfile.bin")
myFtp.Upload("C:\myfile.bin", "/pub/myfile.bin")

或者一个更复杂的例子,下载目录中的所有文件。

For Each file As FtpFileInfo In myFtp.GetDirectoryDetail("/pub/").GetFiles
    myFtp.Download(file, "C:\" & file.Filename)
Next file

如果对于上传或下载的目标文件已存在,客户端默认会抛出异常,以防止不必要的覆盖。要关闭此行为,请将最后一个可选参数PermitOverwrite设置为True

读取FTP目录

读取FTP目录相当简单:使用ListDirectoryListDirectoryDetails请求方法。ListDirectory非常简单——它返回一个List(Of String)——但列表中文件或目录条目之间没有区别,所以在大多数情况下可能没有用。

ListDirectoryDetails提供了关于每个文件的更多信息。它使用详细的FTP列表,该列表返回一个FtpFileInfo对象的集合。FtpFileInfo对象包含从详细目录列表中读取的条目的完整路径、名称、日期/时间和文件大小,这与System.IO中的FileInfo类似。

详细的FTP目录列表输出根据FTP服务器及其运行的操作系统而有所不同。特别是,NT/XP FTP服务器可能与UNIX和Linux的结果非常不同。FtpFileInfo的构造函数以列表文本作为参数,并尝试使用几个正则表达式模式(保存在_ParseFormats中)来解析它。

如果您在特定FTP服务器读取详细目录时遇到错误,您可能需要将自己的正则表达式添加到_ParseFormats字符串数组中,以使库能够工作。我认为提供的正则表达式应该适用于大多数服务器。如果您发现需要新的模式,请告诉我。

当前目录

我在设计中包含了存储当前目录的功能,风格与标准的FTP客户端应用程序相同,尽管我本人并未将其用于此。要设置目录,请使用FtpClient.CurrentDirectory = "/path"。当您不为远程文件指定路径时,就会用到它。

    Dim myFtp As New FtpClient(hostname, username, password)
    myFtp.CurrentDirectory = "/pub"
    myFtp.Download("fileInPub.bin", "C:\test\fileInPub.bin")
    
    myFtp.CurrentDirectory = "/pub/etc"
    'will upload to file /pub/etc/MyFile.bin
    myFtp.Upload("C:\MyFile.bin")

可能的改进

如前所述,此客户端不支持使用打开的连接,并且每次请求都需要登录。在我的应用程序中,这并不是一个大问题,而且我发现对保持连接活动和执行多个请求的支持文档不足,所以我决定采取KISS原则(保持简单)。

另一个改进可能是增加对异步操作的支持,FtpWebRequest对象支持这一点,但同样,KISS原则在我的项目中占了上风。

总之,我希望你能发现这是一个有用的微型库,能够完成你对FTP所需的基本操作。

© . All rights reserved.