martes, 12 de febrero de 2013

Implementación De Un Protocolo


En los sockets TCP es necesario establecer una conexión. El servidor TCP espera que un cliente TCP se le conecte. Una vez hecha la conexión, se pueden enviar mensajes. El protocolo TCP garantiza que todos los mensajes enviados van a llegar bien y en el orden enviado. Sólo el cliente necesita saber dónde está el servidor y en qué puerto está escuchando.

Por el contrario, en los sockets UDP no se establece conexión. El servidor se pone a la escucha de un puerto de su ordenador. El cliente también se pone a la escucha de un puerto de su ordenador. En cualquier momento, cualquiera de ellos puede enviar un mensaje al otro. Ambos necesitan saber en qué ordenado y en qué puerto está escuchando el otro. Aquí el concepto de cliente y servidor está un poco más difuso que en el caso de TCP. Podemos considerar servidor al que espera un mensaje y responde. Cliente sería el que inicia el trasiego de mensajes. El servidor debería, además, estar siempre arrancado y a la escucha.

Además, en contra de TCP, el protocolo UDP sólo garantiza que si el mensaje llega, llega bien. No garantiza que llegue ni que lleguen en el mismo orden que se han enviado. Este tipo de sockets es útil para el envío más o menos masivo de información no crucial. Por ejemplo, enviar los datos para el refresco de gráficos en una pantalla.

Otra ventaja de UDP respecto a TCP, es que con UDP se puede enviar un mensaje a varios receptores a la vez, mientras que en TCP, al haber una conexión previa, sólo se puede enviar el mensaje al que está conectado al otro lado. Con UDP se puede, por ejemplo, enviar una sola vez los datos para que varias pantallas refresquen sus gráficos a la vez.


LA CLASE DatagramSocket

Tanto cliente como servidor, para ponerse a la escucha de un puerto UDP, únicamente tienen que instanciar la clase DatagramSocket, pasándole los parámetros adecuados:
El número de puerto que están escuchando.
El InetAddress del ordenador en que se está corriendo cada uno, habitualmente InetAddress.getLocalHost();

Por ello, el código del servidor es tan sencillo como esto
ServidorUdp.java
DatagramSocket socket = new DatagramSocket(
           Constantes.PUERTO_DEL_SERVIDOR,
           InetAddress.getByName("localhost"));

y el del cliente se parece bastante

ClienteUdp.java
DatagramSocket socket = new DatagramSocket(
         Constantes.PUERTO_DEL_CLIENTE,
          InetAddress.getByName("localhost"));



LA CLASE DatagramParcket

Esta es la clase que se va a enviar y recibir como mensaje. Lleva dentro un array de bytes que es el que debemos rellenar con lo queremos enviar o en el que estará lo que hemos recibido.

Dependiendo de si es para enviar o recibir, esta clase se instancia de forma distinta. En ambos casos hay que pasarle el array de bytes. En el caso de enviar, hay que pasarle además la InetAdress del destinatario y el puerto en el que está escuchando el destinatario.
Para enviar

DatagramPacket dato = new DatagramPacket(
   elDatoEnBytes, // El array de bytes
   elDatoEnBytes.length, // Su longitud
   InetAddress.getByName(Constantes.HOST_SERVIDOR),  // Destinatario
   Constantes.PUERTO_DEL_SERVIDOR);   // Puerto del destinatario
Para recibir
DatagramPacket dato = new DatagramPacket(new byte[100], 100);

ENVIAR Y RECIBIR LOS DatagramPacket
Para enviar el DatagramPacket, debemos llamar al método send() de DatagramSocket pasando como parámetro el DatagramPacket que acabamos de crear.
Enviar DatagramPacket
socket.send(dato);

Para recibir, igual pero con socket.receive().

Recibir DatagramPacket
socket.receive(dato);


CONVERTIR UNA CLASE Serializable A ARRAY DE BYTES Y VICEVERSA
Lo de meter un array de bytes en el DatagramPacket está muy bien si leemos bytes de un fichero o de algún otro sitio. Sin embargo, si queremos enviar o recibir clases (Serializable, por supuesto), tenemos que hacer una conversión. Los siguientes código nos ayudan a hacer este tipo de conversiones. Suponemos que la clase se llamaDatoUdp y que implenta la interface Serializable.

De Serializable a byte[]

ByteArrayOutputStream bytes = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream (bytes);
os.writeObject(this); // this es de tipo DatoUdp
os.close();
return bytes.toByteArray(); // devuelve byte[]

De byte[] a Serializable

ByteArrayInputStream byteArray = new ByteArrayInputStream(bytes); // bytes es el byte[]
ObjectInputStream is = new ObjectInputStream(byteArray);
DatoUdp aux = (DatoUdp)is.readObject();
is.close();
return aux;

EL EJEMPLO
Con todo esto ya sabemos lo necesario para hacer un pequeño ejemplo de Servidor udp y cliente udp

Constantes
/** Constantes para el ejemplo de envío y recepción con socket udp. */
public class Constantes
{
    /** Puerto en el que escucha el servidor. */
    public static final int PUERTO_DEL_SERVIDOR=5557;
   
