Este artigo foi traduzido por máquina.

Windows Phone

Adicionando suporte a FTP ao Windows Phone 8

Uday Gupta

Baixar o código de exemplo

FTP é um dos protocolos mais utilizados para compartilhamento de arquivos. É usado para arquivos de clientes, para distribuir arquivos de instalação para os consumidores, para fornecer acesso a partes inacessíveis do sistema de arquivos por motivos de segurança em ambientes corporativos e para uma série de outros cenários. Com toda a ênfase da nuvem, pode parecer contra-intuitivo hoje continuar a contar com FTP. Mas se você quer ter o controle direto de seus arquivos, proporcionando fácil acesso a eles, FTP ainda pode ser o caminho a percorrer.

A adição de suporte de soquete no Windows Phone habilitado os dispositivos se comuniquem com uma infinidade de diferentes serviços, tornando os usuários sentem mais conectado e disponível do que nunca. No entanto, no Windows Phone (bem como Windows 8), pode ser até o desenvolvedor para fornecer sua própria implementação de um serviço. Uma característica que ainda está falta no Windows Phone é suporte para FTP — há há direto APIs disponíveis para alavancar serviços de FTP em uma app Store do Windows. Do ponto de vista empresarial, isso torna bastante difícil para os funcionários acessar arquivos através de seus telefones.

Este artigo é sobre o fornecimento de suporte para FTP sobre Windows Phone 8 criando uma biblioteca FTP e um aplicativo de cliente FTP simples que será executado em um dispositivo Windows Phone. FTP é um protocolo bem estabelecido, bem documentado (consulte a RFC 959 no bit.ly/fB5ezA). RFC 959 descreve a especificação, sua funcionalidade e arquitetura e seus comandos com suas respostas. Não vou descrever cada recurso e comando de FTP, mas espero que para fornecer uma vantagem para ajudá-lo a criar sua própria implementação de FTP. Observe que o recurso que eu vou falar não é uma implementação da Microsoft e pode não necessariamente ser aprovado.

Canais de FTP

FTP é composto por dois canais, um canal de comando e um canal de dados. O canal de comando é criado quando um cliente se conecta a um servidor FTP, e é usado para transmitir todos os comandos e respostas para e do servidor de FTP. O canal de comando permanece aberto até que o cliente se desconecta, realiza-se um tempo limite de conexão-ocioso ou ocorre um erro no canal de comando ou servidor de FTP.

O canal de dados é usado para transmitir dados de e para o servidor FTP. Este canal é de natureza temporário — uma vez concluída a transferência de dados, o canal está desligado. Para cada transmissão de dados, um novo canal de dados é estabelecido.

Uma conexão FTP pode ser ativo ou passivo. No modo ativo, o cliente FTP envia as informações de canal de dados (o número da porta de dados) para que a transferência do arquivo será enviada. No modo passivo, o cliente solicita o servidor para criar um canal de dados em sua extremidade e fornecer as informações de endereço e porta de soquete para que o cliente pode se conectar a esse canal de dados e iniciar a operação de transferência de arquivos.

Modo passivo é útil quando o cliente FTP não quer gerenciar dados portas ou canais de dados em sua extremidade. Ao desenvolver o cliente FTP para Windows Phone, eu vou mudar para o modo passivo. Ou seja, antes de iniciar uma operação de transferência de arquivos, vou pedir o servidor FTP para criar e abrir um canal de dados e me enviar suas informações de ponto de extremidade de soquete para que quando eu conectar, começa a transferência de dados. Falarei sobre como usar modo passivo posteriormente neste artigo.

Guia de Introdução

Este artigo não cobre todos os comandos mencionados no RFC 959. Eu vou focar em vez dos comandos básicos que são necessários para construir um cliente FTP mínimo, trabalhando, incluindo aqueles envolvidos na conectar-desconectar o procedimento, autenticação, consultando o sistema de arquivos e upload e download de arquivos.

Para começar, vou criar uma solução de Visual Studio que conterá dois projetos, um projeto de biblioteca de classe do Windows Phone para a biblioteca FTP e um projeto Windows Phone App para conter o código para o UX e usando a biblioteca FTP.

Para criar a solução, abra o Visual Studio, crie um projeto de biblioteca de classe do Windows Phone e denomine WinPhoneFtp.FtpService.

