domingo, 28 de septiembre de 2014


En este nuevo post mostraremos como hacer un chat mediante el uso de Sockets en Java. Los Sockets sirven para comunicar procesos de diferentes maquinas de una red.
Haremos un servidor y un cliente utilizando Sockets.

Del lado del Servidor se tiene un bucle infinito que espera conexiones de clientes. Cuando un cliente se conecta el servidor acepta la conexion y genera dos threads: uno para enviar datos y el otro para recibirlos.

Del lado del Cliente se tiene que esperar un Servidor para poder conectarse, cuando se conecta al servidor se generan dos threads, al igual que en el Servidor uno para enviar y otro para recibir los datos.

Los threads que se generan del lado del servidor y del cliente son los mismos.
La clase principal del Servidor es identica a la clase principal del cliente; la unica diferencia esta en el main, el servidor espera conexiones del cliente y el cliente busca servidor para conectarse.

Para nuestro ejemplo utilizaremos localhost para poder correr el programa en nuestra propia maquina.

Es importante tener en cuenta que puerto se va a utilizar para poder abrirlo con anterioridad, en nuestro caso abriremos el puerto 11111.

Se debe conocer que las asignaciones a los puertos comprendidos entre los valores (0 - 1023) estan determinados por la IANA (Internet Assigned Numbers Authority). y no se los puede utilizar de otro manera.

Se puede utilizar los puertos comprendidos entre los valores (1024 - 65535). 

Nuestro programa cuenta con dos paquetes, uno para el servidor y otro para el cliente; los mismo que contiene threads identicos para envio y recepcion de datos.

Empezaremos describiendo las clases del lado del servidor:

