Windows Phone

为 Windows Phone 8 添加 FTP 支持

Uday Gupta

下载代码示例

FTP 是最广泛使用的共享文件协议之一。 它用于手关闭文件到客户端,将安装文件分发到消费者,提供访问文件系统的否则为无法访问部分出于安全考虑在企业环境中,和其他方案的主机。 云上的所有重点,似乎可能违反直觉的今天继续依靠 FTP。 但如果你想要直接控制您的文件的同时提供了方便地访问它们,FTP 可以仍然是要走的路。

添加在 Windows Phone 的套接字支持启用要传达着各种不同的服务,使用户感受到更多连接和可用比以往任何时候的设备。 但是,在 Windows Phone (以及 Windows 8),它可能由开发人员来提供他自己的一种服务的实现。 在 Windows Phone 中仍缺少的一个功能是对 FTP 支持 — — 有没有直接的 Api 可供利用 FTP 服务的 Windows 应用程序商店。 从企业角度来看,这使得员工要通过他们的手机访问文件相当困难。

这篇文章是关于为通过创建一个 FTP 库和一个简单的 FTP 客户端应用程序将在 Windows Phone 的设备上运行 Windows Phone 8 提供 FTP 支援。 FTP 是一种行之有效、 翔实协议 (请参阅 RFC 959 在 bit.ly/fB5ezA)。 RFC 959 描述规范,它的功能和结构和其命令与他们的反应。 我不会来描述每项功能和命令的 FTP,但我希望能提供一个头开始,可帮助您创建您自己的 FTP 实现。 请注意我要讨论的功能并不是微软实现可能不一定会批准。

FTP 渠道

FTP 包括两个电视频道、 命令通道和数据通道。 命令通道是时创建一个客户端连接到 FTP 服务器,并且它用来传输所有命令和响应对服务器和从 FTP 服务器。 命令通道保持打开状态,直到客户端断开连接、 连接空闲超时发生,或在 FTP 服务器或命令通道中发生的错误。

数据信道用于传输数据到和从 FTP 服务器。 这种渠道是临时性质 — — 通道数据传输完成后,已断开连接。 为每个数据传输建立一个新的数据通道。

FTP 连接可以是主动或被动。 在主动模式下,FTP 客户端文件传输将发送到发送的数据通道信息 (数据端口号)。 在被动模式下,客户端要求服务器在它的末端上创建的数据通道和提供的套接字地址和端口信息,以便客户端可以连接到该数据通道和开始文件传输操作。

当 FTP 客户端并不想要管理数据端口,或者在它的末端上的数据通道,被动模式十分有用。 在开发 Windows Phone 的 FTP 客户端时,我就会切换到被动模式。 就是在开始文件传输操作之前, 我会问 FTP 服务器以创建和打开一个数据通道和发送我的套接字的终结点信息以便在连接时,数据传输开始。 我将讨论如何在本文的后面使用被动模式。

开始使用

这篇文章不会涵盖所有在 RFC 959 中提到的命令。 我将改为重点要建立一个最小、 工作 FTP 客户端,包括那些参与连接所需的基本命令-断开连接的过程中,身份验证,浏览文件系统中,并上传和下载文件。

若要开始,我将创建一个 Visual Studio 解决方案,将包含两个项目、 一个 Windows Phone 类库项目的 FTP 库和一个 Windows Phone 应用程序项目,为用户体验和为使用 FTP 库包含的代码。

若要创建该解决方案,打开 Visual Studio、 创建一个 Windows Phone 类库项目并将其命名 WinPhoneFtp.FtpService。

现在添加一个 Windows Phone 应用程序项目,并将其命名 WinPhone­-Ftp.UserExperience。 这将 con­汀用于测试应用程序的用户界面。

WinPhoneFtp.FtpService 是将 WinPhoneFtp.UserExperience UX 的项目中引用要使用 FTP 服务,FTP 客户端库。 FTP 库使用基于事件的异步/等待模式。 每个操作将在后台任务异步调用并在完成后,将引发一个事件提供的用户界面中的通知。 FTP 客户端库包含各种异步方法为每个 FTP 操作 (支持命令) 和每个异步方法映射到异步方法完成时将引发的某些事件。 图 1 描述了异步方法和事件的映射。

图 1 异步方法和关联的事件

(带有参数) 的方法 相关的事件
System.String ip 地址 System.Windows.Threading.Dispatcher UIDispatcher) 的构造函数 没有关联的事件
ConnectAsync FtpConnected
DisconnectAsync FtpDisconnected

AuthenticateAsync

AuthenticateAsync System.String 密码 System.String 用户名)

成功-FtpAuthenticationSucceeded

失败-FtpAuthenticationFailed

