Introdução às Redes de Computadores/Bate-papo em grupo

Clientes se conectarão ao servidor de chat, em uma determinada porta TCP. O servidor deverá estar ativo para atender às requisições de conexão. Após esse estabelecimento, os usuários entrarão numa sala, onde se encontrarão com outros usuários que estejam conectados. Poderá ser realizado troca de mensagens dentro da sala (para que todos vejam), mensagem privativa (para um determidado usuário), e se possível, transferência de arquivo. Em essência, o protocolo será parecido com o protocolo IRC (Internet Relay Chat).

A linguagem de programação será JAVA, e suas APIs serão utilizadas nesse desenvolvimento.

As fontes de pesquisas serão livros (como Tanenbaum, Kurose, Deitel) e documentações e códigos encontrados na Web.

O diagrama básico da arquitetura do protocolo será:


\ Servidor(chat)<--\<------->Cliente-1
                    \            ^
                     \           |
                      \          v
                       \<------>Cliente-2
                        \          ^
                         \         |
                          \        v
                           \<----->Cliente-3
                            \          ^
 Legenda                     \         |
 <---> Comunicação            \        v
         Direta                \<---->Cliente-4
                                \        .
   ^  Comunicação                \       .
   |    Indireta                  \      .       
   V                               \<--->Cliente-n

Código do Programa

editar

O código do programa foi desenvolvido em Java, por ser uma linguagem simples e por poder rodar em diferentes plataformas.

Como dito, o projeto divide-se em duas partes: o lado Cliente e o lado Servidor. O servidor é responsável por abrir uma conexão numa determinada porta, escolhida pelo usuário do servidor (chamado de administrador nesse caso). O administrador executa o servidor, que aguarda conexões de usuários. O serviço pode ser finalizado quando o administrador assim decidir. O usuário do lado cliente deve informar o endereço do servidor (seja o IP ou o nome, via DNS) e a porta em que ele está oferecendo o serviço, além de escolher um apelido (nickname).

As telas do programa foram criadas utilizando a API Thinlet, que gera janelas amigáveis a partir de parâmetros XML. Os códigos importam o arquivo XML com os dados da janela, e o Thinlet se encarrega de montá-las para visualização. A interface utilizada para a criação dessas janelas para o Thinlet foi o ThinG GUI Editor.

FSM do Bate-Papo em grupo

editar

Abaixo está especificado o FSM simplificado do método apresentado:

 

Diagrama de Mensagens

editar

Podemos ver abaixo um exemplo de uma comunicação entre cliente e servidor. Podemos observar que ao Cliente analisado sair, a mensagem de saída é rotornada pelo servidor apenas aos clientes restantes.

 

Os códigos-fonte referentes ao arquivo XML utilizado por cada classe e as respectivas classes são apresentadas a seguir:

Lado Servidor

editar

Essas classes ficam do lado servidor, devendo ser executados pelo administrador da Sala de Bate-Papos.

Classe Servir

editar

Essa classe é resposável por apresentar uma tela ao administrador para que ele escolha a porta em que o serviço será oferecido. O arquivo servir.xml cria a tela apresentada ao administrador, que escolhe a porta e envia esse parâmetro à classe Server.java.

servir.xml

editar
   <?xml version="1.0" encoding="ISO-8859-1"?>
   <dialog border="true" bottom="20" columns="3" gap="10" left="30" name="servir" right="30"
   text="Servidor" top="20">
   <label halign="center" text="Porta"/>
   <textfield colspan="2" name="porta"/>
   <button action="servir(0)" colspan="2" mnemonic="0" text="Servir" type="default"/>
   <button action="servir(1)" halign="left" mnemonic="0" text="Cancelar" type="cancel"/>
   </dialog>


Servir.java

editar
   package chat;
   import thinlet.*;
   import java.io.IOException.*;
  /**
   * Essa classe é responsável pelo lado do servidor. Ela cria a janela para
   * escolha da porta em que irá rodar o servidor de chat ENNN. É gerada uma tela
   * criada pelo thinlet, que irá gerar uma instância da classe Server. Será
   * considerado que o usuário dessa classe é o administrador do servidor.
   * @author Alan Caio Rodrigues Marques, Lauro Ramon Gomides,
   *  Pablo Cavalcante Nunes, Pedro Henrique da Silva Palhares, Roberto Alexandre
   *  Araújo Ribeiro.
   */
  public class Servir extends Thinlet {
   /**
    * O construtor da classe apenas lê o arquivo xml com as informações da
    * janela para o thinlet gerá-la.
    */
   public Servir() {
       try {
           // Pega o código com informações sobre a janela do servidor.
           add(parse("xml/servir.xml"));
       } catch (Exception e) {
           e.printStackTrace();
           System.out.println(e.toString());
       }
   }
   /**
    * Esse método inicia o servidor na porta escolhida pelo administrador.
    */
   public void startserver() {
       try {
           // Pega a porta digitada no campo "Porta" pelo administrador.
           int porta = Integer.parseInt(getString(find("porta"), "text"));
           // cria uma instância do servidor na dada porta.
           Server server = new Server(porta);
           // remove a tela inicial do servidor.
           remove(find("servir"));
           // cria uma nova tela, com a opção de parar o serviço.
           add(parse("xml/serverok.xml"));
       } catch (Exception e) {
           e.printStackTrace();
           System.out.println(e.toString());
       }
   }
   /**
    * Método que inicia a tela do servidor para o administrador.
    * @param args Argumentos para o programa (não utilizado).
    * @throws java.lang.Exception Caso haja algum erro, reportá-lo.
    */
   public static void main(String args[]) throws Exception {
       // Cria uma tela thinlet com os parâmetros escolhidos no cosntrutor.
       new FrameLauncher("Servidor", new Servir(), 200, 115);
   }
   /**
    * Esse método é chamado pela interface com o administrador. Cada opção
    * escolhida gera uma ação.
    * @param value Valor da opção escolhida pelo administrador na interface.
    * @throws java.lang.Exception Caso haja algum erro, reportá-lo.
    */
   public void servir(int value) throws Exception {
       switch (value) {
           case 0:
               // escolhida a opção: "Servir".
               // então, iniciar o servidor de chat.
               startserver();
               break;
           case 1:
               // escolhida a opção: Cancelar
               // então, fechar o programa.
               System.exit(0);
               break;
           default:
               break;
          }
      }
  }

