C# - Compartilhando arquivos - Cliente/Servidor (socket) - Client

 Neste artigo vamos criar uma aplicação para compartilhar arquivos usando a arquitetura cliente/servidor e os recursos dos sockets na linguagem C#.


A plataforma .NET fornece um conjunto de classes no namespaces System.Net.sockets que torna a nossa vida mais fácil , pois elas fornecem um conjunto de funcionalidades que abstraem muitas tarefas que antes exigiam a criação de código extra.

Antes de entrar na aplicação propriamente dita vamos recordar alguns conceitos básicos.

O que é um socket ?

Um socket pode ser entendido como uma porta de um canal de comunicação que permite a um processo executando em um computador enviar/receber mensagens para/de outro processo que pode estar sendo executado no mesmo computador ou num computador remoto.

Os sockets permitem então a comunicação processo a processo da seguinte forma :

  • comunicação local : processos locais usando sockets locais

  • comunicação remota : processos remotos usando sockets em rede (TCP/IP)

Abaixo temos uma figura com que representa a comunicação de sockets e a pilha TCP/IP

Tipos de serviço de transporte:
  • Datagrama - transporte não orientado a conexão  e sem controle de erros ( protocolo UDP)
  • DataStream - transporte orientado a conexão com controle de erros ( protocolo TCP )

Paradigma cliente/servidor (modo orientado a conexão )

A seguir a seqüência de ações realizadas no paradigma cliente/servidor

Cliente Servidor
Cria um socket e atribui-lhe um endereço cria um socket e atribui-lhe um endereço. Este endereço deve ser conhecido pelo cliente.
Solicita a conexão do seu socket ao socket do servidor (conhece o endereço ) Aguarda a conexão de um cliente
Aguarda que a conexão seja estabelecida Aceita a conexão e cria um novo socket para comunicar com o cliente em causa
Envia uma mensagem ( request ) Recebe a mensagem no novo socket
Recebe a mensage de resposta (reply) Envia mensagem de resposta (reply)
Fecha a conexão com o servidor fecha a conexão com o cliente

Assim um Socket é um objeto que representa um ponto de acesso de baixo nível para a pilha do protocolo Internet (IP), onde ele é usado para enviar e receber dados, podendo ser aberto e fechado; os dados a serem enviados são sempre enviados em blocos conhecido como pacotes.

Os pacotes devem conter o endereço IP da origem dos pacotes e do computador de destino onde os dados estão sendo enviados e, opcionalmente, ele pode conter um número de porta. Um número de porta está entre 1 e 65.535. Uma porta é um canal de comunicação ou nós de extremidade nos quais os computadores podem se comunicar. Recomenda-se sempre que os programas utilizem um número de porta superior a 1024 para evitar conflitos com outras aplicações em execução no sistema, uma vez que não existem duas aplicações que podem utilizar a mesma porta.

Os pacotes contendo números de porta podem ser enviados usando UDP (User Datagram Protocol) ou TCP/IP (protocolo de controle de transmissão).

O UDP é mais fácil de usar do que o TCP porque o TCP é mais complexo e tem latências mais longas, mas quando a integridade dos dados a serem transferidos é mais importante do que o desempenho, então o TCP é preferível ao UDP e, portanto, no nosso aplicativo de compartilhamento de arquivos iremos utilizar o TCP/IP  pois ele garante que nosso arquivo não se corrompe enquanto está sendo transferido, e, se durante o processo de transmissão um pacote for perdido, ele será retransmitido, tendo dessa forma a integridade do arquivo mantida.

Para colocar em prática a teoria vou criar dois projetos : TCPServidor e TCPCliente. Como não vou usar threads vamos precisar executar cada projeto separadamente. O projeto TCPServidor deverá ser executado primeiro e a seguir o projeto TCPCliente.

Lembrando que o exemplo funciona para arquivos menores que 1 GB e que para testar em máquinas remotas você deve verificar a comunicação entre as máquinas e as configurações do seu firewall.

Então vamos ao trabalho...

Criando o projeto no VS Community 2015

Abra no VS community 2015 e no menu File clique em New Project;

A seguir selecione o template Visual C# -> Windows -> Windows Forms Application e informe o nome TCPCliente e clique em OK;

Agora abra o formulário Form1.cs e inclua os seguintes controles no formulário:

Disponha os controles conforme o leiaute da figura abaixo:

Definindo o código do formulário

Inclua os seguintes namespaces no formulário:

using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

No início do formulário declare a seguinte variável:

private static string nomeAbreviadoArquivo = "";

No evento Click do botão de comando Procurar inclua o código abaixo:

private void btnProcurar_Click(object sender, EventArgs e)
 {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Title = "Envio de Arquivo - Cliente";
            dlg.ShowDialog();
            txtArquivo.Text = dlg.FileName;
            nomeAbreviadoArquivo = dlg.SafeFileName;
 }