Clase PrincipalChat:
Esta clase implementa la interfaz grafica para poder mostrar los mensajes entrantes y un JTextField para poder enviarlos. La interfaz contiene un menu para poder salir del programa.
En el main se puede ver que se espera conexiones de clientes.

  1. package servidor;
  2.  
  3. import java.awt.BorderLayout;
  4. import java.awt.Color;
  5. import java.awt.event.ActionEvent;
  6. import java.awt.event.ActionListener;
  7. import java.io.IOException;
  8. import java.net.InetAddress;
  9. import java.net.ServerSocket;
  10. import java.net.Socket;
  11. import java.util.concurrent.ExecutorService;
  12. import java.util.concurrent.Executors;
  13. import java.util.logging.Level;
  14. import java.util.logging.Logger;
  15. import javax.swing.*;
  16.  
  17. /**Clase que se encarga de correr los threads de enviar y recibir texto
  18.  * y de crear la interfaz grafica.
  19.  *
  20.  * @author Rafa
  21.  */
  22. public class PrincipalChat extends JFrame{
  23.     public JTextField campoTexto; //Para mostrar mensajes de los usuarios
  24.     public JTextArea areaTexto; //Para ingresar mensaje a enviar
  25.     private static ServerSocket servidor; //
  26.     private static Socket conexion; //Socket para conectarse con el cliente
  27.     private static String ip = "127.0.0.1"; //ip a la cual se conecta
  28.    
  29.     public static PrincipalChat main;
  30.    
  31.     public PrincipalChat(){
  32.         super("Servidor"); //Establece titulo al Frame
  33.        
  34.         campoTexto = new JTextField(); //crea el campo para texto
  35.         campoTexto.setEditable(false); //No permite que sea editable el campo de texto
  36.         add(campoTexto, BorderLayout.NORTH); //Coloca el campo de texto en la parte superior
  37.        
  38.         areaTexto = new JTextArea(); //Crear displayArea
  39.         areaTexto.setEditable(false);
  40.         add(new JScrollPane(areaTexto)BorderLayout.CENTER);
  41.         areaTexto.setBackground(Color.orange); //Pone de color cyan al areaTexto
  42.         areaTexto.setForeground(Color.BLACK); //pinta azul la letra en el areaTexto
  43.         campoTexto.setForeground(Color.BLACK); //pinta toja la letra del mensaje a enviar
  44.        
  45.        
  46.         //Crea menu Archivo y submenu Salir, ademas agrega el submenu al menu
  47.         JMenu menuArchivo = new JMenu("Archivo");
  48.         JMenuItem salir = new JMenuItem("Salir");
  49.         menuArchivo.add(salir); //Agrega el submenu Salir al menu menuArchivo
  50.        
  51.         JMenuBar barra = new JMenuBar(); //Crea la barra de menus
  52.         setJMenuBar(barra); //Agrega barra de menus a la aplicacion
  53.         barra.add(menuArchivo); //agrega menuArchivo a la barra de menus
  54.        
  55.         //Accion que se realiza cuando se presiona el submenu Salir
  56.         salir.addActionListener(new ActionListener() { //clase interna anonima
  57.                 public void actionPerformed(ActionEvent e) {
  58.                     System.exit(0); //Sale de la aplicacion
  59.                 }
  60.         });
  61.        
  62.         setSize(300320); //Establecer tamano a ventana
  63.         setVisible(true); //Pone visible la ventana
  64.     }
  65.    
  66.     //Para mostrar texto en displayArea
  67.     public void mostrarMensaje(String mensaje) {
  68.         areaTexto.append(mensaje + "\n");
  69.     }
  70.     public void habilitarTexto(boolean editable) {
  71.         campoTexto.setEditable(editable);
  72.     }
  73.  
  74.      /**
  75.      * @param args the command line arguments
  76.      */
  77.     public static void main(String[] args) {
  78.         PrincipalChat main = new PrincipalChat(); //Instanciacion de la clase Principalchat
  79.         main.setLocationRelativeTo(null);   //Centrar el JFrame
  80.         main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //habilita cerrar la ventana
  81.         ExecutorService executor = Executors.newCachedThreadPool(); //Para correr los threads
  82.  
  83.         try {
  84.             //main.mostrarMensaje("No se encuentra Servidor");
  85.             servidor = new ServerSocket(11111100);
  86.             main.mostrarMensaje("Esperando Cliente ...");
  87.  
  88.             //Bucle infinito para esperar conexiones de los clientes
  89.             while (true){
  90.                 try {
  91.                     conexion = servidor.accept(); //Permite al servidor aceptar conexiones        
  92.  
  93.                     //main.mostrarMensaje("Conexion Establecida");
  94.                     main.mostrarMensaje("Conectado a : " +conexion.getInetAddress().getHostName());
  95.  
  96.                     main.habilitarTexto(true); //permite escribir texto para enviar
  97.  
  98.                     //Ejecucion de los threads
  99.                     executor.execute(new Recibe(conexion, main)); //client
  100.                     executor.execute(new Envia(conexion, main));
  101.                 } catch (IOException ex) {
  102.                     Logger.getLogger(PrincipalChat.class.getName()).log(Level.SEVEREnull, ex);
  103.                 }
  104.             }
  105.         } catch (IOException ex) {
  106.             Logger.getLogger(PrincipalChat.class.getName()).log(Level.SEVERE,null, ex);
  107.         } //Fin del catch
  108.         finally {
  109.         }
  110.         executor.shutdown();
  111.     }
  112. }

  113. Clase ThreadEnvia:
  114. En esta clase establecemos nuestro canal de salida tipo ObjectOutputStream, el cual nos sirve para escribir el mensaje, enviarlo y mostrarlo en pantalla mediante el metodo enviarDatos().
    Ademas declaramos la variable conexion tipo Socket, la cual se encarga de establecer el flujo de datos entre  cliente y servidor.
    1. package servidor;
    2.  
    3. import java.io.IOException;
    4. import java.io.ObjectOutputStream;
    5. import java.net.Socket;
    6. import java.awt.event.ActionEvent;
    7. import java.awt.event.ActionListener;
    8. import java.net.SocketException;
    9.        
    10. public class ThreadEnvia implements Runnable {
    11.     private final PrincipalChat main;
    12.     private ObjectOutputStream salida;
    13.     private String mensaje;
    14.     private Socket conexion;
    15.    
    16.     public ThreadEnvia(Socket conexion, final PrincipalChat main){
    17.         this.conexion = conexion;
    18.         this.main = main;
    19.        
    20.         //Evento que ocurre al escribir en el areaTexto
    21.         main.campoTexto.addActionListener(new ActionListener() {
    22.             public void actionPerformed(ActionEvent event) {
    23.                 mensaje = event.getActionCommand();
    24.                 enviarDatos(mensaje); //se envia el mensaje
    25.                 main.campoTexto.setText(""); //borra el texto del enterfield
    26.             } //Fin metodo actionPerformed
    27.         }
    28.         );//Fin llamada a addActionListener
    29.     }
    30.    
    31.    //enviar objeto a cliente
    32.    private void enviarDatos(String mensaje){
    33.       try {
    34.          salida.writeObject("Servidor>>> " + mensaje);
    35.          salida.flush(); //flush salida a cliente
    36.          main.mostrarMensaje("Servidor>>> " + mensaje);
    37.       } //Fin try
    38.       catch (IOException ioException){
    39.          main.mostrarMensaje("Error escribiendo Mensaje");
    40.       } //Fin catch  
    41.      
    42.    } //Fin methodo enviarDatos
    43.  
    44.    //manipula areaPantalla en el hilo despachador de eventos
    45.     public void mostrarMensaje(String mensaje) {
    46.         main.areaTexto.append(mensaje);
    47.     }
    48.    
    49.     public void run() {
    50.          try {
    51.             salida = new ObjectOutputStream(conexion.getOutputStream());
    52.             salida.flush();
    53.         } catch (SocketException ex) {
    54.         } catch (IOException ioException) {
    55.           ioException.printStackTrace();
    56.         } catch (NullPointerException ex) {
    57.         }
    58.     }  
    59.    
    60. }
    61. Clase ThreadRecibe:
    En esta clase establecemos nuestro canal de entrada tipo ObjectInputStream, el cual se encarga de recibir los mensajes enviados por el cliente o servidor.
    Aqui se procesa los mensajes recibidos y luego son mostrados en pantalla.
    Es importante aclarar que se debe cerrar el canal de entrada de datos y el Socket de conexion una vez finalizado el flujo de datos.
  1. package servidor;
  2.  
  3. import java.io.EOFException;
  4. import java.io.IOException;
  5. import java.io.ObjectInputStream;
  6. import java.net.Socket;
  7. import java.net.SocketException;
  8. import java.util.logging.Level;
  9. import java.util.logging.Logger;
  10.  
  11. public class ThreadRecibe implements Runnable {
  12.     private final PrincipalChat main;
  13.     private String mensaje;
  14.     private ObjectInputStream entrada;
  15.     private Socket cliente;
  16.    
  17.    
  18.    //Inicializar chatServer y configurar GUI
  19.    public ThreadRecibe(Socket cliente, PrincipalChat main){
  20.        this.cliente = cliente;
  21.        this.main = main;
  22.    }  
  23.  
  24.     public void mostrarMensaje(String mensaje) {
  25.         main.areaTexto.append(mensaje);
  26.     }
  27.    
  28.     public void run() {
  29.         try {
  30.             entrada = new ObjectInputStream(cliente.getInputStream());
  31.         } catch (IOException ex) {
  32.             Logger.getLogger(ThreadRecibe.class.getName()).log(Level.SEVERE,null, ex);
  33.         }
  34.         do { //procesa los mensajes enviados dsd el servidor
  35.             try {//leer el mensaje y mostrarlo
  36.                 mensaje = (String) entrada.readObject(); //leer nuevo mensaje
  37.                 main.mostrarMensaje(mensaje);
  38.             } //fin try
  39.             catch (SocketException ex) {
  40.             }
  41.             catch (EOFException eofException) {
  42.                 main.mostrarMensaje("Fin de la conexion");
  43.                 break;
  44.             } //fin catch
  45.             catch (IOException ex) {
  46.                 Logger.getLogger(ThreadRecibe.class.getName()).log(Level.SEVEREnull, ex);
  47.             } catch (ClassNotFoundException classNotFoundException) {
  48.                 main.mostrarMensaje("Objeto desconocido");
  49.             } //fin catch              
  50.  
  51.         } while (!mensaje.equals("Servidor>>> TERMINATE")); //Ejecuta hasta que el server escriba TERMINATE
  52.  
  53.         try {
  54.             entrada.close(); //cierra input Stream
  55.             cliente.close(); //cieraa Socket
  56.         } //Fin try
  57.         catch (IOException ioException) {
  58.             ioException.printStackTrace();
  59.         } //fin catch
  60.  
  61.         main.mostrarMensaje("Fin de la conexion");
  62.         System.exit(0);
  63.     }
  64. }