Classe Server

editar

Essa classe é a resposável por iniciar o serviço do chat. Ela cria o socket para comunicação com o cliente. Ela também envia as mensagens passadas para as threads clientes.

serverok.xml

editar
  <?xml version="1.0" encoding="ISO-8859-1"?>
  <dialog bottom="35" colspan="2" gap="10" left="30" right="30" text="Servidor" top="35">
      <label colspan="2" text="Servidor iniciado"/>
      <button action="servir(1)" colspan="2" text="Fechar" type="default"/>
  </dialog>


Server.java

editar
  package chat;
  import java.net.ServerSocket;
  import java.util.HashMap;
  import java.util.ArrayList;
  /**
   * Essa classe é a "engine" do servidor. Ela cria o socket para a comunicação. 
   * Também contém os métodos que enviam mensagens públicas, mensagens privadas e
   * a lista dos usuários on-line.
   * @author Alan Caio Rodrigues Marques, Lauro Ramon Gomides,
   *  Pablo Cavalcante Nunes, Pedro Henrique da Silva Palhares, Roberto Alexandre
   *  Araújo Ribeiro.
   */
  public class Server {
      /**Os sockets para a comunicação entre cliente e servidor.*/
      private ServerSocket serversocks;
      /**A porta em que será criado o socket.*/
      private int port;
      /**Contém a lista de usuários e threads ativas.*/
      private HashMap<String, Thread> map;
      /**Contém a lista de usuários online.*/
      private ArrayList<String> nicks;
      /**O construtor da classe cria um novo socket de comunicação e uma thread
       * para servir clientes. Também cria uma lista que receberá essas threads
       * (com a chave sendo os nicks dos usuários) e a própria lista de usuários
       * que entrem no chat.
       */
      public Server(int port) {
          try {
              // pega a porta escolhida.
              this.port = port;
              // cria o socket naquela porta.
              serversocks = new ServerSocket(port);
              // inicia uma thread do servidor.
              new Threadserver(this).start();
              // cria a lista de threads ativas.
              map = new HashMap<String, Thread>();
              // cria a lista de usuários.
              nicks = new ArrayList<String>();
          } catch (Exception e) {
              e.printStackTrace();
              System.out.println(e.toString());
          }
      }
      /**
       * Esse método adiciona na lista de threads ativas determinada thread
       * com o nick do usuário como chave.
       * @param key O nick do usuário daquela thread.
       * @param value A thread que está ativa para aquele usuário.
       */
      public void putmap(String key, Thread value) {
          map.put(key, value);
      }
      /**
       * Esse método remove determinada thread da lista de thread. É chamado
       * quando algum usuário sai do chat.
       * @param key O nick do usuário que saiu do chat.
       */
      public void delmap(String key) {
          map.remove(key);
      }
      /**
       * Remove o nick do usuário da lista de nicks ativos do servidor.
       * @param nick O nick a ser removido da lista.
       */
      public void delnick(String nick) {
          for (Iterator<String> it = nicks.iterator(); it.hasNext(); ) {
              if (it.next().equals(nick)) {
                  it.remove();
              }
          }
      }
      /**
       * Adiciona determinado nick (usuário) à lista de nicks ativos. É chamado
       * quando um novo usuário entra no chat.
       * @param nick O nickname do usuário que entrou no chat.
       */
      public void putnick(String nick) {
          nicks.add(nick);
      }
  
      /**
       * Retorna a lista de nicks ativos no servidor.
       * @return Uma lista com os nicks de usuários ativos no servidor, separa
       * dos com vírgula (",").
       */
      public String getNicks() {
          int count;
          String nick = "";
          if (nicks.size() > 0) {
              nick = nicks.get(0);
              for (count = 1; count < nicks.size(); count++) {
                  nick += "," + nicks.get(count);
              }
          }
          return nick;
      }
      /**
       * Retorna o socket em que o servidor está rodando.
       * @return O socket em que o servidor está ativo.
       */
      public ServerSocket getServer() {
          return serversocks;
      }
      /**
       * Finaliza a seção dos sockets do servidor.
       * @throws java.lang.Exception
       */
      public void disconnect() throws Exception {
          if (serversocks.isBound()) {
              serversocks.close();
          }
      }
      /**
       * Envia mensagem para todos os usuários. No caso, envia a mensagem
       * para todas as threads ativas (clientes on-line).
       * @param msg A mensagem a ser escrita para os clientes.
       */
      public void sendall(String msg) {
          Threadserver sock;
          for (String x : nicks) {
              sock = (Threadserver) map.get(x);
              sock.write(msg);
          }
      }
       /**
       * Envia uma mensagem apenas para determinado usuário. No caso, envia
       * somente à thread referente àquele usuário.
       * @param nick O nickname do usuário que receberá a mensagem.
       * @param msg A mensagem a ser enviada àquele usuário.
       */
      public void sendto(String nick, String msg) {
          Threadserver sock = (Threadserver) map.get(nick);
          sock.write(msg);
      }
  }