    /** Puerto en el que escucha el cliente */
    public static final int PUERTO_DEL_CLIENTE=5558;
   
    /** Host en el que está el servidor */
    public static final String HOST_SERVIDOR="localhost";
   
    /** Host en el que está el cliente */
    public static final String HOST_CLIENTE="localhost";

Dato udp


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class DatoUdp implements Serializable
{
    /**
     * serial uid
     */
    private static final long serialVersionUID = 3258698714674442547L;

    /**
     * Crea una instancia de la clase, guardando la cadena que se le pasa.
     * @param cadena
     */
    public DatoUdp (String cadena)
    {
        this.cadenaTexto=cadena;
    }
    public String cadenaTexto;
 
    /**
     * Se autoconvierte esta clase a array de bytes.
     * @return La clase convertida a array de bytes.
     */
    public byte [] toByteArray()
    {
        try
        {
             // Se hace la conversión usando un ByteArrayOutputStream y un
             // ObjetOutputStream.
            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
            ObjectOutputStream os = new ObjectOutputStream (bytes);
            os.writeObject(this);
            os.close();
            return bytes.toByteArray();
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }
 
    /**
     * Se convierte el array de bytes que recibe en un objeto DatoUdp.
     * @param bytes El array de bytes
     * @return Un DatoUdp.
     */
    public static DatoUdp fromByteArray (byte [] bytes)
    {
        try
        {
            // Se realiza la conversión usando un ByteArrayInputStream y un
            // ObjectInputStream
            ByteArrayInputStream byteArray = new ByteArrayInputStream(bytes);
            ObjectInputStream is = new ObjectInputStream(byteArray);
            DatoUdp aux = (DatoUdp)is.readObject();
            is.close();
            return aux;
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }
}


Cliente

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * Abre un socket udp y envía por él 10 mensajes consistentes en 10 clases
 *
 */
public class ClienteUdp
{

    /**
     * Programa de prueba. Instancia esta clase
     * @param args
     */
    public static void main(String[] args)
    {
        new ClienteUdp();
    }

    /**
     * Crea una instancia de esta clase y envía los 10 mensajes
     *
     */
    public ClienteUdp()
    {
        try
        {

            // La IP es la local, el puerto es en el que este cliente esté
            // escuchando.
            DatagramSocket socket = new DatagramSocket(
                    Constantes.PUERTO_DEL_CLIENTE, InetAddress
                            .getByName("localhost"));

            // Se instancia un DatoUdp y se convierte a bytes[]
            DatoUdp elDato = new DatoUdp("hola");
            byte[] elDatoEnBytes = elDato.toByteArray();

            // Se meten los bytes en el DatagramPacket, que es lo que se
            // va a enviar por el socket.
            // El destinatario es el servidor.
            // El puerto es por el que esté escuchando el servidor.
            DatagramPacket dato = new DatagramPacket(elDatoEnBytes,
                    elDatoEnBytes.length, InetAddress
                            .getByName(Constantes.HOST_SERVIDOR),
                    Constantes.PUERTO_DEL_SERVIDOR);
           
            // Se envía el DatagramPacket 10 veces, esperando 1 segundo entre
            // envío y envío.
            for (int i = 0; i < 10; i++)
            {
                System.out.println("Envio dato " + i);
                socket.send(dato);
                Thread.sleep(1000);
            }
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
Servidor udp

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * Servidor de udp que se pone a la escucha de DatagramPacket que contengan
 * dentro DatoUdp y los escribe en pantalla.
 */
public class ServidorUdp
{

    /**
     * Prueba del prorama ServidorUdp
     *
     * @param args
     */
    public static void main(String[] args)
    {
        new ServidorUdp();
    }

    /**
     * Crea una instancia de esta clase, poniendose a la escucha del puerto
     * definido en Constantes y escribe en pantalla todos los mensajes que le
     * lleguen.
     */
    public ServidorUdp()
    {
        try
        {

            // La IP es la local, el puerto es en el que el servidor esté
            // escuchando.
            DatagramSocket socket = new DatagramSocket(
                    Constantes.PUERTO_DEL_SERVIDOR, InetAddress
                            .getByName("localhost"));

            // Un DatagramPacket para recibir los mensajes.
            DatagramPacket dato = new DatagramPacket(new byte[100], 100);

            // Bucle infinito.
            while (true)
            {
                // Se recibe un dato y se escribe en pantalla.
                socket.receive(dato);
                System.out.print("Recibido dato de "
                        + dato.getAddress().getHostName() + " : ");
               
                // Conversion de los bytes a DatoUdp
                DatoUdp datoRecibido = DatoUdp.fromByteArray(dato.getData());
                System.out.println(datoRecibido.cadenaTexto);
            }
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
Deberías ver cómo el cliente envía 10 mensajes y el servidor los escribe en pantalla. Si no ejecutas ambos programas en el mismo ordenador, tendrás que cambiar los fuentes para que el cliente tenga el nombre del servidor.


1 comentario:

  1. La estructura de la entrada no es la mejor posible y además el protocolo(?) no hace nada en particular - simplemente envia mensajes... Van 6 pts.

    ResponderEliminar