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).
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.
En el main se puede ver que se espera conexiones de clientes.
- package servidor;
- import java.awt.BorderLayout;
- import java.awt.Color;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.IOException;
- import java.net.InetAddress;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import javax.swing.*;
- /**Clase que se encarga de correr los threads de enviar y recibir texto
- * y de crear la interfaz grafica.
- *
- * @author Rafa
- */
- public class PrincipalChat extends JFrame{
- public JTextField campoTexto; //Para mostrar mensajes de los usuarios
- public JTextArea areaTexto; //Para ingresar mensaje a enviar
- private static ServerSocket servidor; //
- private static Socket conexion; //Socket para conectarse con el cliente
- private static String ip = "127.0.0.1"; //ip a la cual se conecta
- public static PrincipalChat main;
- public PrincipalChat(){
- super("Servidor"); //Establece titulo al Frame
- campoTexto = new JTextField(); //crea el campo para texto
- campoTexto.setEditable(false); //No permite que sea editable el campo de texto
- add(campoTexto, BorderLayout.NORTH); //Coloca el campo de texto en la parte superior
- areaTexto = new JTextArea(); //Crear displayArea
- areaTexto.setEditable(false);
- add(new JScrollPane(areaTexto), BorderLayout.CENTER);
- areaTexto.setBackground(Color.orange); //Pone de color cyan al areaTexto
- areaTexto.setForeground(Color.BLACK); //pinta azul la letra en el areaTexto
- campoTexto.setForeground(Color.BLACK); //pinta toja la letra del mensaje a enviar
- //Crea menu Archivo y submenu Salir, ademas agrega el submenu al menu
- JMenu menuArchivo = new JMenu("Archivo");
- JMenuItem salir = new JMenuItem("Salir");
- menuArchivo.add(salir); //Agrega el submenu Salir al menu menuArchivo
- JMenuBar barra = new JMenuBar(); //Crea la barra de menus
- setJMenuBar(barra); //Agrega barra de menus a la aplicacion
- barra.add(menuArchivo); //agrega menuArchivo a la barra de menus
- //Accion que se realiza cuando se presiona el submenu Salir
- salir.addActionListener(new ActionListener() { //clase interna anonima
- public void actionPerformed(ActionEvent e) {
- System.exit(0); //Sale de la aplicacion
- }
- });
- setSize(300, 320); //Establecer tamano a ventana
- setVisible(true); //Pone visible la ventana
- }
- //Para mostrar texto en displayArea
- public void mostrarMensaje(String mensaje) {
- areaTexto.append(mensaje + "\n");
- }
- public void habilitarTexto(boolean editable) {
- campoTexto.setEditable(editable);
- }
- /**
- * @param args the command line arguments
- */
- public static void main(String[] args) {
- PrincipalChat main = new PrincipalChat(); //Instanciacion de la clase Principalchat
- main.setLocationRelativeTo(null); //Centrar el JFrame
- main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //habilita cerrar la ventana
- ExecutorService executor = Executors.newCachedThreadPool(); //Para correr los threads
- try {
- //main.mostrarMensaje("No se encuentra Servidor");
- servidor = new ServerSocket(11111, 100);
- main.mostrarMensaje("Esperando Cliente ...");
- //Bucle infinito para esperar conexiones de los clientes
- while (true){
- try {
- conexion = servidor.accept(); //Permite al servidor aceptar conexiones
- //main.mostrarMensaje("Conexion Establecida");
- main.mostrarMensaje("Conectado a : " +conexion.getInetAddress().getHostName());
- main.habilitarTexto(true); //permite escribir texto para enviar
- //Ejecucion de los threads
- executor.execute(new Recibe(conexion, main)); //client
- executor.execute(new Envia(conexion, main));
- } catch (IOException ex) {
- Logger.getLogger(PrincipalChat.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
- } catch (IOException ex) {
- Logger.getLogger(PrincipalChat.class.getName()).log(Level.SEVERE,null, ex);
- } //Fin del catch
- finally {
- }
- executor.shutdown();
- }
- }
- Clase ThreadEnvia:
- 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. - package servidor;
- import java.io.IOException;
- import java.io.ObjectOutputStream;
- import java.net.Socket;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.net.SocketException;
- public class ThreadEnvia implements Runnable {
- private final PrincipalChat main;
- private ObjectOutputStream salida;
- private String mensaje;
- private Socket conexion;
- public ThreadEnvia(Socket conexion, final PrincipalChat main){
- this.conexion = conexion;
- this.main = main;
- //Evento que ocurre al escribir en el areaTexto
- main.campoTexto.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent event) {
- mensaje = event.getActionCommand();
- enviarDatos(mensaje); //se envia el mensaje
- main.campoTexto.setText(""); //borra el texto del enterfield
- } //Fin metodo actionPerformed
- }
- );//Fin llamada a addActionListener
- }
- //enviar objeto a cliente
- private void enviarDatos(String mensaje){
- try {
- salida.writeObject("Servidor>>> " + mensaje);
- salida.flush(); //flush salida a cliente
- main.mostrarMensaje("Servidor>>> " + mensaje);
- } //Fin try
- catch (IOException ioException){
- main.mostrarMensaje("Error escribiendo Mensaje");
- } //Fin catch
- } //Fin methodo enviarDatos
- //manipula areaPantalla en el hilo despachador de eventos
- public void mostrarMensaje(String mensaje) {
- main.areaTexto.append(mensaje);
- }
- public void run() {
- try {
- salida = new ObjectOutputStream(conexion.getOutputStream());
- salida.flush();
- } catch (SocketException ex) {
- } catch (IOException ioException) {
- ioException.printStackTrace();
- } catch (NullPointerException ex) {
- }
- }
- }
- 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.
- package servidor;
- import java.io.EOFException;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.net.Socket;
- import java.net.SocketException;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- public class ThreadRecibe implements Runnable {
- private final PrincipalChat main;
- private String mensaje;
- private ObjectInputStream entrada;
- private Socket cliente;
- //Inicializar chatServer y configurar GUI
- public ThreadRecibe(Socket cliente, PrincipalChat main){
- this.cliente = cliente;
- this.main = main;
- }
- public void mostrarMensaje(String mensaje) {
- main.areaTexto.append(mensaje);
- }
- public void run() {
- try {
- entrada = new ObjectInputStream(cliente.getInputStream());
- } catch (IOException ex) {
- Logger.getLogger(ThreadRecibe.class.getName()).log(Level.SEVERE,null, ex);
- }
- do { //procesa los mensajes enviados dsd el servidor
- try {//leer el mensaje y mostrarlo
- mensaje = (String) entrada.readObject(); //leer nuevo mensaje
- main.mostrarMensaje(mensaje);
- } //fin try
- catch (SocketException ex) {
- }
- catch (EOFException eofException) {
- main.mostrarMensaje("Fin de la conexion");
- break;
- } //fin catch
- catch (IOException ex) {
- Logger.getLogger(ThreadRecibe.class.getName()).log(Level.SEVERE, null, ex);
- } catch (ClassNotFoundException classNotFoundException) {
- main.mostrarMensaje("Objeto desconocido");
- } //fin catch
- } while (!mensaje.equals("Servidor>>> TERMINATE")); //Ejecuta hasta que el server escriba TERMINATE
- try {
- entrada.close(); //cierra input Stream
- cliente.close(); //cieraa Socket
- } //Fin try
- catch (IOException ioException) {
- ioException.printStackTrace();
- } //fin catch
- main.mostrarMensaje("Fin de la conexion");
- System.exit(0);
- }
- }