Classe ThreadServer

editar

Essa classe contém as mensagens de protocolo interpretadas pelo servidor. Ela realiza a comunicação direta com os clientes, criando uma thread para cada cliente conectado.

ThreadServer.java

editar
 package chat;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.util.StringTokenizer;
 /**
  * Essa classe cria uma thread para cada usuário on-line no servidor de chat.
  * Ela faz a troca de mensagens segundo um protocolo criado para o próprio
  * chat ENNN.
  * @author Alan Caio Rodrigues Marques, Lauro Ramon Gomides,
  *  Pablo Cavalcante Nunes, Pedro Henrique da Silva Palhares, Roberto Alexandre
  *  Araújo Ribeiro.
  */
 public class Threadserver extends Thread {
     /**Recebe o servidor que está ativo.*/
     private Server server;
     /**Recebe o socket aberto pelo servidor.*/
     private ServerSocket serversocks;
     /**Recebe o socket aberto no lado cliente.*/
     private Socket socks;
     /**Recebe os dados de entrada no socket do servidor.*/
     private BufferedReader input;
     /**Recebe os dados de saída do socket do servidor.*/
     private PrintWriter output;
     /**Recebe um valor de nickname de usuário.*/
     private String nickname;
     /**
      * Cria uma nova thread com o nome "Server" e pega informações do servidor,
      * como o servidor ativo e seu socket.
      * @param server O servidor que está ativo no momento.
      * @throws java.lang.Exception Caso haja uma exceção, reportá-la.
      */
     public Threadserver(Server server) throws Exception {
         super("Server");
         this.server = server;
         serversocks = server.getServer();
         nickname = "";
     }
     /**
      * Esse método sobrescreve o método Run das threads. Aqui é feita a troca
      * de mensagens entre cliente e servidor. Há três tipos de mensagens que
      * o servidor recebe: tipo NICK (entrada de um novo usuário no chat),
      * tipo MSG (mensagem a ser enviada para todos os usuários ou algum em
      * específico) e tipo CAIU (saída de determinado usuário do chat).
      */
     @Override
     public void run() {
         String inputLine, comando, nick;
         StringTokenizer tokens;
         try {
             // aceita conexões no socket ativo.
             socks = serversocks.accept();
             // recebe dados de entrada no buffer.
             input = new BufferedReader(new InputStreamReader(socks.getInputStream()));
             // cria um buffer de saída.
             output = new PrintWriter(socks.getOutputStream(), true);
             // inicia outra thread, para permitir que novos usuário entrem no chat.
             new Threadserver(server).start();
             // fica sempre aguardando alguma mensagem do cliente.
             while ((inputLine = input.readLine()) != null) {
                 // escreve a mensagem do cliente no terminal do servidor (para controle).
                 System.out.println(inputLine);
                 // separa a mensagem recebida pelo "dois pontos" (":").
                 tokens = new StringTokenizer(inputLine, ":");
                 // pega o primeiro token, no caso, o protocolo de comando.
                 comando = tokens.nextToken();
                 // caso seja um "NICK", inclui o usuário no servidor.
                 if (comando.equals("NICK")) {
                     // pega o nickname daquele usuário.
                     nickname = tokens.nextToken();
                     // envia a mensagem para todos clientes sobre o novo usuário.
                     server.sendall("NICK:" + nickname);
                     // adiciona o nick à lista de nicknames.
                     server.putnick(nickname);
                     // adiciona a thread daquele usuário à lista.
                     server.putmap(nickname, this);
                     // escreve para o novo usuário a lista de usuários ativos.
                     write("NICK:" + server.getNicks());
                     // envia a mensagem sobre o novo usuário a todos do chat.
                     server.sendall("MSG:" + nickname + " entrou na sala...");
                 }// caso o comando seja MSG, é para enviar uma mensagem.
                 else if (comando.equals("MSG")) {
                     // pega o nick do usuário que receberá a mensagem.
                     nick = tokens.nextToken();
                     // pega a mensagem digitada.
                     while (tokens.hasMoreTokens()) {
                         comando = tokens.nextToken();
                     // se a mensagem for para todos, enviá-la para todos no chat.
                     }
                     if (nick.equals("TODOS")) {
                         server.sendall("MSG:<" + nickname + "> " + comando);
                     } else { //se for para um determinado usuário, enviar para ele e para quem 
                              //andou.
                         server.sendto(nick, "MSG:<" + nickname + " enviou para " + nick + "> "
                                       + comando);
                         server.sendto(nickname, "MSG:<" + nickname + " enviou para " + nick + ">" 
                                       + comando);
                     }
                 }// Se o comando for caiu, o usuário saiu do chat.
                 else if (comando.equals("CAIU")) {
                     // remover o nock da lista de nicknames ativos.
                     server.delnick(nickname);
                     // remover a thread daquele usuário.
                     server.delmap(nickname);
                     // enviar mensagem a todos que aquele usuário saiu do chat.
                     server.sendall("MSG:" + nickname + " saiu da sala...");
                     // enviar mensagem de protocolo para os clientes que aquele usuário saiu.
                     server.sendall("EXIT:" + nickname);
                     // fecha o buffer de saída desse usuário.
                     output.close();
                     //fecha o buffer de entrada desse usuário.
                     input.close();
                     //desconecta esse usuário do chat.
                     disconnect();
                     //finaliza essa thread.
                     this.stop();
                 }
             }
         } catch (Exception e) {
             e.printStackTrace();
             System.out.println(e.toString());
         }
     }
     /**
      * Fecha o socket aberto para a thread ativa.
      * @throws java.lang.Exception Caso ocorra alguma exceção, reportá-la.
      */
     public void disconnect() throws Exception {
         if (socks != null) {
             if (socks.isConnected()) {
                 socks.close();
             }
         }
     }
     /**
      * Retorna o nickname do usuário conectado ao socket dessa thread.
      * @return O nickname desse usuário.
      */
     public String getNickname() {
         return nickname;
     }
     /**
      * Escreve a mensagem no buffer de saída do socket ativo dessa thread
      * (ou seja, somente para esse usuário).
      * @param msg A mensagem a ser escrita na saída.
      */
     public void write(String msg) {
         try {
             if (output != null) {
                 output.println(msg);
             }
         } catch (Exception e) {
             e.printStackTrace();
             System.out.println(e.toString());
         }
     }
 }

Lado Cliente

editar

Essas classes rodam do lado cliente, apresentando ao usuário uma interface simples e intuitiva de acesso ao servidor de chat.

Principal

editar

Essa é a classe pricnipal do lado cliente. Ela cria a tela inicial de interface com o usuário, assim como também faz chamada a todas as outras classes do lado cliente. Nessa tela, são apresentadas as opções de conexão e desconexão e a tela do chat que é apresentada ao usuário, com a lista dos usuários on-line e as mensagens enviadas e recebidas.

principal.xml

editar
 <?xml version="1.0" encoding="ISO-8859-1"?>
 <panel columns="6" name="principal">
     <menubar colspan="6" weightx="1">
         <menu mnemonic="0" text="Arquivo">
             <menuitem action="arquivo(0)" mnemonic="0" name="conmenu" text="Conectar"/>
             <menuitem action="arquivo(1)" enabled="false" name="descmenu" text="Desconectar"/>
             <separator/>
             <menuitem action="arquivo(2)" mnemonic="0" text="Sair"/>
         </menu>
         <menu mnemonic="1" text="Ajuda">
             <menuitem action="about()" mnemonic="0" text="Sobre"/>
         </menu>
     </menubar>
 </panel>

Principal.java

editar
 package chat;
 import thinlet.*;
 import java.io.IOException.*;
 /**
  * Essa classe faz a interface do usuário com o programa chat ENNN. Ela chama
  * todas as outras classes que fazem a comunicação e execução do programa.
  * @author Alan Caio Rodrigues Marques, Lauro Ramon Gomides,
  *  Pablo Cavalcante Nunes, Pedro Henrique da Silva Palhares, Roberto Alexandre
  *  Araújo Ribeiro.
  */
 public class Principal extends Thinlet {
     /**Recebe a instância da classe que faz a conexão com o servidor.*/
     private Conectar conectar;
     /**Recebe o cliente que se comunicará com o servidor.*/
     private Cliente cliente;
     /**Recebe a tela de chat do cliente.*/
     private Chat chat;
     /**
      * Cria a tela inicial do programa de chat ENNN.
      * @throws java.lang.Exception Caso ocorra alguma exceção, reportá-la.
      */
     public Principal() throws Exception {
         // Cria a tela segundo informações do arquivo xml.
         Object window = parse("xml/principal.xml");
         add(window);
     }
     /**
      * Define qual a tela de chat desse cliente.
      * @param chat O chat exibido para o usuário.
      */
     public void setChat(Chat chat) {
         this.chat = chat;
     }
     /**
      * Método Main que cria a tela inicial para exibição para o usuário.
      * @param args Argumentos para o programa (não usado).
      * @throws java.lang.Exception
      */
     public static void main(String[] args) throws Exception {
         new Principal();
         new ThisFrame("ENNN", new Principal(), 800, 600);
     }
     /**
      * Recebe as opções passadas pela interface com o usuário.
      * @param value
      * @throws java.lang.Exception
      */
     public void arquivo(int value) throws Exception {
         switch (value) {
             case 0:// caso escolha conectar, cria um objeto da classe Conectar.
                 conectar = new Conectar(this);
                 break;
             case 1:// Caso escolha desconectar, fecha o chat.
                 remove(find("chat"));
                 disconnect();
                 // desativa o botão Desconectar.
                 setBoolean(find("descmenu"), "enabled", false);
                 // e ativa o botão Conectar.
                 setBoolean(find("conmenu"), "enabled", true);
                 break;
             case 2:// caso escolha Sair, fecha a conexão e sai do programa.
                 disconnect();
                 System.exit(0);
                 break;
             default:
                 break;
         }
     }
     /**
      * Define qual é o objeto da classe Cliente ativo para o objeto dessa classe.
      * @param cliente O Cliente criado para essa classe.
      */
     public void setCliente(Cliente cliente) {
         this.cliente = cliente;
     }
     /**
      * Recebe a opção após o usuário definir o endereço do servidor, a porta
      * e seu nick. Então, realiza a conexão.
      * @param value O valor da opção escolhida pelo usuário.
      * @throws java.lang.Exception Caso haja algum erro, reportá-lo.
      */
     public void conectar(int value) throws Exception {
         switch (value) {  
             case 0:// caso escolha Conectar, inicia a conexão.
                 conectar.conectar();
                 break;
            case 1:// caso escolha Cancelar, fecha a janela de conexão.
                 conectar.close();
                 break;
             default:
                 break;
         }
     }
     /**
      * Envia a mensagem escrita pelo usuário ao buffer de saída (servidor).
      */
     public void sendmsg() {
         // pega o usuário selecionado como destinatário da mensagem.
         String nick = getString(getSelectedItem(find("lista")), "text");
         // pega a mensagem escrita pelo usuário.
         String msg = getString(find("msg"), "text");
         // se o destinatário for todos, enviar a mensagem, segundo o protocolo,
         // para todos.
         if (nick.equals("TODOS")) {
             cliente.write("MSG:" + nick + ":" + getString(find("msg"), "text"));
         } else {// senão, enviar, segundo o protocolo, para um determinado usuário.
             cliente.write("MSG:" + nick + ":" + getString(find("msg"), "text"));
         }
         // apaga o campo de mensagem do usuário após a mensagem enviada.
         setString(find("msg"), "text", "");
     }
     /**
      * Caso o usuário se desconecte, enviar a mensagem de que ele CAIU.
      */
     public void disconnect() {
         try {
             // envia a mensagem CAIU no buffer de saída (para o servidor).
             cliente.write("CAIU:" + cliente.getNickname());
             //remove a tela de chat da tela Principal do usuário.
             remove(find("chat"));
             // fecha o socket de comunicação.
             cliente.disconnect();
             //finaliza a thread do usuário.
             cliente.stop();
         } catch (Exception e) {
         }
     }
     /**
      * Caso o usuário clique em Sobre (na tela principal), será exibida informações
      * sobre o programa Chat ENNN.
      */
     public void about() {
         try {
             add(parse("xml/sobre.xml"));
         } catch (Exception e) {
             e.printStackTrace();
             System.out.println(e.toString());
         }
     }
     /**
      * Fecha a tela de About da janela do usuário.
      */
     public void closeabout() {
         try {
             remove(find("sobre"));
         } catch (Exception e) {
             e.printStackTrace();
             System.out.println(e.toString());
         }
     }
 }


Conectar

editar

Ao iniciar o programa, o usuário pode escolher a opção de se conectar com o servidor. Nesse momento, uma tela com a opção de escolha do endereço do servidor e a porta do serviço oferecido é mostrada, assim como o campo para digitar o nickname do usuário. Essa classe efetua a conexão com o servidor através do uso de sockets.

conectar.xml

editar
 <?xml version="1.0" encoding="ISO-8859-1"?>
 <dialog border="true" bottom="20" columns="4" gap="10" left="30" name="conectar" right="30"
                                                                       text="Conectar" top="20">
     <label text="Servidor"/>
     <textfield columns="24" name="server"/>
     <label text="Porta"/>
     <textfield name="porta"/>
     <label text="Nickname"/>
     <textfield colspan="3" name="nick"/>
     <separator colspan="4"/>
     <button action="conectar(0)" colspan="2" halign="right" mnemonic="0" name="conectarbutton"
                                                                 text="Conectar" type="default"/>
     <button action="conectar(1)" colspan="2" halign="left" mnemonic="1" name="cancelbutton" 
                                                                  text="Cancelar" type="cancel"/>
 </dialog>


Conectar.java

editar
 package chat;
 /**
  * Essa classe realiza a conexão do usuário com o servidor. Também faz ser
  * exibida a tela do chat para o usuário.
  * @author Alan Caio Rodrigues Marques, Lauro Ramon Gomides,
  *  Pablo Cavalcante Nunes, Pedro Henrique da Silva Palhares, Roberto Alexandre
  *  Araújo Ribeiro.
  */
 public class Conectar {
     /**Recebe o objeto da classe Principal para que toda janela seja criada
     na janela principal para visualização do usuário.*/
     private Principal principal;
     /**Recebe um objeto da classe Chat, com a tela de chat para o usuário.*/
     private Chat chat;
     /**
      * Esse método exibe a tela de conexão com o servidor para o usuário.
      * @param principal
      */
     public Conectar(Principal principal) {
         try {
             this.principal = principal;
             // apresenta a tela de conexão dentro da tela principal do programa.
             principal.add(principal.parse("xml/conectar.xml"));
         } catch (Exception e) {
             e.printStackTrace();
             System.out.println(e.toString());
         }
     }
     /**
      * Esse método realiza a conexão com o servidor no endereço e porta fornecidos,
      * com o nome do usuário sendo o nick escolhido.
      */
     public void conectar() {
         // pega o endereço do servidor escolhido.
         String server = principal.getString(principal.find("server"), "text");
         // pega a porta de conexão escolhida.
         int porta = Integer.parseInt(principal.getString(principal.find("porta"), "text"));
         // pega o nickname escolhido pelo usuário.
         String nick = principal.getString(principal.find("nick"), "text");
         // Cria uma tela de chat para o usuário.
         chat = new Chat(principal);
         // cria a thread do cliente que se comunicará com o servidor, trocando mensagens.
         Cliente cliente = new Cliente(server, porta, chat);
         // inicia a thread do cliente.
         cliente.start();
         // define o nickname daquele cliente.
         cliente.setNickname(nick);
         // define o chat visualizado pelo usuário na tela do programa principal.
         principal.setChat(chat);
         // define qual a thread está sendo executada no lado cliente.
         principal.setCliente(cliente);
         // aguarda até que a conexão seja efetivada para continuar o programa.
         while (!cliente.isConnected()) {
         }
         // ativa o botão Desconectar.
         principal.setBoolean(principal.find("descmenu"), "enabled", true);
         // desativa o botão Conectar.
         principal.setBoolean(principal.find("conmenu"), "enabled", false);
         // remove a tela de conexão da tela principal do programa.
         principal.remove(principal.find("conectar"));
         // escreve no buffer de saída o nickname do usuário que entrou no chat.
         cliente.write("NICK:" + nick);
     }
     /**
      * Caso o usuário clique no botão Cancelar ou feche a tela de conexão,
      * ela será removida da tela principal do programa.
      */
     public void close() {
         principal.remove(principal.find("conectar"));
     }
 }
 