No evento Click do botão de comando Enviar Arquivo inclua o código abaixo:

        private void btnEnviarArquivo_Click(object sender, EventArgs e)
        {
            if(string.IsNullOrEmpty(txtEnderecoIP.Text) &&
                string.IsNullOrEmpty(txtPortaHost.Text) &&
                string.IsNullOrEmpty(txtArquivo.Text))
            {
                MessageBox.Show("Dados Inválidos...");
                return;
            }
            //
            string enderecoIP = txtEnderecoIP.Text;
            int porta = int.Parse(txtPortaHost.Text);
            string nomeArquivo = txtArquivo.Text;
            //
            try
            {
                Task.Factory.StartNew(() => EnviarArquivo(enderecoIP, porta, nomeArquivo, nomeAbreviadoArquivo));
                MessageBox.Show("Arquivo Enviado com sucesso");
            }
            catch(Exception ex)
            {
                MessageBox.Show("Erro : " + ex.Message);
            }
     }

Neste código obtemos os valores para o IP , porta e arquivo a ser enviado e chama o método EnviarArquivo() passando essas informações para que o arquivo seja enviado.

O código do método EnviarArquivo() é o seguinte:

       public void EnviarArquivo(string IPHostRemoto, int PortaHostRemoto, string nomeCaminhoArquivo, string nomeAbreviadoArquivo)
        {
            try
            {
                if (!string.IsNullOrEmpty(IPHostRemoto))
                {
                    byte[] fileNameByte = Encoding.ASCII.GetBytes(nomeAbreviadoArquivo);
                    byte[] fileData = File.ReadAllBytes(nomeCaminhoArquivo);
                    byte[] clientData = new byte[4 + fileNameByte.Length + fileData.Length];
                    byte[] fileNameLen = BitConverter.GetBytes(fileNameByte.Length);
                    //
                    fileNameLen.CopyTo(clientData, 0);
                    fileNameByte.CopyTo(clientData, 4);
                    fileData.CopyTo(clientData, 4 + fileNameByte.Length);
                    //
                    TcpClient clientSocket = new TcpClient(IPHostRemoto, PortaHostRemoto);
                    NetworkStream networkStream = clientSocket.GetStream();
                    //
                    networkStream.Write(clientData, 0, clientData.GetLength(0));
                    networkStream.Close();
                }
            }
            catch
            {
                throw;
            }
        }

No código acima temos o código que vai enviar o arquivo selecionado.

Quando o botão Procurar for clicado um objeto OpenFileDialog será criado para abrir uma caixa de diálogo e obter o nome do arquivo a ser enviado.

Quando o botão Enviar Arquivo for clicado, o método EnviarArquivo será chamado e manipulado em paralelo para que a interface de usuário do formulário principal não fique congelado enquanto o arquivo está sendo enviado e os processadores são totalmente utilizados em um ambiente multi-core.

O método EnviarArquivo recebe o endereço IP e número de porta do computador de destino, bem como o caminho e nome do arquivo e o nome do arquivo. Tanto o nome do arquivo como o arquivo são convertidos em bytes e enviados para o computador de destino utilizando TCPClient e objecto da classe NetworkStream criados.

Simples assim...

Dessa forma temos a aplicação cliente pronta para ser usada, falta agora a aplicação servidor para receber o arquivo enviado.

Lembrando que esse exemplo é uma das muitas abordagens que podemos fazer para enviar arquivos.

No projeto eu criei uma classe chamada Cliente com o código abaixo onde temos o método Enviar que mostra outra forma de enviar o arquivo:(Fique a vontade para usar ou não esse método)

using System;
using System.Net.Sockets;
using System.Threading;
namespace TCPCliente
{
    public class Arquivos
    {
        public static void Enviar(Socket socket, byte[] buffer, int offset, int tamanho, int timeout)
        {
            //Obtém o número de milissegundos decorridos desde a inicialização do sistema.
            int iniciaContagemTick = Environment.TickCount;
            //define o número de bytes enviados
            int enviados = 0;  // quantos bytes ja foram enviados

            do
            {
                //verifica se o timeout ocorreu
                if (Environment.TickCount > iniciaContagemTick + timeout)
                    throw new Exception("Tempo esgotado.");
                try
                {
                   //envia o arquivo e computa os bytes enviados
                    enviados += socket.Send(buffer, offset + enviados, tamanho - enviados, SocketFlags.None);
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
                        ex.SocketErrorCode == SocketError.IOPending ||
                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                    {
                        // o buffer do socket buffer esta cheio , aguarde e tente novamente
                        Thread.Sleep(30);
                    }
                    else
                    {
                        throw ex;  // ocorreu um erro catastrófico
                    }
                }
            } while (enviados < tamanho);
        }
    }
}

A classe esta comentada e a seguir temos um trecho de código de como usar o método Enviar :

  Socket socket = tcpClient.Client;
  string arquivo = "exemplo de arquivo para envio!";
  try
  {
// envia o texto com timeout de 10s
     Arquivos.Enviar(socket, Encoding.UTF8.GetBytes(arquivo), 0, str.Length, 10000);
  }
  catch (Exception ex)
  { /* ... */ }

Na próxima parte do artigo vamos criar o projeto TCPServidor.

Pegue o projeto completo aqui: TCPCliente.zip

1 Todo aquele que crê que Jesus é o Cristo, é nascido de Deus; e todo aquele que ama ao que o gerou também ama ao que dele é nascido. 2 Nisto conhecemos que amamos os filhos de Deus, quando amamos a Deus e guardamos os seus mandamentos.
1 João 5:1,2

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

  Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

Referências:


José Carlos Macoratti