Como hacer un minichat con sockets en Java

Hola a todos, hoy os voy a enseñar como hacer un pequeño chat con sockets en Java.

Manejar sockets en Java es mas o menos complicado, pero hacen aplicaciones muy vistosas. Os muestro la idea final.

En esta ocasión, tenemos que conseguir que cada vez que uno de los dos clientes escriba en la caja de texto y le da al botón de enviar, se actualicen ambas ventanas. Recomiendo hacerlo en Java 8.

La idea es tener una clase Servidor, donde este pendiente de lo que le llegue, una para cada ventana y un Cliente, también para cada uno para enviar su correspondiente mensaje

En tema del diseño de la ventana, os los dejo a vuestra elección, yo he puesto un JTextArea, un JTextField y un JButton.

Lo primero que vamos a necesitar es dos clases, que le vamos a llamar Cliente y Servidor, para que se conecten entre ellas.

Aquí os las dejo:— Cliente.java


package ejercicio_sockets_ddr_3;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Cliente implements Runnable {

    private int puerto;
    private String mensaje;

    public Cliente(int puerto, String mensaje) {
        this.puerto = puerto;
        this.mensaje = mensaje;
    }

    @Override
    public void run() {
        //Host del servidor
        final String HOST = "127.0.0.1";
        //Puerto del servidor
        DataOutputStream out;

        try {
            //Creo el socket para conectarme con el cliente
            Socket sc = new Socket(HOST, puerto);

            out = new DataOutputStream(sc.getOutputStream());

            //Envio un mensaje al cliente
            out.writeUTF(mensaje);

            sc.close();

        } catch (IOException ex) {
            Logger.getLogger(Cliente.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}

— Servidor.java


package ejercicio_sockets_ddr_3;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Observable;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Servidor extends Observable implements Runnable {

    private int puerto;

    public Servidor(int puerto) {
        this.puerto = puerto;
    }

    @Override
    public void run() {

        ServerSocket servidor = null;
        Socket sc = null;
        DataInputStream in;

        try {
            //Creamos el socket del servidor
            servidor = new ServerSocket(puerto);
            System.out.println("Servidor iniciado");

            //Siempre estara escuchando peticiones
            while (true) {

                //Espero a que un cliente se conecte
                sc = servidor.accept();

                System.out.println("Cliente conectado");
                in = new DataInputStream(sc.getInputStream());
               
                //Leo el mensaje que me envia
                String mensaje = in.readUTF();

                System.out.println(mensaje);

                this.setChanged();
                this.notifyObservers(mensaje);
                this.clearChanged();
                
                //Cierro el socket
                sc.close();
                System.out.println("Cliente desconectado");

            }

        } catch (IOException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

}


Fíjate que ambas clases implementan la interfaz Runnable, algo necesario para poder arrancar nuestros hilos.

 

También fíjate que la clase Servidor extiende de la clase Observer, esto lo hacemos para que al haber un cambio, es decir, que uno de los clientes cuando envié un mensaje el servidor lo recogerá y notificara a las ventanas que ha habido un cambio, lo hacemos aquí:


 this.setChanged();
 this.notifyObservers(mensaje);
 this.clearChanged();

Cada una de las ventanas implementaran la interfaz Observer, que nos obligara a crear el método update() que se ejecutara cuando el Servidor notifique los cambios.

En el constructor de cada uno iniciaremos el Servidor, en uno el puerto 5000 y el otro puerto 6000.

— Frm1


public Frm1() {
    initComponents();
    this.getRootPane().setDefaultButton(this.btnEnviar);
    Servidor s = new Servidor(5000);
    s.addObserver(this);
    Thread t = new Thread(s);
    t.start();
}

— Frm2


public Frm2() {
    initComponents();
    this.getRootPane().setDefaultButton(this.btnEnviar);
    Servidor s = new Servidor(6000);
    s.addObserver(this);
    Thread t = new Thread(s);
    t.start();

}

Esto lo que hace es iniciar el Servidor con su puerto, añado como observador a la propia ventana. Después, creo un hilo y lo arranco.

Ahora cuando pulsemos en el botón de enviar en el evento de clic, haremos lo siguiente:

— Frm1


String mensaje = "1: " + this.txtTextoEnviar.getText() + "\n";

this.txtTexto.append(mensaje);

Cliente c = new Cliente(6000, mensaje);
Thread t = new Thread(c);
t.start();

— Frm2


String mensaje = "2: " + this.txtTextoEnviar.getText() + "\n";

this.txtTexto.append(mensaje);

Cliente c = new Cliente(5000, mensaje);
Thread t = new Thread(c);
t.start();

Creamos un objeto Cliente con el puerto del otro servidor y el mensaje a mandar, creamos el hilo y lo arrancamos, esto se conectara al servidor, recibirá el mensaje y notificara los cambios a la ventana.

Este es el método update que tendrá cada ventana:


@Override
public void update(Observable o, Object arg) {
    this.txtTexto.append((String) arg);
}

El objeto arg viene el mensaje que hemos escrito, lo parseamos y lo añadimos a la ventana.

Con esto hará que haga el efecto deseado.

Estas son las ventanas completas.

— Frm1.java


package ejercicio_sockets_ddr_3;

import java.util.Observable;
import java.util.Observer;

public class Frm1 extends javax.swing.JFrame implements Observer {

    public Frm1() {
        initComponents();
        this.getRootPane().setDefaultButton(this.btnEnviar);
        Servidor s = new Servidor(5000);
        s.addObserver(this);
        Thread t = new Thread(s);
        t.start();
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        txtTexto = new javax.swing.JTextArea();
        btnEnviar = new javax.swing.JButton();
        txtTextoEnviar = new javax.swing.JTextField();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("FRM1");

        txtTexto.setColumns(20);
        txtTexto.setRows(5);
        jScrollPane1.setViewportView(txtTexto);

        btnEnviar.setText("Enviar");
        btnEnviar.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnEnviarActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(txtTextoEnviar)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(btnEnviar, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 237, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(btnEnviar, javax.swing.GroupLayout.DEFAULT_SIZE, 35, Short.MAX_VALUE)
                    .addComponent(txtTextoEnviar))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>                        

    private void btnEnviarActionPerformed(java.awt.event.ActionEvent evt) {                                          

        String mensaje = "1: " + this.txtTextoEnviar.getText() + "\n";

        this.txtTexto.append(mensaje);

        Cliente c = new Cliente(6000, mensaje);
        Thread t = new Thread(c);
        t.start();


    }                                         

    @Override
    public void update(Observable o, Object arg) {
        this.txtTexto.append((String) arg);
    }

    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new Frm1().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify                     
    private javax.swing.JButton btnEnviar;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextArea txtTexto;
    private javax.swing.JTextField txtTextoEnviar;
    // End of variables declaration                   

}


— Frm2.java


/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package ejercicio_sockets_ddr_3;

import java.util.Observable;
import java.util.Observer;

public class Frm2 extends javax.swing.JFrame implements Observer {

    public Frm2() {
        initComponents();
        this.getRootPane().setDefaultButton(this.btnEnviar);
        Servidor s = new Servidor(6000);
        s.addObserver(this);
        Thread t = new Thread(s);
        t.start();

    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        txtTexto = new javax.swing.JTextArea();
        btnEnviar = new javax.swing.JButton();
        txtTextoEnviar = new javax.swing.JTextField();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("FRM2");

        txtTexto.setColumns(20);
        txtTexto.setRows(5);
        jScrollPane1.setViewportView(txtTexto);

        btnEnviar.setText("Enviar");
        btnEnviar.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnEnviarActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(txtTextoEnviar)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(btnEnviar, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 237, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(btnEnviar, javax.swing.GroupLayout.DEFAULT_SIZE, 35, Short.MAX_VALUE)
                    .addComponent(txtTextoEnviar))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>                        

    private void btnEnviarActionPerformed(java.awt.event.ActionEvent evt) {                                          

        String mensaje = "2: " + this.txtTextoEnviar.getText() + "\n";

        this.txtTexto.append(mensaje);

        Cliente c = new Cliente(5000, mensaje);
        Thread t = new Thread(c);
        t.start();

    }                                         

    @Override
    public void update(Observable o, Object arg) {
        this.txtTexto.append((String) arg);
    }

    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new Frm2().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify                     
    private javax.swing.JButton btnEnviar;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextArea txtTexto;
    private javax.swing.JTextField txtTextoEnviar;
    // End of variables declaration                   

}


Os dejo un vídeo donde lo vemos con detalle:

Espero que os sea de ayuda. Si tenéis dudas, preguntad. Estamos para ayudarte.

Etiquetas

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *