Neste artigo vamos criar uma aplicação para compartilhar arquivos usando a arquitetura cliente/servidor e os recursos dos sockets na linguagem C#. |
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:
|
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:
4 Labels
3 TextBox - txtEnderecoIP, txtPortaHost, txtArquivo
2 Buttons - btnProcurar , btnEnviarArquivo
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:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#
VB .NET - Programação MultiThread Socket - Servidor - Macoratti