C #
- Criando um Chat - Parte 2 - O Servidor
![]() |
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:
Na primeira parte eu criei a aplicação cliente e agora vamos criar a aplicação que atuará como servidor.
O aplicativo Chat Servidor será um pouco mais complexo do que a aplicação cliente, porque ele precisa manter informações sobre todos os clientes conectados, aguardar as mensagens de cada um e enviar mensagens de entrada para todos.
Chat - Criando a aplicação Servidor
Inicie o Visual C# 2010 Express Edition e crie um novo projeto do tipo Windows Appliation com o nome ChatServidor:
![]() |
Vamos alterar o nome do formulário form1.cs criado por padrão no projeto para frmServidor.cs e a seguir definir o leiaute conforme mostra a figura abaixo no formulário:
![]() |
Controles usados no
formulário:
|
Vamos agora ao código usado no servidor:
1- Namespaces usados:
using
System.Windows.Forms;
using System.Net;
2- Logo após a declaraçãod a classe frmServidor vamos declarar um delegate que sera usado para atualziar o TextBox - txtLog da outra thread.
private delegate void AtualizaStatusCallback(string strMensagem);
3- Código do evento Click do botão Iniciar Atendimento:
private void btnAtender_Click(object sender, System.EventArgs e)
{
// Analisa o endereço IP do servidor informado no textbox
IPAddress enderecoIP = IPAddress.Parse(txtIP.Text);
// Cria uma nova instância do objeto ChatServidor
ChatServidor mainServidor = new ChatServidor(enderecoIP);
// Vincula o tratamento de evento StatusChanged a mainServer_StatusChanged
ChatServidor.StatusChanged += new StatusChangedEventHandler(mainServer_StatusChanged);
// Inicia o atendimento das conexões
mainServidor.IniciaAtendimento();
// Mostra que nos iniciamos o atendimento para conexões
txtLog.AppendText("Monitorando as conexões...\r\n");
}
|
Estamos instanciando objetos, incluindo um objeto ChatServidor. Vamos escrever a classe ChatServidor logo a seguir, e você vai ver que ela lida com todas as conexões de entrada. Por sua vez, faremos uso de uma outra classe chamado Conexao.
A próxima coisa que faremos será criar um manipulador de eventos para o evento StatusChanged, que é um evento personalizado que vamos escrever. Ele vai nos informar quando um cliente se conecta, uma nova mensagem foi recebida, um cliente foi desconectado, etc
Finalmente, o método IniciaAtendimento diz ao objeto ChatServidor para iniciar a ouvir as conexões de entrada.
Vamos usar também um manipulador de eventos que ja usamos antes, e o outro é o método AtualizaStatus() que é chamado quando uma atualização precisa ser feita para o formulário.
Isso é necessário pois usamos Invoke() e o delegado que criamos anteriormente para fazer uma chamada na thread cruzada (ChatServidor esta em uma thread diferente):
Abaixo vemos o manipulador de eventos e o método AtualizaStatus() :
public void mainServidor_StatusChanged(object sender, StatusChangedEventArgs e)
{
// Chama o método que atualiza o formulário
this.Invoke(new AtualizaStatusCallback(this.AtualizaStatus), new object[] { e.EventMessage });
}
private void AtualizaStatus(string strMensagem)
{
// Atualiza o logo com mensagens
txtLog.AppendText(strMensagem + "\r\n");
}
|
Vamos agora criar uma classe chamada ChatServidor.cs no projeto;
No menu Project-> Add Class , selecione o template Class e informe o no me ChatServidor.cs e clique em Add;
Vamos iniciar definindo os namespaces usados nessa classe:
using
System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Collections;
A seguir vamos definir uma classe para o tratamento para o evento StatusChanged que monitora a mudança de estado relacionado as mensagens. A classe StatusChangedEventArgs :
// Trata os argumentos para o evento StatusChanged
public class StatusChangedEventArgs : EventArgs
{
// Estamos interessados na mensagem descrevendo o evento
private string EventMsg;
// Propriedade para retornar e definir um mensagem do evento
public string EventMessage
{
get { return EventMsg;}
set { EventMsg = value;}
}
// Construtor para definir a mensagem do evento
public StatusChangedEventArgs(string strEventMsg)
{
EventMsg = strEventMsg;
}
}
|
Precisamos definir o delegate StatusChangedEventHandler para tratar os parãmetros passados ao evento:
// Este delegate é necessário para especificar os parametros que estamos pasando com o nosso evento public delegate void StatusChangedEventHandler(object sender, StatusChangedEventArgs e); |
Na classe ChatServidor vamos tratar as mensagens e o usuários definindo os seguintes métodos:
temos também o evento OnStatusChanged que é usado para disparar o evento de mudança de estado.
O código completo da classe comentado segue abaixo:
| class ChatServidor { // Esta hash table armazena os usuários e as conexões (acessado/consultado por usuário) public static Hashtable htUsuarios = new Hashtable(30); // 30 usuarios é o limite definido // Esta hash table armazena os usuários e as conexões (acessada/consultada por conexão) public static Hashtable htConexoes = new Hashtable(30); // 30 usuários é o limite definido // armazena o endereço IP passado private IPAddress enderecoIP; private TcpClient tcpCliente; // O evento e o seu argumento irá notificar o formulário quando um usuário se conecta, desconecta, envia uma mensagem,etc public static event StatusChangedEventHandler StatusChanged; private static StatusChangedEventArgs e; // O construtor define o endereço IP para aquele retornado pela instanciação do objeto public ChatServidor(IPAddress endereco) { enderecoIP = endereco; } // A thread que ira tratar o escutador de conexões private Thread thrListener; // O objeto TCP object que escuta as conexões private TcpListener tlsCliente; // Ira dizer ao laço while para manter a monitoração das conexões bool ServRodando = false; // Inclui o usuário nas tabelas hash public static void IncluiUsuario(TcpClient tcpUsuario, string strUsername) { // Primeiro inclui o nome e conexão associada para ambas as hash tables ChatServidor.htUsuarios.Add(strUsername, tcpUsuario); ChatServidor.htConexoes.Add(tcpUsuario, strUsername); // Informa a nova conexão para todos os usuário e para o formulário do servidor EnviaMensagemAdmin(htConexoes[tcpUsuario] + " entrou.."); } // Remove o usuário das tabelas (hash tables) public static void RemoveUsuario(TcpClient tcpUsuario) { // Se o usuário existir if (htConexoes[tcpUsuario] != null) { // Primeiro mostra a informação e informa os outros usuários sobre a conexão EnviaMensagemAdmin(htConexoes[tcpUsuario] + " saiu..."); // Removeo usuário da hash table ChatServidor.htUsuarios.Remove(ChatServidor.htConexoes[tcpUsuario]); ChatServidor.htConexoes.Remove(tcpUsuario); } } // Este evento é chamado quando queremos disparar o evento StatusChanged public static void OnStatusChanged(StatusChangedEventArgs e) { StatusChangedEventHandler statusHandler = StatusChanged; if (statusHandler != null) { // invoca o delegate statusHandler(null, e); } } // Envia mensagens administratias public static void EnviaMensagemAdmin(string Mensagem) { StreamWriter swSenderSender; // Exibe primeiro na aplicação e = new StatusChangedEventArgs("Administrador: " + Mensagem); OnStatusChanged(e); // Cria um array de clientes TCPs do tamanho do numero de clientes existentes TcpClient[] tcpClientes = new TcpClient[ChatServidor.htUsuarios.Count]; // Copia os objetos TcpClient no array ChatServidor.htUsuarios.Values.CopyTo(tcpClientes, 0); // Percorre a lista de clientes TCP for (int i = 0; i < tcpClientes.Length; i++) { // Tenta enviar uma mensagem para cada cliente try { // Se a mensagem estiver em branco ou a conexão for nula sai... if (Mensagem.Trim() == "" || tcpClientes[i] == null) { continue; } // Envia a mensagem para o usuário atual no laço swSenderSender = new StreamWriter(tcpClientes[i].GetStream()); swSenderSender.WriteLine("Administrador: " + Mensagem); swSenderSender.Flush(); swSenderSender = null; } catch // Se houver um problema , o usuário não existe , então remove-o { RemoveUsuario(tcpClientes[i]); } } } // Envia mensagens de um usuário para todos os outros public static void EnviaMensagem(string Origem, string Mensagem) { StreamWriter swSenderSender; // Primeiro exibe a mensagem na aplicação e = new StatusChangedEventArgs(Origem + " disse : " + Mensagem); OnStatusChanged(e); // Cria um array de clientes TCPs do tamanho do numero de clientes existentes TcpClient[] tcpClientes = new TcpClient[ChatServidor.htUsuarios.Count]; // Copia os objetos TcpClient no array ChatServidor.htUsuarios.Values.CopyTo(tcpClientes, 0); // Percorre a lista de clientes TCP for (int i = 0; i < tcpClientes.Length; i++) { // Tenta enviar uma mensagem para cada cliente try { // Se a mensagem estiver em branco ou a conexão for nula sai... if (Mensagem.Trim() == "" || tcpClientes[i] == null) { continue; } // Envia a mensagem para o usuário atual no laço swSenderSender = new StreamWriter(tcpClientes[i].GetStream()); swSenderSender.WriteLine(Origem + " disse: " + Mensagem); swSenderSender.Flush(); swSenderSender = null; } catch // Se houver um problema , o usuário não existe , então remove-o { RemoveUsuario(tcpClientes[i]); } } } public void IniciaAtendimento() { try { // Pega o IP do primeiro dispostivo da rede IPAddress ipaLocal = enderecoIP; // Cria um objeto TCP listener usando o IP do servidor e porta definidas tlsCliente = new TcpListener(ipaLocal, 2502); // Inicia o TCP listener e escuta as conexões tlsCliente.Start(); // O laço While verifica se o servidor esta rodando antes de checar as conexões ServRodando = true; // Inicia uma nova tread que hospeda o listener thrListener = new Thread(MantemAtendimento); thrListener.Start(); } catch (Exception ex) { throw ex; } } private void MantemAtendimento() { // Enquanto o servidor estiver rodando while (ServRodando == true) { // Aceita uma conexão pendente tcpCliente = tlsCliente.AcceptTcpClient(); // Cria uma nova instância da conexão Conexao newConnection = new Conexao(tcpCliente); } } } |
A classe Conexao trata as conexões dos usuários e possui os métodos:
O código da classe é visto a seguir:
// Ocorre quando um novo cliente é aceito
private void AceitaCliente()
{
srReceptor = new System.IO.StreamReader(tcpCliente.GetStream());
swEnviador = new System.IO.StreamWriter(tcpCliente.GetStream());
// Lê a informação da conta do cliente
usuarioAtual = srReceptor.ReadLine();
// temos uma resposta do cliente
if (usuarioAtual != "")
{
// Armazena o nome do usuário na hash table
if (ChatServidor.htUsuarios.Contains(usuarioAtual) == true)
{
// 0 => significa não conectado
swEnviador.WriteLine("0|Este nome de usuário já existe.");
swEnviador.Flush();
FechaConexao();
return;
}
else if (usuarioAtual == "Administrator")
{
// 0 => não conectado
swEnviador.WriteLine("0|Este nome de usuário é reservado.");
swEnviador.Flush();
FechaConexao();
return;
}
else
{
// 1 => conectou com sucesso
swEnviador.WriteLine("1");
swEnviador.Flush();
// Inclui o usuário na hash table e inicia a escuta de suas mensagens
ChatServidor.IncluiUsuario(tcpCliente, usuarioAtual);
}
}
else
{
FechaConexao();
return;
}
//
try
{
// Continua aguardando por uma mensagem do usuário
while ((strResposta = srReceptor.ReadLine()) != "")
{
// Se for inválido remove-o
if (strResposta == null)
{
ChatServidor.RemoveUsuario(tcpCliente);
}
else
{
// envia a mensagem para todos os outros usuários
ChatServidor.EnviaMensagem(usuarioAtual, strResposta);
}
}
}
catch
{
// Se houve um problema com este usuário desconecta-o
ChatServidor.RemoveUsuario(tcpCliente);
}
}
|
Para podermos avaliar a nossa aplicação primeiro devemos executar o servidor e iniciar o seu atendimento deixando pronto para escutar e tratar as requisições dos clientes. A seguir executaremos duas instâncias da aplicação ChatCliente para simular a conexão de dois clientes simultâneos. Como estou executando o projeto em minha máquina local vou usar o ip 127.0.0.1. A porta definida é 2502. A seguir vemos os resultados para esta simulação:
![]() |
Podemos observar a conexão e a troca de mensagens entre os usuários Macoratti e Convidado.
Dessa forma lancei os fundamentos de uma aplicação Chat Multithread. Ela pode ser melhorada em diversos aspectos tornando-a mais flexível, configurável e robusta.
Pegue o projeto completo
aqui:
ChatServidor.zip
"Porque o salário do pecado é a morte,
mas o dom gratuito de Deus é a vida eterna, por Cristo Jesus Nosso Senhor."
Romanos 6-23
Referências: