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.

Listagem 1. Classe Servidor
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);
            }
        }
      }
}

 

Listagem 2. Classe Cliente
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.

Listagem 3. Classe Servidor Modificada
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 Map MAP_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

Download

Ballem

Marcio Ballem é bacharel em Sistemas de Informação pelo Centro Universitário Franciscano em Santa Maria/RS. Tem experiência com desenvolvimento Delphi e Java em projetos para gestão pública e acadêmica. Possui certificação em Java, OCJP 6.

Você pode gostar...