Essa classe é responsável por apresentar ao usuário a tela de comunicação com os outros usuários on-line. Apresenta a lista de usuários ativos no momento e as mensagens enviadas e recebidas.

chat.xml

editar
 <?xml version="1.0" encoding="ISO-8859-1"?>
 <dialog border="true" columns="3" name="chat" text="Chat">
     <panel border="true" colspan="2" columns="3" name="chat">
         <textarea colspan="3" columns="60" editable="false" name="msgs" rows="18"/>
         <textfield colspan="2" columns="70" name="msg"/>
         <button action="sendmsg()" text="Enviar" type="default"/>
         <label/>
     </panel>
     <panel text="Nicks">
         <list name="lista" weighty="1">
             <item name="todos" selected="true" text="TODOS"/>
         </list>
     </panel>
 </dialog>

Chat.java

editar
 package chat;
 /**
  * Essa classe simplesmente é a interface do chat com o usuário.
  * Recebe os comandos e envia para a classe Principal.
  * @author Alan Caio Rodrigues Marques, Lauro Ramon Gomides,
  *  Pablo Cavalcante Nunes, Pedro Henrique da Silva Palhares, Roberto Alexandre
  *  Araújo Ribeiro.
  */
 public class Chat {
     /**A classe principal que criou essa tela de chat.*/
     private Principal principal;
     /**
      * Cria a tela de chat para o usuário dentro da tela principal do programa.
      * @param principal O objeto da classe Principal.
      */
     public Chat(Principal principal) {
         try {
             this.principal = principal;
             //cria a tela segundo o arquivo xml de chat.
             principal.add(principal.parse("xml/chat.xml"));
         } catch (Exception e) {
             e.printStackTrace();
             System.out.println(e.toString());
         }
     }
     /**
      * Adiciona determinado item (nickname) na lista de usuários que é exibida
      * ao usuário.
      * @param nick O nickname do usuário que entrou no chat.
      */
     public void additem(String nick) {
         // cria um novo item.
         Object item = Principal.create("item");
         principal.setString(item, "text", nick);
         principal.setString(item, "name", nick);
         // adiciona o nickname na lista de nicks que é exibida ao usuário.
         principal.add(principal.find("lista"), item);
     }
     /**
      * Remove determinado nickname da lista de nicks vistas pelo usuário.
      * @param item
      */
     public void removeitem(String item) {
         principal.remove(principal.find(item));
         // ao sair algum usuário, voltar o cursor de destinatário para TODOS.
         principal.setBoolean(principal.find("todos"), "selected", true);
     }
     /**
      * Adiciona as mensagems enviadas e recebidas à tela do chat.
      * @param msg A mensagem enviada ou recebida a ser exibida.
      */
     public void addline(String msg) {
         Object ta = principal.find("msgs");
         principal.setString(ta, "text", principal.getString(ta, "text") + msg + "\n");
     }
     /**
      * Fecha a janela do chat.
      */
     public void close() {
         principal.remove(principal.find("chat"));
     }
 }
 