GetPresentWorkingDirectoryAsync FtpDirectoryListed
ChangeWorkingDirectoryAsync

成功-FtpDirectoryChangedSucceeded

失败-FtpDirectoryChangedFailed

GetDirectoryListingAsync FtpDirectoryListed
UploadFileAsync (System.IO.Stream LocalFileStream,System.String RemoteFilename)

成功-FtpFileUploadSucceeded

失败-FtpFileUploadFailed

进展-FtpFileTransferProgressed

DownloadFileAsync (System.IO.Stream LocalFileStream,System.String RemoteFilename)

成功-FtpFileDownloadSucceeded

失败-FtpFileDownloadFailed

进展-FtpFileTransferProgressed

应用程序的用户界面包括两个文本框和两个按钮。 一个文本框和按钮将用于接受 FTP 服务器的 IP 地址和连接到它。 其他文本框和按钮将接受来自用户的 FTP 命令,并将它们发送到 FTP 服务器。

TCP 套接字包装

在开始之前在 FTP 客户端上,我创建了一个名为 TcpClientSocket 的 Windows.Networking.Sockets.StreamSocket 类的包装 (见 bit.ly/15fmqhK)。 此包装将提供基于不同的套接字操作某些事件通知。 我感兴趣的通知是 SocketConnected、 DataReceived、 ErrorOccured 和 SocketClosed。 当 StreamSocket 对象连接到远程终结点时,将引发 SocketConnected 事件。 同样,关于在套接字,数据接收数据­将引发收到的事件,以及包含作为事件参数的数据的缓冲区。 如果套接字操作期间发生错误,它是 ErrorOccured 事件,不会通知。 而且,最后,当套接字关闭 (远程或本地主机),SocketClosed 引发事件,与在它的事件参数关闭的原因。 我不会详细介绍如何实现套接字包装,但您可以下载本文的源代码并见其执行情况 (archive.msdn.microsoft.com/mag201309WPFTP)。

FTP 连接和断开连接

当 FTP 客户端首次连接到 FTP 服务器时,它建立了与服务器的命令和他们的反应共享命令通道。 通常情况下,FTP 使用的端口 21,但出于安全原因或其他因素,可能配置不同的端口号。 用户可以开始共享文件之前,他需要向服务器,通常使用的用户名和密码进行身份验证。 如果身份验证成功,服务器将响应 (在我的情况):220 微软 FTP 服务。

在连接到 FTP 服务器之前, 我将创建一个名为 FtpClient 基于我 FTP 客户端库类与前面所述的所有事件的事件处理程序对象:

FtpClient ftpClient = null;
async private void btnLogin_Tap(object sender,
  System.Windows.Input.GestureEventArgs e)
{
  ftpClient = new FtpClient(txtIp.Text, this.Dispatcher);
  // Add event-handlers to various events of ftpClient object
  await ftpClient.ConnectAsync();
}

一旦创建了该对象,并添加各种 FTP 客户端事件处理程序,我打电话给 FtpClient 对象的 ConnectAsync 方法。 这里是 ConnectAsync 的工作原理:

public async Task ConnectAsync()
{
  if (!IsConnected)
  {
    logger.AddLog("FTP Command Channel Initailized");
    await FtpCommandSocket.PrepareSocket();
  }
}

ConnectAsync 将连接到 FTP 服务器作为连接的套接字操作。 当 FTP 客户端成功连接到 FTP 服务器时,将引发 FtpConnected 事件,以通知它已连接到 FTP 服务器的客户端:

async void ftpClient_FtpConnected(object sender, EventArgs e)
{
  // Handle the FtpConnected event to show some prompt to
  // user or call AuthenticateAsync to authenticate user
  // automatically once connected
}

图 2 显示用户已成功连接到 FTP 服务器应用程序的外观。

Connecting and Authenticating to the FTP Server
图 2: 连接和对 FTP 服务器进行身份验证

请注意当您连接到其他 FTP 服务器,您看到的文本可能完全不同。

有三种方法可以从 FTP 服务器断开连接:

  • 用户发送 QUIT 命令到服务器,以故意关闭该连接。
  • 在指定的时间被闲置后, FTP 服务器关闭连接。
  • 套接字错误或一些内部错误可能发生在一个或两个端点上。

要故意断开连接通过退出命令,我发出 QUIT 命令的文本框中,并点击发送命令按钮的事件处理程序调用 DisconnectAsync 方法:

async private void btnFtp_Tap(object sender, 
    System.Windows.Input.GestureEventArgs e)
{
  ...
  if (txtCmd.Text.Equals("QUIT"))
  {
    logger.Logs.Clear();
    await ftpClient.DisconnectAsync();
    return;
  }
  ...
}

