Chat Multi Usuários com Socket
Encontrei esse chat multi usuários que tive que fazer na época da faculdade, acho que na matéria de sistemas distribuídos. Vou disponibilizar as duas classes, a classe servidor e a classe cliente.
Para usá-lo, primeiro é necessário rodar a classe servidor, para então rodar a classe cliente. Como é multi usuário, por que tem implementação de Threads, é possível rodar varias instancias da classe cliente.
Na classe cliente é possível configurar a máquina que está rodando o servidor, está por padrão como localhost
, mas caso queira testar em alguma rede, é só alterar para o IP da máquina em que está o servidor.
As explicações estão comentadas linha a linha no código.
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Vector; public class ServidorSocket extends Thread { // Parte que controla as conexões por meio de threads. private static Vector CLIENTES; // socket deste cliente private Socket conexao; // nome deste cliente private String nomeCliente; // lista que armazena nome de CLIENTES private static List LISTA_DE_NOMES = new ArrayList(); // construtor que recebe o socket deste cliente public ServidorSocket(Socket socket) { this.conexao = socket; } //testa se nomes são iguais, se for retorna true public boolean armazena(String newName){ // System.out.println(LISTA_DE_NOMES); for (int i=0; i < LISTA_DE_NOMES.size(); i++){ if(LISTA_DE_NOMES.get(i).equals(newName)) return true; } //adiciona na lista apenas se não existir LISTA_DE_NOMES.add(newName); return false; } //remove da lista os CLIENTES que já deixaram o chat public void remove(String oldName) { for (int i=0; i< LISTA_DE_NOMES.size(); i++){ if(LISTA_DE_NOMES.get(i).equals(oldName)) LISTA_DE_NOMES.remove(oldName); } } public static void main(String args[]) { // instancia o vetor de CLIENTES conectados CLIENTES = new Vector(); try { // cria um socket que fica escutando a porta 5555. ServerSocket server = new ServerSocket(5555); System.out.println("ServidorSocket rodando na porta 5555"); // Loop principal. while (true) { // aguarda algum cliente se conectar. // A execução do servidor fica bloqueada na chamada do método accept da // classe ServerSocket até que algum cliente se conecte ao servidor. // O próprio método desbloqueia e retorna com um objeto da classe Socket Socket conexao = server.accept(); // cria uma nova thread para tratar essa conexão Thread t = new ServidorSocket(conexao); t.start(); // voltando ao loop, esperando mais alguém se conectar. } } catch (IOException e) { // caso ocorra alguma excessão de E/S, mostre qual foi. System.out.println("IOException: " + e); } } // execução da thread public void run() { try { // objetos que permitem controlar fluxo de comunicação que vem do cliente BufferedReader entrada = new BufferedReader(new InputStreamReader(this.conexao.getInputStream())); PrintStream saida = new PrintStream(this.conexao.getOutputStream()); // recebe o nome do cliente this.nomeCliente = entrada.readLine(); //chamada ao metodo que testa nomes iguais if (armazena(this.nomeCliente)){ saida.println("Este nome ja existe! Conecte novamente com outro Nome."); CLIENTES.add(saida); //fecha a conexao com este cliente this.conexao.close(); return; } else { //mostra o nome do cliente conectado ao servidor System.out.println(this.nomeCliente + " : Conectado ao Servidor!"); } //igual a null encerra a execução if (this.nomeCliente == null) { return; } //adiciona os dados de saida do cliente no objeto CLIENTES CLIENTES.add(saida); //recebe a mensagem do cliente String msg = entrada.readLine(); // Verificar se linha é null (conexão encerrada) // Se não for nula, mostra a troca de mensagens entre os CLIENTES while (msg != null && !(msg.trim().equals(""))) { // reenvia a linha para todos os CLIENTES conectados sendToAll(saida, " escreveu: ", msg); // espera por uma nova linha. msg = entrada.readLine(); } //se cliente enviar linha em branco, mostra a saida no servidor System.out.println(this.nomeCliente + " saiu do bate-papo!"); //se cliente enviar linha em branco, servidor envia // mensagem de saida do chat aos CLIENTES conectados sendToAll(saida, " saiu", " do bate-papo!"); //remove nome da lista remove(this.nomeCliente); //exclui atributos setados ao cliente CLIENTES.remove(saida); //fecha a conexao com este cliente this.conexao.close(); } catch (IOException e) { // Caso ocorra alguma excessão de E/S, mostre qual foi. System.out.println("Falha na Conexao... .. ."+" IOException: " + e); } } // enviar uma mensagem para todos, menos para o próprio public void sendToAll(PrintStream saida, String acao, String msg) throws IOException { Enumeration e = CLIENTES.elements(); while (e.hasMoreElements()) { // obtém o fluxo de saída de um dos CLIENTES PrintStream chat = (PrintStream) e.nextElement(); // envia para todos, menos para o próprio usuário if (chat != saida) { chat.println(this.nomeCliente + acao + msg); } } } }
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; public class ClienteSocket extends Thread { // parte que controla a recepção de mensagens do cliente private Socket conexao; // construtor que recebe o socket do cliente public ClienteSocket(Socket socket) { this.conexao = socket; } public static void main(String args[]) { try { //Instancia do atributo conexao do tipo Socket, // conecta a IP do Servidor, Porta Socket socket = new Socket("127.0.0.1", 5555); //Instancia do atributo saida, obtem os objetos que permitem // controlar o fluxo de comunicação PrintStream saida = new PrintStream(socket.getOutputStream()); BufferedReader teclado = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Digite seu nome: "); String meuNome = teclado.readLine(); //envia o nome digitado para o servidor saida.println(meuNome.toUpperCase()); //instancia a thread para ip e porta conectados e depois inicia ela Thread thread = new ClienteSocket(socket); thread.start(); //Cria a variavel msg responsavel por enviar a mensagem para o servidor String msg; while (true) { // cria linha para digitação da mensagem e a armazena na variavel msg System.out.print("Mensagem > "); msg = teclado.readLine(); // envia a mensagem para o servidor saida.println(msg); } } catch (IOException e) { System.out.println("Falha na Conexao... .. ." + " IOException: " + e); } } // execução da thread public void run() { try { //recebe mensagens de outro cliente através do servidor BufferedReader entrada = new BufferedReader(new InputStreamReader(this.conexao.getInputStream())); //cria variavel de mensagem String msg; while (true) { // pega o que o servidor enviou msg = entrada.readLine(); //se a mensagem contiver dados, passa pelo if, // caso contrario cai no break e encerra a conexao if (msg == null) { System.out.println("Conexão encerrada!"); System.exit(0); } System.out.println(); //imprime a mensagem recebida System.out.println(msg); //cria uma linha visual para resposta System.out.print("Responder > "); } } catch (IOException e) { // caso ocorra alguma exceção de E/S, mostra qual foi. System.out.println("Ocorreu uma Falha... .. ." + " IOException: " + e); } } }
Post editado a partir daqui [Edição em 28/03/2011 19:42]
Um dos participantes do blog pediu um exemplo diferente da classe Servidor
postada na listagem 1. Ele queria saber se era possível enviar uma mensagem para um usuário reservadamente, e não para todos como a classe anterior fazia. Fiz então alguma modificações na classe Servidor
para incluir esta função. Não sei se existe alguma maneira diferente, talvez exista, mas esta que criei se mostrou bem útil ao propósito. Na classe Servidor
original eu tinha criado uma lista do tipo Vector
, que gravava todos os clientes que estavam conectados ao servidor. Foi nesse ponto que fiz a primeira modificação.
Alterei a variável do tipo Vector
para o tipo Map
, onde a chave será o nome do cliente e o valor será o objeto PrintStream
. Desse modo, eu posso guardar o cliente conectado na lista, e ao mesmo tempo seu objeto PrintStream
, e então terei como saber qual cliente pertence a qual objeto.
Como tenho um método que válida o usuário por nome, não deixando alguém se conectar com um mesmo nome que já está registrado no Servidor
, esse modo funciona muito bem.
Quando um usuário estiver conectado ao Servidor
e quizer enviar uma mensagem para todos participantes, é só escrever a mensagem e enviar. Porém, caso queira enviar a mensagem de forma reservada à um único participante deve escrever a mensagem e no final digitar o caracter dois pontos :
e o nome do cliente para quem irá enviar a mensagem, então ficaria assim > testando socket:marcio
Dessa forma é possivel usar o método String.split()
e separ em um array a mensagem do nome do cliente. Na 1ª posição do array, posição = 0, ficará armazenada a mensagem e na 2ª posição, posição = 1, ficará armazenado o nome do cliente.
Em seguida, no método send()
, que na classe Servidor
anterior se chamava sendToAll()
, é necessário fazer dois testes.
O primeiro testamos se o array possui tamanho maior que 1, se possuir é por que a mensagem será enviada de forma reservada. Se não possuir, é por que a mensagem não será reservada. Caso seja reservada, fazemos um for na lista Map
, que substituiu a Vector
, e procuramos pela chave que possua o mesmo nome que está armazenado na 2ª posição do array. Quando encontrada, enviamos a mensagem para ele e saimos do loop.
Na listagem 3, segue a classe Servidor com estas modificações.
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.*; public class ServidorSocket extends Thread { private static MapMAP_CLIENTES; private Socket conexao; private String nomeCliente; private static List LISTA_DE_NOMES = new ArrayList (); public ServidorSocket(Socket socket) { this.conexao = socket; } public boolean armazena(String newName) { for (int i = 0; i < LISTA_DE_NOMES.size(); i++) { if (LISTA_DE_NOMES.get(i).equals(newName)) return true; } LISTA_DE_NOMES.add(newName); return false; } public void remove(String oldName) { for (int i = 0; i < LISTA_DE_NOMES.size(); i++) { if (LISTA_DE_NOMES.get(i).equals(oldName)) LISTA_DE_NOMES.remove(oldName); } } public static void main(String args[]) { MAP_CLIENTES = new HashMap (); try { ServerSocket server = new ServerSocket(5555); System.out.println("ServidorSocket rodando na porta 5555"); while (true) { Socket conexao = server.accept(); Thread t = new ServidorSocket(conexao); t.start(); } } catch (IOException e) { System.out.println("IOException: " + e); } } public void run() { try { BufferedReader entrada = new BufferedReader(new InputStreamReader(this.conexao.getInputStream())); PrintStream saida = new PrintStream(this.conexao.getOutputStream()); this.nomeCliente = entrada.readLine(); if (armazena(this.nomeCliente)) { saida.println("Este nome ja existe! Conecte novamente com outro Nome."); this.conexao.close(); return; } else { //mostra o nome do cliente conectado ao servidor System.out.println(this.nomeCliente + " : Conectado ao Servidor!"); //Quando o cliente se conectar recebe todos que estão conectados saida.println("Conectados: " + LISTA_DE_NOMES.toString()); } if (this.nomeCliente == null) { return; } //adiciona os dados de saida do cliente no objeto MAP_CLIENTES //A chave será o nome e valor o printstream MAP_CLIENTES.put(this.nomeCliente, saida); String[] msg = entrada.readLine().split(":"); while (msg != null && !(msg[0].trim().equals(""))) { send(saida, " escreveu: ", msg); msg = entrada.readLine().split(":"); } System.out.println(this.nomeCliente + " saiu do bate-papo!"); String[] out = {" do bate-papo!"}; send(saida, " saiu", out); remove(this.nomeCliente); MAP_CLIENTES.remove(this.nomeCliente); this.conexao.close(); } catch (IOException e) { System.out.println("Falha na Conexao... .. ." + " IOException: " + e); } } /** * Se o array da msg tiver tamanho igual a 1, então envia para todos * Se o tamanho for 2, envia apenas para o cliente escolhido */ public void send(PrintStream saida, String acao, String[] msg) { out: for (Map.Entry cliente : MAP_CLIENTES.entrySet()) { PrintStream chat = cliente.getValue(); if (chat != saida) { if (msg.length == 1) { chat.println(this.nomeCliente + acao + msg[0]); } else { if (msg[1].equalsIgnoreCase(cliente.getKey())) { chat.println(this.nomeCliente + acao + msg[0]); break out; } } } } } }
Outras Referencias