Cliente

editar

Essa classe é responsável pela comunicação através do uso do protocolo criado entre o servidor e o cliente. Aqui é gerada uma thread para se comunicar com o servidor, interpretando as mensagens recebidas e enviando-as para a classe Principal, que apresenta as mensagens formatadas para o usuário visualizar.

Cliente.java

editar
 package chat;
 import java.net.Socket;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.util.StringTokenizer;
 /**
  * Essa classe é a responsável pela comunicação do cliente com o servidor. Ela
  * envia e recebe mensagens do servidor, segundo o protocolo do programa.
  * @author Alan Caio Rodrigues Marques, Lauro Ramon Gomides,
  *  Pablo Cavalcante Nunes, Pedro Henrique da Silva Palhares, Roberto Alexandre
  *  Araújo Ribeiro.
  */
 public class Cliente extends Thread {
     /**Recebe o nickname do usuário desta thread.*/
     private String nickname;
     /**Recebe o socket de comunicação desta thread com o servidor.*/
     private Socket socks;
     /**Recebe o endereço do servidor.*/
     private String address;
     /**Recebe a porta de comunicação com o servidor.*/
     private int port;
     /**Buffer de entrada deste cliente.*/
     private BufferedReader input;
     /**Buffer de saída deste cliente.*/
     private PrintWriter output;
     /**O chat que será apresentado ao usuário para se comunicar.*/
     private Chat chat;
     /**
      * Construtor da classe Cliente. Inicializa as variáveis address, port e
      * chat.
      * @param address O endereço do servidor (IP ou DNS).
      * @param port A porta de comunicação com o servidor.
      * @param chat A tela de chat a ser exibida para o usuário.
      */
     public Cliente(String address, int port, Chat chat) {
         try {
             this.address = address;
             this.port = port;
             this.chat = chat;
         } catch (Exception e) {
             e.printStackTrace();
             System.out.println(e.toString());
         }
     }
     /**
      * Esse método sobrescreve o método Run em Thread. É a thread criada para
      * a comunicação do cliente com o servidor. O cliente entende três tipos de
      * mensagens: as do tipo NICK (recebe a lista de usuários on-line e atualiza
      * sua lista de nicks), tipo MSG (escreve a mensagem recebida na tela de chat
      * do usuário) e tipo EXIT (remove o nick do usuário que saiu do chat).
      */
     public void run() {
         String inputLine, comando;
         String newnick;
         StringTokenizer token, lista;
         try {
             // conecta-se ao servidor pelo endereço e porta definidos.
             socks = new Socket(address, port);
             input = new BufferedReader(new InputStreamReader(socks.getInputStream()));
             output = new PrintWriter(socks.getOutputStream(), true);
             //aguarda algum tipo de mensagem.
             while ((inputLine = input.readLine()) != null) {
                 // ao receber alguma mensagem, separa os tokens por dois pontos (":").
                 token = new StringTokenizer(inputLine, ":");
                 // pega o comando (segundo o protocolo).
                 comando = token.nextToken();
                 // se o comando for NICK, adiciona o(s) nick(s) à lista de nicks.
                 if (comando.equals("NICK")) {
                     lista = new StringTokenizer(token.nextToken(), ",");
                     while (lista.hasMoreTokens()) {
                         chat.additem(lista.nextToken());
                     }
                 } else if (comando.equals("MSG")) {
                     while (token.hasMoreTokens()) {
                         //se o comando for MSG, escreve na tela do usuário a mensagem.
                         comando = token.nextToken();
                     }
                     chat.addline(comando);
                 } else if (comando.equals("EXIT")) {
                     // se o comando for EXIT, remove o nick do usuário que saiu da lista.
                     chat.removeitem(token.nextToken());
                 }
             }
         } catch (Exception e) {
             e.printStackTrace();
             System.out.println(e.toString());
         }
     }
     /**
      * Verifica se este usuário está conectado.
      * @return Verdadeiro se estiver conectado e falso se não estiver.
      */
     public boolean isConnected() {
         if (socks != null) {
             return socks.isConnected();
         }
         return false;
     }
     /**
      * Define o nickname deste usuário.
      * @param nick O nickname escolhido na tela de conexão.
      */
     public void setNickname(String nick) {
         nickname = nick;
     }
     /**
      * Retorna o nickname deste usuário.
      * @return O nickname deste usuário.
      */
     public String getNickname() {
         return nickname;
     }
     /**
      * Caso seja feito um pedido de desconexão, fecha o socket com o servidor.
      * @throws java.lang.Exception Caso ocorra algum erro, reportá-lo.
      */
     public void disconnect() throws Exception {
         if (socks != null) {
             if (socks.isConnected()) {
                 socks.close();
             }
         }
     }
     /**
      * Escreve a mensagem no buffer de saída (servidor) deste cliente.
      * @param msg A mensagem a ser envia para o servidor.
      */
      public void write(String msg) {
         try {
            while (output == null) {
              }
             output.println(msg);
         } catch (Exception e) {
             e.printStackTrace();
             System.out.println(e.toString());
         }
     }
 }
 