这是 DisconnectAsync 将命令发送到 FTP 服务器:

public async Task DisconnectAsync()
{
  ftpCommand = FtpCommand.Logout;
  await FtpCommandSocket.SendData("QUIT\r\n");
}

一旦关闭,该连接引发 FtpDisconnected 事件,以便客户端可以处理断开连接事件和优雅地释放的资源,所示,结果图 3

void ftpClient_FtpDisconnected(object sender, 
    FtpDisconnectedEventArgs e)
{
  // Handle FtpDisconnected event to show some prompt
  // or message to user or release
}

Disconnecting from the FTP Server
图 3 从 FTP 服务器断开连接

在任何提到的方案,从 FTP 服务器断开 FTP 客户端和任何正在进行的 FTP 文件传输操作也将会中止。

FTP 身份验证

在开始之前任何 FTP 文件操作,用户需要对 FTP 服务器进行身份验证。 有两种类型的用户:匿名和非匿名 (这些凭据)。 当任何人都可以访问 FTP 服务器时,用户进行身份验证的匿名。 对他们来说,则用户名为"匿名",密码可以是任何文本格式设置为电子邮件地址。 对于非匿名 FTP 用户必须提供有效的凭据。 在实施之前任何身份验证方案,您需要知道服务器上启用了哪种类型的身份验证。

通过两个 FTP 命令,用户和密码进行身份验证。 用户命令用于发送用户的身份,就是用户名。 PASS 命令用来提供的密码。 虽然它是很好的做法提供用户的电子邮件地址,将工作任何文本格式设置为电子邮件地址:

[FTP Client]: USER anonymous
[FTP Server:] 331 Anonymous access allowed, send identity (e-mail name) as password
[FTP Client]: PASS m@m.com
[FTP Server]: 230 User logged in

AuthenticateAsync 方法具有两个重载。 第一个重载将对具有默认凭据,一般用于匿名身份验证的用户进行身份验证。 第二个重载需要到 FTP 服务器的用户名和密码进行身份验证。

进行身份验证的用户将自动作为匿名用户,我打电话 AuthenticateAsync 收到 FtpConnected 事件:

async void ftpClient_FtpConnected(object sender, 
    EventArgs e)
{
  await (sender as FtpClient).AuthenticateAsync();
}

在内部,AuthenticateAsync 调用另一个重载使用默认凭据:

public async Task AuthenticateAsync()
{
  await AuthenticateAsync("anonymous", m@m.com);
}

AuthenticateAsync 过载问题到 FTP 服务器的用户名和用户命令收到从参数:

public async Task AuthenticateAsync(String Username, 
    String Password)
{
  ftpCommand = FtpCommand.Username;
  this.Username = Username;
  this.Password = Password;
  logger.AddLog(String.Format("FTPClient -> USER {0}\r\n", 
    Username));
  await FtpCommandSocket.SendData(String.Format("USER {0}\r\n", 
    Username));
}

当收到对用户命令的响应时,PASS 命令发行同时收到从参数的密码 (请参阅图 4)。

图 4 用户命令后发送与通行证,密码

async void FtpClientSocket_DataReceived(object sender, 
    DataReceivedEventArgs e)
{
  String Response = System.Text.Encoding.UTF8.GetString(
    e.GetData(), 0, e.GetData().Length);
  logger.AddLog(String.Format("FTPServer -> {0}", Response));
    switch (ftpPassiveOperation)
    {
      ...
      case FtpPassiveOperation.None:
        switch (ftpCommand)
        {
          case FtpCommand.Username:
          if (Response.StartsWith("501"))
          {
            IsBusy = false;
            RaiseFtpAuthenticationFailedEvent();
            break;
          }
          this.ftpCommand = FtpCommand.Password;
          logger.AddLog(String.Format(
            "FTPClient -> PASS {0}\r\n", this.Password));
          await FtpCommandSocket.SendData(
            String.Format("PASS {0}\r\n", this.Password));
          break;
          ...
        }
    }
    ...
  }

如果身份验证成功,引发 FtpAuthenticationSucceeded 事件 ; 如果不是,引发 FtpAuthenticationFailed 事件:

void ftpClient_FtpAuthenticationFailed(object sender, 
    EventArgs e)
{
  logger.AddLog("Authentication failed");
}
void ftpClient_FtpAuthenticationSucceeded(object sender, 
    EventArgs e)
{
  logger.AddLog("Authentication succeeded");
}

目录浏览

FTP 提供支持的目录浏览,但这取决于如何写入 FTP 客户端以显示目录信息。 如果 FTP 客户端 GUI,可能目录树的形式显示信息。 相比之下,控制台客户端,可能只是显示在屏幕上列出的目录。