Agora adicione um projeto Windows Phone App e denomine WinPhone­-Ftp.UserExperience. Isso será con­tain o interface do usuário para o aplicativo de teste.

WinPhoneFtp.FtpService é a biblioteca de cliente FTP que será referenciada no projeto WinPhoneFtp.UserExperience UX para utilizar serviços de FTP. A biblioteca FTP usa um padrão baseado em evento, async/aguardar. Cada operação será chamada de forma assíncrona em uma tarefa em segundo plano e, após a conclusão, será gerado um evento para fornecer uma notificação na interface do usuário. A biblioteca de cliente FTP contém vários métodos assíncronos para cada operação FTP (os comandos suportados) e cada método assíncrono é mapeado para um determinado evento que será gerado quando o método assíncrono for concluído. Figura 1 retrata o mapeamento dos métodos assíncronos e eventos.

Figura 1 assíncrona métodos e eventos associados

Método (com parâmetros) Eventos associados
Construtor (System. String IPAddress, System.Windows.Threading.Dispatcher UIDispatcher) Nenhum evento associado
ConnectAsync FtpConnected
DisconnectAsync FtpDisconnected

AuthenticateAsync

AuthenticateAsync (Username System. String, System. String senha)

Sucesso - FtpAuthenticationSucceeded

Falha - FtpAuthenticationFailed

GetPresentWorkingDirectoryAsync FtpDirectoryListed
ChangeWorkingDirectoryAsync

Sucesso - FtpDirectoryChangedSucceeded

Falha - FtpDirectoryChangedFailed

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

Sucesso - FtpFileUploadSucceeded

Falha - FtpFileUploadFailed

Progresso - FtpFileTransferProgressed

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

Sucesso - FtpFileDownloadSucceeded

Falha - FtpFileDownloadFailed

Progresso - FtpFileTransferProgressed

Interface do usuário do aplicativo é composto por duas caixas de texto e dois botões. Um textbox e button serão usados para aceitar o endereço IP do servidor FTP e conectar a ele. O outro textbox e button vão aceitar comandos FTP de um usuário e enviá-los para o servidor FTP.

O Wrapper de soquete TCP

Antes de iniciar o cliente FTP, eu criei um invólucro chamado TcpClientSocket para a classe de Windows.Networking.Sockets.StreamSocket (ver bit.ly/15fmqhK). Esse wrapper irá fornecer determinadas notificações de eventos com base em várias operações de soquete. As notificações que estou interessado em são SocketConnected, DataReceived, tiaguinhofogo e SocketClosed. Quando o objeto StreamSocket conecta-se ao ponto de extremidade remoto, o SocketConnected evento será gerado. Da mesma forma, ao receber dados sobre o soquete, os dados­recebido evento será gerado, juntamente com o buffer que contém dados como argumentos de evento. Se ocorrer um erro durante a operação de soquete, que é o evento de tiaguinhofogo que faz a notificação. E, finalmente, quando o soquete é fechado (por qualquer um host local ou remoto), o SocketClosed evento é gerado, com o motivo para o fechamento em seu argumento de evento. Não vou entrar em detalhes sobre como o wrapper de soquete é implementado, mas você pode baixar o código-fonte para este artigo e ver a sua implementação (archive.msdn.microsoft.com/mag201309WPFTP).

FTP conectar e desconectar

Quando o cliente FTP primeiro se conecta ao servidor FTP, estabelece um canal de comando com o servidor sobre quais comandos e suas respostas são compartilhadas. Normalmente, o FTP usa a porta 21, mas por razões de segurança ou outros fatores, um número de porta diferente pode ser configurado. Antes que um usuário pode iniciar o compartilhamento de arquivos, ele precisa autenticar para o servidor, normalmente com um nome de usuário e senha. Se a autenticação for bem-sucedida, o servidor responderá (no meu caso): 220 Microsoft FTP Service.

Antes de conectar ao servidor FTP, vou criar um objeto chamado FtpClient baseado na minha classe de biblioteca de cliente FTP, juntamente com manipuladores de eventos para todos os eventos descritos anteriormente:

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();
}

Uma vez que o objeto é criado, e vários manipuladores de eventos de cliente FTP são adicionados, vou chamar o método ConnectAsync do objeto FtpClient. Aqui está como funciona o ConnectAsync:

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

