C # - Criando um Chat - Parte 1 - O Cliente


Neste artigo eu vou mostrar como criar um Chat procurando ser bem objetivo e mantendo as coisas o simples possível.

Na verdade uma aplicação de Chat (bate-papo) consiste em duas aplicações:

Para poder acompanhar e entender tudo que vamos fazer é necessário que se tenha conhecimento sobre os seguintes tópicos:

Vamos iniciar criando o cliente.

Chat - Criando a aplicação cliente

A aplicação cliente é a mais simples, já que tudo o que ela tem a fazer é tentar se conectar ao servidor de bate-papo, pedir um nome de usuário, começar a ouvir as mensagens e enviar suas próprias e, finalmente, desligar.

Inicie o Visual C# 2010 Express Edition e crie um novo projeto do tipo Windows Appliation com o nome ChatCliente:

Vamos alterar o nome do formulário form1.cs criado por padrão no projeto para frmCliente.cs e a seguir definir o leiaute conforme mostra a figura abaixo no formulário:

Controles usados no formulário:
  • txtServidorIP - informa o ip do servidor;
  • txtUsuario - informa o nome do usuário;
  • btnConectar - Realiza a conexão com o servidor;
  • txtLog - Exibe todas as mensagens;
  • txtMensagem - Informa a mensagem a ser enviada;
  • btnEnviar - Envia a mensagem;

Vamos agora ao código do cliente:

1- Namespaces usados:

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

2- Declarando as variáveis usadas no projeto:

// Trata o nome do usuário
private string NomeUsuario = "Desconhecido";
private StreamWriter stwEnviador;
private StreamReader strReceptor;
private TcpClient tcpServidor;

// Necessário para atualizar o formulário com mensagens da outra thread
private delegate void AtualizaLogCallBack(string strMensagem);
// Necessário para definir o formulário para o estado "disconnected" de outra thread
private delegate void FechaConexaoCallBack(string strMotivo);
private Thread mensagemThread;
private IPAddress enderecoIP;
private bool Conectado;

3- Código do evento Click do botão Conectar que verifica se estamos conectados e inicia a conexão;

     private void btnConectar_Click(object sender, System.EventArgs e)
        {
            // se não esta conectando aguarda a conexão
            if (Conectado == false)
            {
                // Inicializa a conexão
                InicializaConexao();
            }
            else // Se esta conectado entao desconecta
            {
                FechaConexao("Desconectado a pedido do usuário.");
            }
        }

4- Código da rotina IncializaConexao():

  private void InicializaConexao()
        {

            // Trata o endereço IP informado em um objeto IPAdress
            enderecoIP = IPAddress.Parse(txtServidorIP.Text);
            // Inicia uma nova conexão TCP com o servidor chat
            tcpServidor = new TcpClient();
            tcpServidor.Connect(enderecoIP, 2502);

            // AJuda a verificar se estamos conectados ou não
            Conectado = true;

            // Prepara o formulário
            NomeUsuario = txtUsuario.Text;

            // Desabilita e habilita os campos apropriados
            txtServidorIP.Enabled = false;
            txtUsuario.Enabled = false;
            txtMensagem.Enabled = true;
            btnEnviar.Enabled = true;
            btnConectar.Text = "Desconectado";

            // Envia o nome do usuário ao servidor
            stwEnviador = new StreamWriter(tcpServidor.GetStream());
            stwEnviador.WriteLine(txtUsuario.Text);
            stwEnviador.Flush();

            //Inicia a thread para receber mensagens e nova comunicação
            mensagemThread = new Thread(new ThreadStart(RecebeMensagens));
            mensagemThread.Start();
        }

O endereço IP é analisado a partir do TextBox em um objeto IPAddress, e então abrimos uma conexão TCP para esse endereço. A porta é 2502, mas você pode usar qualquer porta disponível. Em seguida, preparar os controles no formulário, desativando alguns e habilitando outros.

Alteramos também a legenda de btnConetar para exibir a mensagem "Desconectado". Através de um Stream, informamos ao servidor o nome de usuário, e imediatamente depois que começamos uma nova thread que chama o método RecebeMensagens() que irá ouvir as mensagens recebidas a partir de agora.

Colocando isso em uma thread separada, a nossa aplicação é totalmente utilizável, enquanto ele está escutando as mensagens do servidor e mantem a conexão ativa.

5- Código da rotina RecebeMensagens

  private void RecebeMensagens()
        {
            // recebe a resposta do servidor
            strReceptor = new StreamReader(tcpServidor.GetStream());
            string ConResposta = strReceptor.ReadLine();
            // Se o primeiro caracater da resposta é 1 a conexão foi feita com sucesso
            if (ConResposta[0] == '1')
            {
                // Atualiza o formulário para informar que esta conectado
                this.Invoke(new AtualizaLogCallBack(this.AtualizaLog), new object[] { "Conectado com sucesso!" });
            }
            else // Se o primeiro caractere não for 1 a conexão falhou
            {
                string Motivo = "Não Conectado: ";
                // Extrai o motivo da mensagem resposta. O motivo começa no 3o caractere
                Motivo += ConResposta.Substring(2, ConResposta.Length - 2);
                // Atualiza o formulário como o motivo da falha na conexão
                this.Invoke(new FechaConexaoCallBack(this.FechaConexao), new object[] { Motivo });
                // Sai do método
               return;
            }

            // Enquanto estiver conectado le as linhas que estão chegando do servidor
            while (Conectado)
            {
                // exibe mensagems no Textbox
                this.Invoke(new AtualizaLogCallBack(this.AtualizaLog), new object[] { strReceptor.ReadLine() });
            }
        }