因为没有任何机制提供了让你知道你在哪个目录,FTP 提供的命令,以显示当前的工作目录 (当前目录)。 通过命令通道向 FTP 服务器发送 PWD 命令,使您的整个路径,从根到当前目录。 当用户通过验证后时,他土地的根目录或为他配置 FTP 服务器配置中的目录中。

您可以通过调用 GetPresentWorkingDirectoryAsync 发出 PWD 命令。 若要获取当前目录,我发出 PWD 命令文本框中,并在发送命令按钮的点击事件中调用 GetPresentWorkingDirectoryAsync:

async private void btnFtp_Tap(object sender,
  System.Windows.Input.GestureEventArgs e)
{
  ...
  if (txtCmd.Text.Equals("PWD"))
  {
    logger.Logs.Clear();
    await ftpClient.GetPresentWorkingDirectoryAsync();
    return;
  }
  ...
}

在内部,GetPresentWorkingDirectoryAsync PWD 在将发送到 FTP 服务器套接字:

public async Task GetPresentWorkingDirectoryAsync()
{
  if (!IsBusy)
  {
    ftpCommand = FtpCommand.PresentWorkingDirectory;
    logger.AddLog("FTPClient -> PWD\r\n");
    await FtpCommandSocket.SendData("PWD\r\n");
  }
}

当此过程是成功的时 FTP 服务器发送的响应的当前工作目录的路径,通知 FTP 客户端和引发 FtpPresentWorkingDirectoryReceived 事件。 使用事件参数,客户端可以获取的当前工作目录的路径信息 (如中所示图 5):

void ftpClient_FtpPresentWorkingDirectoryReceived(object sender,
  FtpPresentWorkingDirectoryEventArgs e)
{
  // Handle PresentWorkingDirectoryReceived event to show some
  // prompt or message to user
}

Getting the Present Working Directory from the FTP Server
图 5 获取当前工作目录从 FTP 服务器

要更改为其他一些目录,FTP 客户端可以使用更改工作目录 (CWD) 命令。 注意 CWD 仅适用要更改到当前的工作目录中的子目录或其父目录。 在用户的根目录中时,他不能向后导航和 CWD 将引发错误的响应。

若要更改工作目录,我发出 CWD 命令在命令文本框中,并在发送命令按钮的点击事件中我调用 ChangeWorkingDirectoryAsync 方法:

async private void btnFtp_Tap(object sender,
  System.Windows.Input.GestureEventArgs e)
{
  ...
  if (txtCmd.Text.StartsWith("CWD"))
  {
    logger.Logs.Clear();
    await ftpClient.ChangeWorkingDirectoryAsync(
      txtCmd.Text.Split(new char[] { ' ' },
      StringSplitOptions.RemoveEmptyEntries)[1]);
    return;
  }
  ...
}

在内部,ChangeWorkingDirectoryAsync 问题 CWD 命令到 FTP 服务器:

public async Task ChangeWorkingDirectoryAsync(String RemoteDirectory)
{
  if (!IsBusy)
  {
    this.RemoteDirectory = RemoteDirectory;
    ftpCommand = FtpCommand.ChangeWorkingDirectory;
    logger.AddLog(String.Format("FTPClient -> CWD {0}\r\n", 
        RemoteDirectory));
    await FtpCommandSocket.SendData(String.Format("CWD {0}\r\n", 
        RemoteDirectory));
  }
}

如果用户想要向后导航,他可以发送双点"......"作为此过程的参数。 当更改是成功、 通知客户端和引发 FtpDirectoryChangedSucceeded 事件时,其中可以看到在图 6。 如果 CWD 失败,并且在响应中发送一个错误,这种失败的通知客户端并引发 FtpDirectoryChangedFailed 事件:

void ftpClient_FtpDirectoryChangedSucceded(object sender,
  FtpDirectoryChangedEventArgs e)
{
  // Handle DirectoryChangedSucceeded event to show
  // some prompt or message to user
}
void ftpClient_FtpDirectoryChangedFailed(object sender,
  FtpDirectoryChangedEventArgs e)
{
  // Handle DirectoryChangedFailed event to show
  // some prompt or message to user
}

Changing Working Directory on the FTP Server
图 6 更改工作目录的 FTP Server 上

添加支持被动命令

现在它是时间为被动的命令,提供支持,因为我想要管理所有数据连接以传输数据到服务器和从服务器的 FTP 服务器。 在被动模式下,需要传输的数据的任何命令必须使用由 FTP 服务器启动一个数据连接。 FTP 服务器将创建一个数据连接,并发送该数据连接的套接字信息,以便客户端可以连接到它并执行一些数据传输操作。