ConnectAsync irá se conectar ao servidor FTP como uma operação de soquete-connect. Quando o cliente FTP se conecta com êxito para o servidor FTP, o FtpConnected evento é gerado para notificar o cliente que está conectado ao servidor de 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
}

Figura 2 mostra como o app vai ficar quando um usuário conectou com êxito para o servidor FTP.

Connecting and Authenticating to the FTP Server
Figura 2 conexão e autenticação para o servidor de FTP

Observe que quando você se conectar a outros servidores FTP, o texto que você vê pode ser completamente diferente.

Existem três maneiras para tornar-se desconectado de um servidor FTP:

  • O usuário envia um comando QUIT para o servidor para deliberadamente fechar a conexão.
  • Depois de estar ocioso por um tempo especificado, o servidor FTP fecha a conexão.
  • Um erro de soquete ou algum erro interno pode ocorrer em uma ou ambas as extremidades.

Para desconectar-se deliberadamente através do comando QUIT, emito QUIT na caixa de texto do comando e manipulador de eventos do botão de comando enviar torneira chama o método 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;
  }
  ...
}

Isto é como o DisconnectAsync envia o comando para o servidor FTP:

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

Uma vez que a conexão é fechada, o FtpDisconnected evento é gerado, para que o cliente pode manipular o evento de desconexão e graciosamente liberar os recursos, com o resultado mostrado no Figura 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
Figura 3 Desconectando o servidor de FTP

Em qualquer dos cenários mencionados, o cliente FTP é desconectado do servidor FTP e quaisquer operações em curso de transferência de arquivos FTP também vão abortar.

Autenticação de FTP

Antes de iniciar qualquer operação de arquivo FTP, o usuário precisa autenticar para o servidor FTP. Existem dois tipos de usuários: anônimo e não anônimo (aqueles com credenciais). Quando o servidor FTP pode ser acessado por qualquer pessoa, os usuários são autenticados como anônimo. Para eles, o nome de usuário é "anônimo" e a senha pode ser qualquer texto, formatado como um endereço de email. Para não-anônimo FTP, os usuários precisam fornecer credenciais válidas. Antes de implementar qualquer esquema de autenticação, você precisa saber qual o tipo de autenticação está habilitado no servidor.

A autenticação ocorre através de dois comandos FTP, usuário e PASS. O comando USER é usado para enviar a identidade do usuário, ou seja, o nome de usuário. O comando PASS é usado para fornecer a senha. Embora seja recomendável para fornecer o endereço de email do usuário, qualquer texto formatado como um endereço de e-mail irá funcionar:

[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

O método AuthenticateAsync tem duas sobrecargas. A primeira sobrecarga autenticará o usuário com credenciais padrão, que geralmente são utilizados para autenticação anônima. A segunda sobrecarga leva um nome de usuário e senha para autenticação no servidor de FTP.

Para autenticar o usuário automaticamente como usuário anônimo, eu chamo AuthenticateAsync quando o evento FtpConnected é recebido:

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

Internamente, AuthenticateAsync chama a sobrecarga com credenciais padrão:

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

Os problemas de sobrecarga AuthenticateAsync o comando de usuário para o servidor FTP junto com o nome de usuário recebido do parâmetro:

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));
}

Quando a resposta ao comando do usuário é recebida, o comando PASS é emitido junto com a senha que recebeu do parâmetro (ver Figura 4).

Figura 4-enviar a senha com PASS, após o comando de usuário

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;
          ...
}
    }
    ...
}

Se a autenticação for bem-sucedida, o evento FtpAuthenticationSucceeded é disparado; Se não, o FtpAuthenticationFailed evento é gerado:

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

Pesquisa no diretório

FTP oferece suporte para pesquisa no diretório, mas isso depende de como o cliente FTP é gravado para exibir informações de diretório. Se o cliente FTP é uma interface gráfica, a informação mostrada como uma árvore de diretório. Um cliente de console, em contraste, só pode exibir o diretório Listagem na tela.

Porque não há nenhum mecanismo fornecido para que você saiba qual diretório você está em, FTP fornece um comando para mostrar o diretório de trabalho atual (o diretório atual). Enviar o comando PWD para o servidor FTP através do canal de comando dá-lhe o caminho inteiro, da raiz para o diretório atual. Quando um usuário é autenticado, ele pousa no diretório raiz ou o diretório configurado para ele na configuração do servidor FTP.