Um novo leitor dee stream está ligado ao cliente TCP. Ele vai ouvir as mensagens recebidas. Mas antes de tudo, lemos a primeira linha vindo do servidor. O motivo é que sabemos que a primeira linha contém uma resposta nos dizendo se estamos ou não conectados com êxito.

Há duas razões para a conexão falhar: 1: usar um nome de usuário já em uso ou 2: usar Administrador como nome de usuário.

O primeiro caractere da resposta dada pelo servidor nos diz o seguinte:

Quando a conexão falha, lemos também o porquê da falha, que inicia no terceiro caractere da mensagem, já que o primeiro é o número, e o segundo é um caractere de pipe. Por exemplo: 0|Nome de Usuário já está em uso. Assim se o primeiro caractere não é um 1 lemos a cadeia que começa no caractere 3 e termina no final da linha para obter essa informação.

O método this.Invoke(...) chama o formulário para se atualizar. Não podemos atualizar diretamente os elementos do formulário nos mesmos a partir deste método porque ele esta em uma thread distinta (lembre-se nós o chamamos usando ThreadStart ()) e operações entre threads são ilegais.

Finalmente, o loop while (Conectado) continua chamando o método strReceptor.ReadLine() que verifica as mensagens recebidas do servidor.

Em seguida, temos o método que continuamos chamando this.Invoke(...); tudo que ele faz é atualizar o TextBox txtLog com a mensagem mais recente.

6- Código da rotina AtualizaLog

     private void AtualizaLog(string strMensagem)
        {
            // Anexa texto ao final de cada linha
            txtLog.AppendText(strMensagem + "\r\n");
        }

7- Código do evento Click do botão Enviar

      private void btnEnviar_Click(object sender, System.EventArgs e)
        {
            EnviaMensagem();
        }

8- O código evento KeyPress da caixa de texto Mensagem:

      private void txtMensagem_KeyPress(object sender, KeyPressEventArgs e)
        {
            // Se pressionou a tecla Enter
            if (e.KeyChar == (char)13)
            {
                EnviaMensagem();
            }
        }

Para enviar uma mensagem usamos o evento click do botão Enviar ou quando a tecla Enter for pressionada. Nestes casos chamamos a rotina EnviaMensagem() conforme os itens 7 e 8 acima.

9- Código da rotina EnviaMensagem

   // Envia a mensagem para o servidor
        private void EnviaMensagem()
        {
            if (txtMensagem.Lines.Length >= 1)
            {   //escreve a mensagem da caixa de texto
                stwEnviador.WriteLine(txtMensagem.Text);
                stwEnviador.Flush();
                txtMensagem.Lines = null;
            }
            txtMensagem.Text = "";
        }

A rotina EnviaMenagem() apenas verifica se o número de linhas é maior ou igual a 1, e então escreve a linha para a conexão TCP através do objeto StreamWriter. A chamda ao método Flush() garante que as mensagens estão sendo enviadas de imediato.

10 - Código da rotina FechaConexao

  // Fecha a conexão com o servidor
        private void FechaConexao(string Motivo)
        {
            // Mostra o motivo porque a conexão encerrou
            txtLog.AppendText(Motivo + "\r\n");
            // Habilita e desabilita os controles apropriados no formulario
            txtServidorIP.Enabled = true;
            txtUsuario.Enabled = true;
            txtMensagem.Enabled = false;
            btnEnviar.Enabled = false;
            btnConectar.Text = "Conectado";

            // Fecha os objetos
            Conectado = false;
            stwEnviador.Close();
            strReceptor.Close();
            tcpServidor.Close();
        }

Para fechar a conexão chamamos o método FechaConexao e neste código temos que o formulário está sendo trazido de volta para o estado não conectado, e a conexão TCP e os streams estão sendo fechados. Mas o que acontece se o usuário não clicar para desconectar mas apenas fechar o aplicativo enquanto a conexão com o servidor está ativa ?

Nós certamente não queremos deixar a conexão aberta como esta até que ele morra por conta própria. Felizmente existe o evento ApplicationExit que é acionado quando o aplicativo for fechado, e é aí que podemos fechar a nossa conexão. Para tratar este evento vamos alterar o código do construtor do formulário conforme abaixo:

11- Definição do tratamento do evento ApplicationOnExit

  public frmCliente()
     {
           // Na saida da aplicação : desconectar
         Application.ApplicationExit += new EventHandler(OnApplicationExit);
           InitializeComponent();
   }

12- Código do evento OnApplicationExit()

    // O tratador de evento para a saida da aplicação
        public void OnApplicationExit(object sender, EventArgs e)
        {
            if (Conectado == true)
            {
                // Fecha as conexões, streams, etc...
                Conectado = false;
                stwEnviador.Close();
                strReceptor.Close();
                tcpServidor.Close();
            }
        }

O código do evento OnApplicationExit() vai ocorrer quando o usuário fechar o aplicativo diretamente com a conexão ativa e vai verificar se estamos conectados e fechar as conexões e os recursos usados

Pegue a primeira parte do projeto completo aqui: ChatCliente.zip

Na segunda parte do artigo irei mostrar a criação do Servidor que irá receber e distribuir as mensagens recebidas: C # - Criando um Chat - Parte 2 - O Servidor (inativo)

"Meus filhinhos , estas coisas vos escrevo, para que não pequeis; e se alguém pecar, temos um advogado para com o Pai, Jesus Cristo, o justo." I João 2:1

Referências:


José Carlos Macoratti