Outros

editar

Uma outra classe utilizada é a ThisFrame.java, que simplesmente utiliza o Thinlet para criar uma janela externa, que conterá as sub-janelas do programa de chat. Outro arquivo XML utilizado é o sobre.xml, que apresenta informações sobre o programa quando o usuário escolhe no menu a opção Ajuda->Sobre.

ThisFrame.java

editar
 package chat;
 import thinlet.*;
 /**
  * Essa classe cria uma janela principal para o programa principal do chat ENNN.
  * @author Alan Caio Rodrigues Marques, Lauro Ramon Gomides,
  *  Pablo Cavalcante Nunes, Pedro Henrique da Silva Palhares, Roberto Alexandre
  *  Araújo Ribeiro.
  */
 class ThisFrame extends FrameLauncher {
     /**Recebe a classe que exibirá a tela principal.*/
     Principal principal;
     /**
      * Cria uma janela FrameLauncher Thinlet com os parâmetros especificados.
      * @param title O título da janela a ser criada.
      * @param content O conteúdo (panel) Thinlet com a janela do usuário.
      * @param width Largura da janela.
      * @param height Altura da janela.
      */
     public ThisFrame(String title, Thinlet content, int width, int height) {
         super(title, content, width, height);
         // define o conteúdo da classe principal.
         principal = (Principal) content;
     }
     /**
      * Esse método sobrescreve o método windowClosing em FrameLauncher. Ao fechar
      * a janela, deve-se parar a conexão da classe Principal.
      * @param e
      */
     @Override
     public void windowClosing(java.awt.event.WindowEvent e) {
         //ao fechar a janela, desonectar a classe principal.
         principal.disconnect();
         // e sair do programa.
         System.exit(0);
     }
 }
 

sobre.xml

editar
 <?xml version="1.0" encoding="ISO-8859-1"?>
 <dialog bottom="20" columns="1" gap="10" left="30" name="sobre" right="30" text="Sobre" top="20">
     <label alignment="center" text="Universidade Federal de Goiás"/>
     <label alignment="center" text="Engenharia de Computação"/>
     <label alignment="center" text="Pedro Henrique da Silva Palhares"/>
     <label alignment="center" text="Alan Caio Rodrigues"/>
     <label alignment="center" text="Lauro Ramon Gomides"/>
     <label alignment="center" text="Pablo Cavalcante Nunes"/>
     <label alignment="center" text="Roberto Alexandre Araújo Ribeiro"/>
     <separator/>
     <button action="closeabout()" halign="center" text="Fechar" type="default"/>
 </dialog>


Previsões futuras para o programa

editar

A maioria dos bugs encontrados no programa é referente à comunicação com o servidor. Ele só aceita três tipos de mensagens: NICK, MSG e CAIU. Se algum outro tipo de mensagem for enviada, o servidor não irá tratá-la e nem tem uma rotina para tal finalidade. Como o programa cliente desse chat somente troca as mensagens tratadas pelo servidor, não há nenhum erro. Contudo, ao se conectar com o servidor através do protoclo TELNET, há a possibilidade de outros parâmetros serem trocados. Nesse caso, podem ocorrer erros.

Uma modificação a ser feita é quanto a mensagens que o servidor não trata, devolvendo ao cliente uma mensagem de erro. Dessa forma, evita-se o erro que ocorre em tempo de execução caso o servidor não saiba o que fazer com a mensagem que recebeu.

Outro problema é devido à mensagem "NICK:". Para um usuário logar no chat, precisa enviar uma mensagem do tipo NICK com o nome de usuário. Caso ele já esteja logado, e entre novamente com esse comando, outro usuário será criado na mesma thread e adicionado ao chat. É preciso criar uma rotina que trate esse comando reenviado como sendo a troca do nick daquele usuário, evitando os erros decorrentes.

Outro problema é o uso de nicknames com o caractere vírgula (","), que o servidor divide como sendo a lista de usuários. Ou seja, um único usuário irá criar três itens no cliente, na sua lista de usuários on-line, porém todos referentes a uma única thread. E não será possível enviar mensagem para aquele usuário.

Outro bug que deve ser tratado é quando a mensagem for enviada a um nick inexistente na lista de usuários do servidor. Nesse caso, é gerada uma exceção no servidor que não é tratada. Pode-se criar uma rotina de tratamento caso o nome do usuário destinatário não esteja no servidor.

Para implementações futuras, serão propostos comandos, como "\nick" que permitirá mudar o nickname do usuário, e também envio de arquivos para determinados usuários. O programa é bem modularizado, permitindo adição de novas funções sem dificuldades.