Você pode emitir o comando PWD, chamando GetPresentWorkingDirectoryAsync. Para obter o diretório atual, emitir PWD na caixa de texto comando e chamar GetPresentWorkingDirectoryAsync no evento de toque do botão de comando enviar:

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

Internamente, GetPresentWorkingDirectoryAsync envia PWD para o servidor FTP Socket:

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

Quando este procedimento for bem-sucedido, o servidor FTP envia a resposta com o caminho do diretório de trabalho atual, o cliente FTP é notificado e o FtpPresentWorkingDirectoryReceived evento é gerado. Usando os argumentos de evento, o cliente pode obter as informações sobre o caminho do diretório de trabalho atual (como mostrado em Figura 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
Figura 5, recebendo o presente trabalho diretório do servidor FTP

Para mudar para algum outro diretório, o cliente FTP pode usar o comando do diretório de trabalho mudar (CWD). Observe que o CWD só funciona para alterar para um subdiretório no diretório de trabalho atual ou para o seu diretório pai. Quando o usuário estiver no diretório raiz, ele não pode navegar para trás e CWD lançará um erro na resposta.

Para alterar o diretório de trabalho, eu emitir o comando CWD na caixa de texto comando e, em caso de toque do botão de comando enviar, chamo o método de 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;
  }
  ...
}

Internamente, o ChangeWorkingDirectoryAsync emite o comando CWD para o servidor 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));
  }
}

Se o usuário deseja navegar para trás, ele pode enviar pontos duplos "..." como um parâmetro para esse procedimento. Quando a alteração for bem-sucedida, o cliente é notificado e o FtpDirectoryChangedSucceeded evento é gerado, que pode ser visto na Figura 6. Se CWD falha e envia um erro em resposta, o cliente é notificado de que o fracasso e o FtpDirectoryChangedFailed evento é gerado:

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
Figura 6 alterar o diretório de trabalho no servidor de FTP

Adicionando suporte para comandos passivos

Agora é tempo para fornecer suporte para comandos passivos, porque eu quero que o servidor FTP para gerenciar todas as conexões de dados para transferir dados para e do servidor. No modo passivo, qualquer comando que precisa transferir dados deve usar uma conexão de dados iniciada pelo servidor FTP. O servidor FTP irá criar uma conexão de dados e enviar as informações de soquete para essa conexão de dados para que o cliente pode se conectar a ele e executar algumas operações de transferência de dados.

Modo passivo é de natureza temporário. Antes de enviar um comando para o servidor FTP que vai enviar ou receber dados, em resposta, o cliente tem que dizer ao servidor para entrar em modo passivo — e deverá fazê-lo para cada comando enviado. Em poucas palavras, para cada transferência de dados, dois comandos são acionados sempre — passiva e, em seguida, o comando em si.

O servidor FTP é dito para se preparar para modo passivo através do comando PASV. Em resposta, ele envia o número de porta e endereço IP da conexão de dados em um formato codificado. A resposta é decodificada e o cliente então prepara a conexão de dados com o servidor FTP usando o número de porta e endereço IP decodificado. Uma vez concluída a operação de dados, esta conexão de dados é fechado e dissolvida, para que não pode ser usado novamente. Isso acontece toda vez que o comando PASV é enviado para o servidor FTP.

A resposta do comando PASV de decodificação a resposta do comando PASV parecido com este:

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

A resposta tem informações de canal de dados contidas nela. Baseado nesta resposta, eu tenho que formular o endereço de soquete — o IP endereço e dados porta FTP server está usando para a operação de transferência de dados. Aqui estão os passos para calcular o endereço IP e a porta, como mostrado em Figura 7:

  • Os primeiros grupos de quatro inteiros formam o endereço IPV4 do servidor FTP; ou seja, 192.168.33.238.
  • Os restantes inteiros compõem a porta de dados. Você shift esquerda o quinto inteiro grupo com 8 e em seguida, executar operação Bitwise OR com o sexto grupo inteiro. O valor final dá-lhe o número da porta onde o canal de dados estará disponível.

Passive Command Request and Reply
Resposta e pedido de comando passivo Figura 7