被动模式是临时性质的。 将命令发送到 FTP 服务器将发送或接收数据的响应之前,客户端必须告诉服务器进入被动模式 — — 和它必须这样做对于每个发送的命令。 总括来说,为每个数据传输,两个命令都始终会激发 — — 被动,然后命令本身。

FTP 服务器是告诉准备通过 PASV 命令的被动模式。 在响应,它在编码格式发送数据连接的 IP 地址和端口号。 响应进行解码和客户端然后准备与使用的解码后的 IP 地址和端口号的 FTP 服务器的数据连接。 数据操作一旦完成,此数据连接关闭并溶解,使它不能再用。 每次 PASV 命令发送到 FTP 服务器时,将发生这种情况。

解码 PASV 命令响应响应 PASV 命令看起来像这样:

227 Entering Passive Mode (192,168,33,238,255,167)

响应了它所包含的数据通道信息。 基于这种反应,已制定的套接字地址 — — IP 地址和数据端口 FTP 服务器使用的数据传输操作。 这里是的步骤来计算的 IP 地址和端口,如中所示图 7

  • 第一次的四个整数组形成的 IPV4 地址,FTP 服务器 ; 就是 192.168.33.238。
  • 其余的整数组成的数据端口。 你左 shift 的第五个整数与 8 组,然后执行与第六组整数位运算符或运算。 最终值为您提供了端口号可的数据通道。

Passive Command Request and Reply
图 7 被动命令请求和答复

图 8 显示的代码,将分析该响应并提取的数据通道终结点的 IP 地址和端口号。 PrepareDataChannelAsync 方法接收的原始 FTP PASV 命令的响应。 有时,响应字符串可更改 FTP 服务器要包括一些其他的参数,但实质上的响应发送的只是 IP 地址和端口号。

图 8 解析的数据通道终结点

private async Task PrepareDataChannelAsync(String ChannelInfo)
{
  ChannelInfo = ChannelInfo.Remove(0, "227 Entering Passive Mode".Length);
  // Configure the IP Address
  String[] Splits = ChannelInfo.Substring(ChannelInfo.IndexOf("(") + 1,
    ChannelInfo.Length - ChannelInfo.IndexOf("(") - 5).Split(
      new char[] { ',', ' ', },
  StringSplitOptions.RemoveEmptyEntries);
  String Ipaddr = String.Join(".", Splits, 0, 4);
  // Calculate the Data Port
  Int32 port = Convert.ToInt32(Splits[4]);
  port = ((port << 8) | Convert.ToInt32(Splits[5]));
  logger.AddLog(String.Format(
    "FTP Data Channel IPAddress: {0}, Port: {1}", Ipaddr, port));
  // Create data channel here with extracted IP Address and Port number
  logger.AddLog("FTP Data Channel connected");
}

更多的命令:列表、 类型、 STOR RETR

列出目录内容要列出当前工作目录的内容,我列表命令通过命令通道发送到 FTP 服务器。 但是,FTP 服务器将发送其对此命令的响应数据信道,因此我必须首先发送 PASV 命令以创建列表命令的响应将发送的数据连接。 目录列表格式将基于在其安装 FTP 服务器的操作系统。 即,如果 FTP 服务器操作系统是 Windows,目录列表将会收到在 Windows 目录列表格式中,并且如果基于 Unix 的操作系统,将收到在 Unix 格式的目录列表。

要列出当前工作目录的目录的内容,我发出列表命令在命令文本框中,在发送命令按钮的点击事件中,我调用 GetDirectoryListingAsync 方法:

async private void btnFtp_Tap(object sender,
  System.Windows.Input.GestureEventArgs e)
{
  ...
  if (txtCmd.Text.Equals("LIST"))
  {
    logger.Logs.Clear();
    await ftpClient.GetDirectoryListingAsync();
    return;
  }
  ...
}

在内部,GetDirectoryListingAsync PASV 命令在将发送到 FTP 服务器套接字:

public async Task GetDirectoryListingAsync()
{
  if (!IsBusy)
  {
    fileListingData = null;
    IsBusy = true;
    ftpCommand = FtpCommand.Passive;
    logger.AddLog("FTPClient -> PASV\r\n");
    await FtpCommandSocket.SendData("PASV\r\n");
  }
}

一旦终结点信息收到 PASV 命令,创建数据连接时,向 FTP 服务器上,然后发出列表命令,如中所示图 9

图 9 处理套接字的 DataReceived Event 中的列表命令

