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 namespace 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 um 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 que um processo executando em um computador envie/receba mensagens para outro processo e até mesmo a partir dele, que pode estar sendo executado no mesmo computador ou em um 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 que representa a comunicação de sockets e a pilha TCP/IP:
Paradigma cliente/servidor (modo orientado à 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 comunicação 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 conhecidos 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, pois o TCP é mais complexo e tem latências mais longas. Porém, quando a integridade dos dados a serem transferidos é mais importante do que o desempenho, 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 corrompa 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 layout 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 Enviar Arquivo recebe o endereço IP e número de porta do computador de destino, bem como o caminho 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 objeto 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 está 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