Figura 8 mostra o código que analisa a resposta e extrai o número de porta e endereço IP do ponto de extremidade do canal de dados. O método PrepareDataChannelAsync recebe a resposta FTP bruta para o comando PASV. Às vezes a seqüência de caracteres de resposta pode ser alterada no servidor de FTP para incluir alguns outros parâmetros, mas essencialmente a resposta envia apenas o endereço IP e porta número.

Figura 8-análise de ponto de extremidade do canal de dados

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");
}

Mais comandos: Lista, tipo, STOR e RETR

Listando o conteúdo do diretório para listar o conteúdo do diretório de trabalho atual, eu enviar o comando lista sobre o canal de comando para o servidor FTP. No entanto, o servidor FTP enviará sua resposta a este comando sobre o canal de dados, para que eu primeiro deve enviar o comando PASV para criar a conexão de dados sobre a qual será enviada a resposta do comando lista. O formato de listagem de diretório vai basear-se o sistema operacional no qual está instalado o servidor FTP. Ou seja, se o servidor FTP, sistema operacional Windows, a listagem de diretório será recebida em formato de listagem de diretório do Windows, e se o sistema operacional é baseado em Unix, a listagem de diretório será recebida em formato Unix.

Para listar o conteúdo do diretório do diretório de trabalho atual, eu emitir o comando de lista na caixa de texto comando e, em caso de toque do botão de comando enviar, chamo o método de 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;
  }
  ...
}

Internamente, GetDirectoryListingAsync envia o comando PASV para o servidor FTP Socket:

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");
  }
}

Uma vez que as informações de ponto de extremidade são recebidas para o comando PASV, a conexão de dados é criada e o comando LIST é então emitido para o servidor FTP, como mostrado em Figura 9.

Figura 9 o comando List do soquete DataReceived evento de processamento

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");
  ...
}

Quando a listagem de diretório é recebida pelo canal de dados, o FtpDirectoryListed evento é gerado com a lista de arquivos e diretórios em seus argumentos de evento (Figura 10 mostra a enviar os comandos PASV e lista, e Figura 11 exibe a saída de listagem de diretório):

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
Figura 10 enviando o comando LIST para o servidor de FTP

Displaying FileSystem Objects in Response to LIST Command
Figura 11 exibindo objetos de sistema de arquivos em resposta ao comando LIST

Descrevendo o formato de dados o tipo comando é usado para descrever o formato de dados em que dados do arquivo serão recebidos ou enviados. Ele não requer o modo PASV. Eu uso o "eu" Formatar com o comando TYPE para significar uma transmissão de dados de imagem-tipo. TIPO é geralmente usado quando armazenar ou recuperar arquivos através de uma conexão de dados. Isso informa o servidor FTP que a transmissão acontecerá em modo binário, em vez de no modo de texto ou alguns dados estruturais. Para outros modos que podem ser usados com o comando de tipo, consulte RC 959 para FTP.

Armazenar um arquivo no servidor FTP para armazenar o conteúdo de um arquivo (localizado em um dispositivo) para um servidor FTP, enviar o comando STOR juntamente com o nome do arquivo (e extensão) o FTP servidor irá usar para criar e salvar o arquivo. A transmissão do conteúdo do arquivo será executada em uma conexão de dados, antes de enviar o STOR, eu vou consultar os detalhes de ponto de extremidade do servidor FTP usando o comando PASV. Uma vez que o ponto de extremidade é recebido, eu emitirei STOR, e quando eu receber a resposta a isso, eu vou enviar o conteúdo do arquivo em formato binário para o servidor FTP através da conexão de dados.

O comando STOR pode ser enviado, chamando o método UploadFileAsync, conforme mostrado no Figura 12. Este método usa dois parâmetros — o objeto de fluxo de arquivo local e o nome do arquivo no servidor FTP como uma seqüência de caracteres.

Figura 12 o método 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;
  }
  ...
}

Internamente, o método UploadFileAsync emite um comando PASV para o servidor FTP para buscar as informações de ponto de extremidade do canal de dados:

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");
  }
}

Como mostrado em Figura 13, em resposta do comando PASV dentro do evento DataReceived, o comando STOR é emitido depois que o canal de dados é criado e aberto; a transmissão de dados, em seguida, a partir de cliente para servidor.

Figura 13 o comando STOR do soquete DataReceived evento de processamento

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));
  ...
}

Enquanto o ficheiro está a ser enviado, o cliente é notificado o progresso de upload via FtpFileTransferProgressed:

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