async void FtpClientSocket_DataReceived(object sender, D
    ataReceivedEventArgs e)
{
  String Response = System.Text.Encoding.UTF8.GetString(
    e.GetData(), 0, e.GetData().Length);
  ...
  IsBusy = true;
  DataReader dataReader = new DataReader(
    FtpDataChannel.InputStream);
  dataReader.InputStreamOptions = InputStreamOptions.Partial;
  fileListingData = new List<byte>();
  while (!(await dataReader.LoadAsync(1024)).Equals(0))
  {
    fileListingData.AddRange(dataReader.DetachBuffer().ToArray());
  }
  dataReader.Dispose();
  dataReader = null;
  FtpDataChannel.Dispose();
  FtpDataChannel = null;
  String listingData = System.Text.Encoding.UTF8.GetString(
    fileListingData.ToArray(), 0, fileListingData.ToArray().Length);
  String[] listings = listingData.Split(
    new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
  List<String> Filenames = new List<String>();
  List<String> Directories = new List<String>();
  foreach (String listing in listings)
  {
    if (listing.StartsWith("drwx") || listing.Contains("<DIR>"))
    {
      Directories.Add(listing.Split(new char[] { ' ' }).Last());
    }
    else
    {
      Filenames.Add(listing.Split(new char[] { ' ' }).Last());
    }
  }
  RaiseFtpDirectoryListedEvent(Directories.ToArray(),
    Filenames.ToArray());
  ...
  fileListingData = new List<byte>();
  ftpPassiveOperation = FtpPassiveOperation.ListDirectory;
  logger.AddLog("FTPClient -> LIST\r\n");
  await FtpCommandSocket.SendData("LIST\r\n");
  ...
}

当在数据通道收到目录列表时,FtpDirectoryListed 事件引发其事件参数中文件和目录的列表 (图 10 显示发送 PASV 和列表命令,和图 11 显示目录列表的输出):

void ftpClient_FtpDirectoryListed(object sender, 
    FtpDirectoryListedEventArgs e)
{
  foreach (String filename in e.GetFilenames())
  {
    // Handle the name of filenames in current working directory
  }
  foreach (String directory in e.GetDirectories())
  {
    // Handle the name of directories in current working directory
  }
}

Sending LIST Command to the FTP Server
图 10 向 FTP 服务器发送列表命令

Displaying FileSystem Objects in Response to LIST Command
图 11 在列表命令响应中显示文件系统对象

描述数据格式类型命令用来描述在其中接收或发送该文件的数据的数据格式。 它不需要 PASV 模式。 我使用的"我"与类型命令,表示图像类型数据传输格式。 存储或检索的文件通过数据连接时,通常使用类型。 这会告诉 FTP 服务器传输会发生以二进制模式而不是文本或一些数据结构模式。 可以使用 TYPE 命令与其他模式,请参阅 FTP 的 RC 959。

存储在 FTP Server 上的文件要到 FTP 服务器存储文件 (位于设备上) 的内容,我发送 STOR 命令文件 (和扩展名) 的名称 FTP 服务器将使用它来创建和保存该文件。 文件内容的传输将执行在数据连接上如此,在发送 STOR 之前,我就会查询使用 PASV 命令的 FTP 服务器的终结点详细信息。 一旦收到该终结点时,我会派 STOR,,当我收到时对它的回应,我会送该文件的内容以二进制形式到 FTP 服务器的数据连接。

如中所示,可以通过调用 UploadFileAsync 方法,发送 STOR 命令图 12。 此方法采用两个参数 — — 流对象的本地文件和一个字符串作为 FTP 服务器上的文件的名称。

图 12 UploadFileAsync 方法

async private void btnFtp_Tap(object sender, 
    System.Windows.Input.GestureEventArgs e)
{
  ...
  if (txtCmd.Text.StartsWith("STOR"))
  {
    logger.Logs.Clear();
    String Filename = txtCmd.Text.Split(new char[] { ' ', '/' },      
      StringSplitOptions.RemoveEmptyEntries).Last();
    StorageFile file =
      await Windows.Storage.ApplicationData.Current.LocalFolder.GetFileAsync(
      txtCmd.Text.Split(new char[] { ' ' },
      StringSplitOptions.RemoveEmptyEntries)[1]);
    await ftpClient.UploadFileAsync(await file.OpenStreamForReadAsync(),
      "video2.mp4");
    return;
  }
  ...
}

在内部,UploadFileAsync 方法发出 PASV 命令到 FTP 服务器,以提取数据通道终结点信息:

public async Task UploadFileAsync(
  System.IO.Stream LocalFileStream, String RemoteFilename)
{
  if (!IsBusy)
  {
    ftpFileInfo = null;
    IsBusy = true;
    ftpFileInfo = new FtpFileOperationInfo(LocalFileStream, 
        RemoteFilename, true);
    ftpCommand = FtpCommand.Type;
    logger.AddLog("FTPClient -> TYPE I\r\n");
    await FtpCommandSocket.SendData("TYPE I\r\n");
  }
}

如中所示图 13,在 DataReceived 事件中的 PASV 命令的响应,在 STOR 命令后,才发出的数据通道是创建并打开 ; 数据传输然后启动从客户端到服务器。

图 13 加工中的套接字 DataReceived 事件的 STOR 命令

async void FtpClientSocket_DataReceived(
  object sender, DataReceivedEventArgs e)
{
  String Response = System.Text.Encoding.UTF8.GetString(
    e.GetData(), 0, e.GetData().Length);
  ...
IsBusy = true;
  DataWriter dataWriter = new DataWriter(
    FtpDataChannel.OutputStream);
  byte[] data = new byte[32768];
  while (!(await ftpFileInfo.LocalFileStream.ReadAsync(
    data, 0, data.Length)).Equals(0))
  {
    dataWriter.WriteBytes(data);
    await dataWriter.StoreAsync();
    RaiseFtpFileTransferProgressedEvent(
      Convert.ToUInt32(data.Length), true);
  }
  await dataWriter.FlushAsync();
  dataWriter.Dispose();
  dataWriter = null;
  FtpDataChannel.Dispose();
  FtpDataChannel = null;
  ...
  await PrepareDataChannelAsync(Response);
  ftpPassiveOperation = FtpPassiveOperation.FileUpload;
  logger.AddLog(String.Format("FTPClient -> STOR {0}\r\n",
    ftpFileInfo.RemoteFile));
  await FtpCommandSocket.SendData(String.Format("STOR {0}\r\n",
    ftpFileInfo.RemoteFile));
  ...
}

虽然上传文件,通过 FtpFileTransferProgressed 上传进度通知客户端:

 

void ftpClient_FtpFileTransferProgressed(
  object sender, FtpFileTransferProgressedEventArgs e)
{
  // Update the UI with some progressive information
  // or use this to update progress bar
}

如果在文件上传操作成功,完成 FtpFile­UploadSucceeded 事件引发。 如果不是,与它的参数中的故障原因引发 FtpFileUploadFailed 事件:

void ftpClient_FtpFileUploadSucceeded(
  object sender, FtpFileTransferEventArgs e)
{
  // Handle UploadSucceeded Event to show some
  // prompt or message to user
}
void ftpClient_FtpFileUploadFailed (
  object sender, FtpFileTransferEventArgs e)
{
  // Handle UploadFailed Event to show some
  // prompt or message to user
}

图 14图 15 显示将文件上载到 FTP 服务器的过程。

Uploading a File to the FTP Server
图 14 将文件上载到 FTP 服务器

Successful File Upload
图 15 成功文件上传

从 FTP 服务器检索文件要从 FTP 服务器检索文件的内容,我 RETR 命令以及该文件,并且其扩展名的名称向 FTP 服务器发送 (假定该文件位于当前目录中的服务器上)。 文件内容的传输将使用数据连接,所以,在发送 RETR 之前,我就会查询使用 PASV 命令的 FTP 服务器的终结点详细信息。 一旦收到该终结点,则我会派 RETR 命令,并在收到后对它的回应,我就会检索该文件的内容以二进制形式从 FTP 服务器通过数据连接。

如中所示图 16,可以通过调用 DownloadFileAsync 方法发送 RETR 命令。 此方法采用两个参数 — — 流对象的本地文件的保存的内容和 FTP 服务器上的文件的名称作为一个字符串。

图 16 DownloadFileAsync 方法

async private void btnFtp_Tap(
  object sender, System.Windows.Input.GestureEventArgs e)
{
  ...
  if (txtCmd.Text.StartsWith("RETR"))
  {
    logger.Logs.Clear();
    String Filename = txtCmd.Text.Split(new char[] { ' ', '/' },
      StringSplitOptions.RemoveEmptyEntries).Last();
    StorageFile file =
      await Windows.Storage.ApplicationData.Current.LocalFolder.
        CreateFileAsync(
      txtCmd.Text.Split(new char[] { ' ' }, StringSplitOptions.
        RemoveEmptyEntries)[1],
      CreationCollisionOption.ReplaceExisting);
    await ftpClient.DownloadFileAsync(
      await file.OpenStreamForWriteAsync(), Filename);
    return;
  }
  ...
}

在内部,DownloadFileAsync 方法发出 PASV 命令到 FTP 服务器,以提取数据通道的终结点信息:

public async Task DownloadFileAsync(
  System.IO.Stream LocalFileStream, String RemoteFilename)
{
  if (!IsBusy)
  {
    ftpFileInfo = null;
    IsBusy = true;
    ftpFileInfo = new FtpFileOperationInfo(
      LocalFileStream, RemoteFilename, false);
    ftpCommand = FtpCommand.Type;
    logger.AddLog("FTPClient -> TYPE I\r\n");
    await FtpCommandSocket.SendData("TYPE I\r\n");
  }
}

如中所示图 17,在里面的 DataReceived 事件的 PASV 命令的响应,RETR 命令后,才发出的数据通道是创建并打开 ; 然后数据传输开始,从服务器到客户端上运行。

图 17 处理套接字的 DataReceived Event 中的 RETR 命令

async void FtpClientSocket_DataReceived(
  object sender, DataReceivedEventArgs e)
{
  String Response = System.Text.Encoding.UTF8.GetString(
    e.GetData(), 0, e.GetData().Length);
  ...
  IsBusy = true;
  DataReader dataReader = new DataReader(FtpDataChannel.InputStream);
  dataReader.InputStreamOptions = InputStreamOptions.Partial;
  while (!(await dataReader.LoadAsync(32768)).Equals(0))
  {
    IBuffer databuffer = dataReader.DetachBuffer();
    RaiseFtpFileTransferProgressedEvent(databuffer.Length, false);
    await ftpFileInfo.LocalFileStream.WriteAsync(
      databuffer.ToArray(), 0, Convert.ToInt32  (databuffer.Length));
  }
  await ftpFileInfo.LocalFileStream.FlushAsync();
  dataReader.Dispose();
  dataReader = null;
  FtpDataChannel.Dispose();
  FtpDataChannel = null;
  ...
  await PrepareDataChannelAsync(Response);
  ftpPassiveOperation = FtpPassiveOperation.FileDownload;
  logger.AddLog(String.Format("FTPClient -> RETR {0}\r\n",
    ftpFileInfo.RemoteFile));
  await FtpCommandSocket.SendData(String.Format("RETR {0}\r\n",
    ftpFileInfo.RemoteFile));
  ...
  }
}

