O C# em conjunto com a plataforma .Net nos oferecem muitos recursos para trabalharmos com soquetes, que nos permitem trabalhar com recursos de rede que facilitam o desenvolvimento de aplicativos baseados na rede mundial de computadores, a internet.
Neste artigo focalizaremos os dois lados da aplicação, o lado cliente e o lado servidor. De forma simples podemos dizer que o lado clientes faz uma requisição ao servidor, e o servidor tratará de processar essa requisição feita pelo cliente. Para exemplificar o processo pense no seu navegador, quando você for navegar pela internet você abre o seu navegador, digita um endereço de algum site nele (lado cliente, fazendo a requisição) e um servidor da web responde essa requisição (lado servidor, executando a requisição do cliente).
Os recursos para se trabalhar com soquetes no C# estão no namespace system.net.sockets. Para comunicação via rede, soquetes são essenciais. É a principal maneira para este tipo fr comunicação.
O C# e a plataforma .Net em conjunto utilizam-se da comunicação baseada em fluxo, ou popularmente soquetes de fluxo. Podemos sistematicamente dizer que esses soquetes de fluxo consistem num programa que esta sendo executado estabelece a conexão com outro programa que também esta sendo executado, com isso enquanto ambos estão ativos os dados ao fluindo entre ambos em fluxos contínuos. O principal protocolo da internet, o TCP (Transmission Control Protocol) oferece o suporte que facilita a transmissão de soquete de fluxo.
Saiba mais: Acessando serviços WCF via TCP
Há também classes e interfaces do namespace System.Net.Sockets que usam como protocolo o UDP, nesse caso o protocolo é usado para ativar soquetes de datagrama e não mais fluxos contínuos como o TCP. O grande problema deste serviço é que ele não garante que os pacotes cheguem em uma ordem específica, pois ele não verifica a integridade dos pacotes que chegam ao destinatário. Assim os pacotes podem ser perdidos ou duplicados ou então chegar fora de seqüência. Existem hoje muitos aplicativos que usam UDP, pois ele é mais rápido que o TCP, freqüentemente jogos preferem aderir ao UDP, pois se leva em consideração muito mais a velocidade do que a integridade dos dados.
Neste artigo vamos nos ater apenas a usar o TCP.
Fazendo um servidor simples
O servidor espera por pela requisição de conexão de um cliente. Portanto, o servidor deve ter uma estrutura de modo que ele será executado continuamente, até receber a requisição do cliente. Somente quando o servidor receber a requisição do cliente é que será efetuada a conexão entre ambos os processos e assim se dará a troca de dados entre eles.
Colocando a “mão na massa”, antes de mais nada devemos criar um objeto da classe responsável pelas requisições, a classe TcpListener:
TcpListener servidor = new TcpListener( numero_da_porta );
Onde o número da porta é a porta na qual vai aguardar uma conexão de um aplicativo servidor naquela porta. Vale salientar que o numero de portas podem ter valores somente entre 0 e 65535. Sugere-se que use portas acima de 1024, pois as portas abaixo de 1024 normalmente são destinadas ao sistema e não devem ser usadas, pois pode causa conflitos. De forma simples, com o comando acima estamos dizendo que o TcpListener primeiramente irá “ouvi-las” na porta indicada.
Agora o nosso servidor precisa começar a ouvir as requisições de conexão, e para isso usamos o método Start da classe TcpListener:
servidor.Start();
Após isso, nos resta agora estabelecer a conexão entre o servidor e o cliente. Isso se dá após o servidor receber uma requisição de um cliente, agora o servidor deverá estabelecer uma conexão com o cliente. Para isso usamos o método AcceptSocket da classe TcpListener é quem cria esta conexão após o recebimento da requisição, este método irá retornar um objeto socket:
Saiba mais: Java Socket
Socket conexao = servidor.AcceptSocket();
Dando prosseguimento teremos agora a fase de processamento, em que nada mais é do que o servidor e o cliente se comunicando. Para isto eles utilizam os métodos Receive e Send. Devemos salientar que ambos os métodos apenas deve, ser utilizados caso o servidor e o cliente já estejam conectados.
Um grande problema que encontramos aqui trata-se de quando o servidor está processando a requisição de um cliente X e um outro cliente Y tenta se conectar ao servidor. O servidor assim irá bloquear qualquer outra tentativa de conexão enquanto está se comunicando com um cliente. Para tratar deste problema temos que aderir ao servidor de múltiplas linhas de execução ou mais conhecido como multithreading que nada mais faz que colocar o código de processamento em uma linha de execução separada. Portanto de forma resumida, ao receber uma conexão de um cliente o servidor irá gerar um objeto Thread para processar essa conexão efetuada por este cliente, assim seu TcpListener agora não mais estará ocupado e assim sendo estará pronto para receber outras conexões. Esse tipo de método é muito usado hoje e para diversas ocasiões, inclusive em servidores de grande porte para web.
socketStream = new NetworkStream(conexao);
escrever = new BinaryWriter(socketStream);
ler = new BinaryReader(socketStream);
Onde “socketStream = new NetworkStream(conexao);” cria o objeto NetworkStream associado do soquete. E nas linhas subseqüentes criamos os objetos pra transferir os dados.
Por fim deve fechar-se a conexão entre o servidor e o cliente. Isso se da através do método Close do objeto Socket.
servidor.Close();
Fazendo um Cliente simples
O cliente é aquele que se conecta e troca dados com o servidor. A primeira coisa que um cliente deve estabelecer é uma conexão com o servidor, e isto se dá através do objeto da classe TcpClient, também pertencendo ao namespace System.Net.Sockets.
Esta conexão se dá através do método Connect da classe TcpClient.
TcpClient cliente = new TcpClient();
client.Connect(Endereco_do_Servidor, Porta_do_Servidor);
Onde “Enredeco_do_Servidor” é o endereço IP do servidor, e “Porta_do_Servidor” é a porta do servidor. Com isso o cliente irá fazer um pedido de conexão ao servidor de IP e porta indicados.
Saiba mais: Como criar minha primeira classe em C#
Adiante teremos que escrever e ler no servidor, com isso usaremos os métodos GetStream para obter um objeto NetworkStream. Usaremos os métodos WriteByte e Write, e os métodos ReadByte e Read de NetworkStream, para produzir a saída no servidor e para introduzir dados no servidor respectivamente. Usaremos esses métodos para a fase de troca de dados entre o cliente e o servidor. Lembrando que de forma idêntica ao servidor, aqui também poderemos usar múltiplas linhas de execução para que o cliente possa se comunicar com múltiplos servidores.
saida = cliente.GetStream();
escreve = new BinaryWriter(saida);
ler = new BinaryReader(saida);
Por fim teremos que fecha a conexão com o servidor, para isso usaremos o método Close da classe TcpClient, assim terminaremos a conexão TCP.
client.Close();
Projetando um aplicativo Cliente/Servidor
Vamos elaborar agora um exemplo de um aplicativo cliente que se conectará a um aplicativo servidor.
Primeiramente vamos desenvolver o servidor, abaixo segue o código de um aplicativo servidor que receberá a requisição para conexão e mensagens de um aplicativo cliente, o servidor também poderá enviar mensagens para o aplicativo cliente:
sing System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Net.Sockets;
namespace Servidor
{
public class Server : System.Windows.Forms.Form
{
private System.Windows.Forms.TextBox Envia;
private System.Windows.Forms.TextBox Exibe;
private Socket conexao;
private Thread tipoThread;
private System.ComponentModel.Container components = null;
private NetworkStream socketStream;
private BinaryWriter escreve;
private BinaryReader le;
public Server()
{
InitializeComponent();
// thread para aceitar multiplas conexões
tipoThread = new Thread( new ThreadStart( RunServer ) );
tipoThread.Start();
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Exibe = new System.Windows.Forms.TextBox();
this.Envia = new System.Windows.Forms.TextBox();
this.SuspendLayout();
this.Exibe.Location = new System.Drawing.Point(8, 40);
this.Exibe.Multiline = true;
this.Exibe.Name = "Exibe";
this.Exibe.Size = new System.Drawing.Size(299, 238);
this.Exibe.TabIndex = 1;
this.Envia.Location = new System.Drawing.Point(8, 8);
this.Envia.Name = "Envia";
this.Envia.Size = new System.Drawing.Size(272, 20);
this.Envia.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Envia_KeyDown);
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(299, 278);
this.Controls.Add(this.Exibe);
this.Controls.Add(this.Envia);
this.Name = "Server";
this.Text = "Servidor";
this.Closing += new System.ComponentModel.CancelEventHandler(this.Server_Closing);
this.Load += new System.EventHandler(this.Server_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
[STAThread]
static void Main()
{
Application.Run( new Server() );
}
protected void Server_Closing(
object sender, CancelEventArgs e )
{
System.Environment.Exit( System.Environment.ExitCode );
}
protected void Envia_KeyDown(
object sender, KeyEventArgs e )
{
// Aqui está o código responsável para mandar mensagens
try
{
if ( e.KeyCode == Keys.Enter && conexao != null )
{
writer.Write( Envia.Text );
Exibe.Text += Envia.Text;
if ( Envia.Text == "FINALIZAR" )
conexao.Close();
Envia.Clear();
}
}
catch ( SocketException )
{
Exibe.Text += "Atneção! Erro...";
}
}
public void RunServer()
{
TcpListener escutando;
int conta = 1; //contaremos quantas conexões teremos
try
{
escutando = new TcpListener( 9000 );
escutando.Start();
while ( true )
{
Exibe.Text = "Aguardando Conexoes";
conexao = escutando.AcceptSocket();
escreve = new BinaryWriter( socketStream );
le = new BinaryReader( socketStream );
socketStream = new NetworkStream( conexao );
Exibe.Text += conta + " Conexões Recebidas!";
escreve.Write( "Cenexão Efetuada!" );
Envia.ReadOnly = false;
string resp = "";
do
{
try
{
resp = le.ReadString();
Exibe.Text += "\r\n" + resp;
}
catch ( Exception )
{
break;
}
} while ( resp != "FIM" && conexao.Connected );
Exibe.Text += "Conexão Finalizada!";
//fechando a conexao
escreve.Close();
le.Close();
socketStream.Close();
conexao.Close();
++conta;
}
}
catch ( Exception error )
{
MessageBox.Show( error.ToString() );
}
}
}
}
Nosso servidor está pronto para receber conexões, agora só falta fazermos o nosso cliente para se conectar ao servidor e interagir com ele.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;
using System.Net.Sockets;
using System.IO;
namespace Cliente
{
public class Client : System.Windows.Forms.Form
{
private System.Windows.Forms.TextBox Envia;
private System.Windows.Forms.TextBox Exibe;
private NetworkStream sockStream;
private BinaryWriter escreve;
private BinaryReader le;
private string message = "";
private Thread tipoThread;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public Client()
{
InitializeComponent();
tipoThread = new Thread( new ThreadStart( RunClient ) );
tipoThread.Start();
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Envia = new System.Windows.Forms.TextBox();
this.Exibe = new System.Windows.Forms.TextBox();
this.SuspendLayout();
this.Envia.Location = new System.Drawing.Point(8, 8);
this.Envia.Name = "Envia";
this.Envia.Size = new System.Drawing.Size(289, 20);
this.Envia.Text = "";
this.Envia.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Envia_KeyDown);
this.Exibe.Location = new System.Drawing.Point(8, 40);
this.Exibe.Multiline = true;
this.Exibe.Name = "Exibe";
this.Exibe.Size = new System.Drawing.Size(282, 218);
this.Exibe.Text = "";
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(272, 272);
this.Controls.AddRange(new System.Windows.Forms.Control[]
{
this.Exibe, this.Envia});
this.Name = "Client";
this.Text = "Cliente";
this.Closing += new System.ComponentModel.CancelEventHandler(this.Client_Closing);
this.ResumeLayout(false);
}
#endregion
[STAThread]
static void Main()
{
Application.Run( new Client() );
}
protected void Client_Closing(
object sender, CancelEventArgs e )
{
System.Environment.Exit( System.Environment.ExitCode );
}
protected void Envia_KeyDown (
object sender, KeyEventArgs e )
{
try
{
if ( e.KeyCode == Keys.Enter )
{
escreve.Write( Envia.Text );
Exibe.Text += Envia.Text;
Envia.Clear();
}
}
catch ( SocketException )
{
Exibe.Text += "Atenção! Erro...";
}
}
public void RunClient()
{
TcpClient cliente;
try
{
cliente = new TcpCliente();
cliente.Connect( "localhost", 9000 );
//Se preferir altere localhost pelo IP do server
sockStream = cliente.GetStream();
escreve = new BinaryWriter( sockStream );
le = new BinaryReader( sockStream );
do
{
try
{
message = le.ReadString();
Exibe.Text += message;
}
catch ( Exception )
{
System.Environment.Exit(System.Environment.ExitCode );
}
} while( message != "FIM" );
escreve.Close();
le.Close();
sockStream.Close();
cliente.Close();
Application.Exit();
}
catch ( Exception error )
{
MessageBox.Show( error.ToString() );
}
}
}
}
Conclusão
Espero que todos tenham gostado do artigo. Podemos notar o quanto é importante este tema devido as inúmeras aplicações que podemos fazer, além disso todos sabemos o quanto a internet é importante hoje e o quanto nos facilita, através dela, a interação com o usuário. Outros artigos sobre o tema virão, qualquer dica ou sugestão me enviem que terei o prazer de responder e sanar qualquer fato não esclarecido.
Links Úteis
- Como criar minha primeira classe em C#: Neste conteúdo você aprenderá a criar sua primeira classe na linguagem C#. Aprenda também a usar herança e interfaces, bem como métodos, atributos e propriedades.
- Um bate-papo sobre o Delphi: O Delphi está em constante evolução e com ele podemos criar aplicações desktop, servidores de aplicações, aplicativos móveis, entre outros.
- Como publicar seu app iOS na App Store: Neste curso você aprenderá a publicar seu aplicativo iOS na App Store, disponibilizando-o para que seus usuários possam fazer download e instalá-lo em seus dispositivos.
Saiba mais sobre C# ;)
- Carreira Programador .NET: Neste guia de estudos você encontra os conteúdos que precisará para se tornar um programador .NET/C# completo. Confira a sequência de cursos e exemplos que te guiarão do básico ao avançado no ecossistema de programação Microsoft.
- Linguagem C#: Neste guia de consulta você encontrará diversos conteúdos que podem ser usados ao longo dos seus estudos sobre a linguagem de programação C#. Consulte este guia para aprender mais sobre certos recursos da linguagem.
- Introdução a programação com C#: Este curso possui o objetivo de capacitar desenvolvedores em linguagem C#. Através da exemplificação das rotinas mais básicas da linguagem e sua sintaxe.