Se a operação de upload de arquivo for concluída com êxito, o FtpFile­UploadSucceeded evento é gerado. Se não, o FtpFileUploadFailed evento é gerado com a razão de falha no seu argumento:

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
}

Figura 14 e Figura 15 exibir o processo de upload de um arquivo para o servidor FTP.

Uploading a File to the FTP Server
Figura 14 carregando um arquivo para o servidor de FTP

Successful File Upload
Upload de arquivo bem-sucedida Figura 15

Recuperando um arquivo de um servidor FTPpara recuperar o conteúdo de um arquivo de um servidor FTP, eu enviar o comando RETR juntamente com o nome do arquivo e sua extensão para o servidor FTP (supondo que o arquivo está no servidor no diretório atual). A transmissão do conteúdo do arquivo irá usar uma conexão de dados, então, antes de enviar RETR, eu vou consultar os detalhes de ponto de extremidade do servidor FTP usando o comando PASV. Uma vez que o ponto de extremidade é recebido, vou enviar o comando RETR, e depois de receber a resposta a isso, eu vou recuperar o conteúdo do arquivo no formato binário do servidor FTP através da conexão de dados.

Como mostrado em Figura 16, o comando RETR pode ser enviado, chamando o método DownloadFileAsync. Este método usa dois parâmetros — o objeto de fluxo do arquivo local para salvar o conteúdo e o nome do arquivo no servidor FTP como uma seqüência de caracteres.

Figura 16 o método 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;
  }
  ...
}

Internamente, o método DownloadFileAsync emite um comando PASV para o servidor FTP para buscar informações de ponto de extremidade do canal de dados:

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");
  }
}

Como mostrado em Figura 17, em resposta do comando PASV dentro do evento DataReceived, o comando RETR é emitido depois que o canal de dados é criado e aberto; Então começa a transmissão de dados, execução de servidor para o cliente.

Figura 17 o comando RETR do soquete DataReceived evento de processamento

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));
  ...
}
}

Enquanto o arquivo está baixando, o cliente é notificado do progresso de download via FtpFileTransferProgressed:

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

Se o arquivo baixando a operação for concluída com êxito, o FtpFileDownloadSucceeded evento é gerado. Se não, o FtpFileDownloadFailed evento é gerado com a razão de falha no seu argumento:

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
}

Figura 18 e Figura 19 exibir o processo de baixar um arquivo do servidor FTP.

Downloading a File from the FTP Server
Figura 18 download de um arquivo do servidor FTP

Successful File Download
Download de arquivo bem-sucedida de Figura 19

Conclusão

Observe que você pode fornecer uma associação de URI para um app aplicação FTP, para que outros aplicativos também podem acessar os dados de serviço e solicitação FTP do servidor FTP. Você encontrará mais informações sobre este na página Windows Phone Dev Center, "Auto-lançamento apps usando o arquivo e associações de URI para Windows Phone 8," em bit.ly/XeAaZ8e o código de amostra em bit.ly/15x4O0y.

O código que escrevi para minha biblioteca FTP e cliente app para Windows Phone é totalmente suportado no Windows 8. x, como eu não usei qualquer API que não é compatível com o Windows 8. x. Você também pode recompilar o código para o Windows 8. x, ou colocá-lo em um portátil classe biblioteca (PCL) que pode direcionar as duas plataformas.

Uday Gupta é engenheiro sênior - desenvolvimento de produtos para sinfonia Teleca Corp. (Índia) Pvt. Ltd. Ele tem experiência em muitas tecnologias .NET, especialmente em Windows Presentation Foundation (WPF), Silverlight, Windows Phone e Windows 8. A maioria de seu tempo é gasto na codificação, jogos, aprender coisas novas e ajudar os outros.

Graças aos seguintes especialistas técnicos para revisar este artigo: Tony campeão (campeão DS) e Andy Wigley (Microsoft)
Tony Champion é presidente da DS campeão, é um Microsoft MVP e ativo na Comunidade como um alto-falante, blogueiro e autor. Ele mantém um blog em tonychampion.net e pode ser contatado via e-mail no tony@tonychampion.net.

Andy Wigley é um divulgador técnico trabalhando para Microsoft UK. Andy é conhecido para os vídeos de Windows Phone JumpStart populares que estão disponíveis na channel9. msdn.com e é palestrante regular em conferências principais tais como Tech Ed.