下载该文件时,客户端通过 FtpFileTransferProgressed 下载进度的通知:

void ftpClient_FtpFileTransferProgressed(object sender,
  FtpFileTransferProgressedEventArgs e)
{
  // Update the UI with some progressive information or use
  // this to update progress bar
}

如果文件下载操作成功完成,引发 FtpFileDownloadSucceeded 事件。 如果不是,与它的参数中的故障原因引发 FtpFileDownloadFailed 事件:

void ftpClient_FtpFileDownloadSucceeded(object sender,
  FtpFileTransferFailedEventArgs e)
{
  // Handle DownloadSucceeded event to show some prompt
  // or message to user
}
void ftpClient_FtpFileDownloadFailed(object sender,
  FtpFileTransferFailedEventArgs e)
{
  // Handle UploadFailed Event to show some prompt
  // or message to user
}

图 18图 19 显示从 FTP 服务器下载一个文件的过程。

Downloading a File from the FTP Server
图 18 从 FTP 服务器下载文件

Successful File Download
图 19 成功文件下载

总结

请注意您可以提供一个 URI 协会执行 FTP,应用程序,这样,其他应用程序也可以从 FTP 服务器访问的 FTP 服务和请求数据。 你就会了解更多有关这在 Windows Phone 开发中心页面上,"自动启动程序的 Windows Phone 8、 使用文件和 URI 关联"在 bit.ly/XeAaZ8,并在示例代码 bit.ly/15x4O0y

我已经为我的 FTP 库编写的代码和客户端应用程序的 Windows Phone 完全支持在 Windows 上 8.x 版,还没有作为任何与 Windows 不兼容的 API 8.x 版。 你可以为 Windows 要么重新编译代码 8.x 版,或把它放在可以针对这两种平台便携类图书馆 (PCL)。

Uday Gupta 是高级工程师-交响乐 Teleca 公司的产品开发 (印度)私人有限公司。 他在很多.NET 技术,特别是在 Windows 的演示文稿基础 (WPF)、 Silverlight Windows 8、 Windows Phone 有经验。 他的大部分时间都花在编码、 游戏、 学习新事物和帮助他人。

衷心感谢以下技术专家对本文的审阅:托尼冠军 (冠军 DS) 和威格利安迪 (Microsoft)
托尼冠军是主席的冠军 DS,是微软最有价值球员,并在作者,扬声器、 博主,以及社区活动。 他坚持在博客 tonychampion.net ,可以通过电子邮件在到达 tony@tonychampion.net

安迪威格利是为微软英国工作技术福音传教士。 安迪是知名的最受欢迎的 Windows Phone JumpStart 录影带,可在 channel9.msdn.com 和在各次主要会议等科技教育署定期发表演讲。