Un objeto java.net.DatagramSocket es un "conector" a trav�s del cual enviamos y recibimos paquetes UDP. En la literatura t�cnica estos paquetes se denominan Datagramas.
La forma usual de crear un DatagramSocket para recibir paquetes es especificando un n�mero de puerto en el constructor. De esta forma, este DatagramSocket estar� "escuchando" en el puerto especificado, preparado para recibir cualquier paquete entrante.
Si queremos construir un DatagramSocket para enviar paquetes, no es necesario especificar el n�mero de puerto, ya que nos es indiferente. No ocurre lo mismo en el caso anterior, ya que siempre querremos usar un puerto fijo conocido por el resto de aplicaciones.
DatagramSocket ds1 = new DatagramSocket(123); /* Aqu� usamos este DatagramSocket para recibir datos... */ /* ... */ /* Hemos terminado, cerramos el socket */ ds1.close(); DatagramSocket ds2 = new Datagramsocket(); /* Aqu� lo usamos para transmitir datos... */ /* ... */ /* Hemos terminado, cerramos el socket */ ds2.close();
�La clase DatagramPacket
Esta clase representa a los paquetes de datos que vamos a recibir o transmitir a trav�s de los objetos DatagramSocket. Estos paquetes constan de una cabecera (que incluye la direcci�n de origen y destino del paquete, el puerto, la longitud del paquete, un checksum, etc.) y un cuerpo (donde se encuentra el contenido real del paquete).
En Java accedemos a las distintas partes de un datagrama mediante los m�todos de la clase java.net.DatagramPacket.
La forma de construir datagramas es distinta dependiendo de si queremos enviar o recibir datos. En caso de que �nicamente queramos recibir, debemos especificar un array de bytes donde almacenar los datos y un n�mero entero con la longitud m�xima que queremos recibir.
Si queremos transmitir, debemos especificar el buffer de datos que queremos enviar, la longitud m�xima de datos, la direcci�n y el puerto de destino del datagrama. La direcci�n de destino se especifica mediante un objeto de tipo InetAddress, mientras que el puerto se indica mediante un n�mero entero.
Veamos un ejemplo, donde enviamos un datagrama a una determinada direcci�n, suponiendo que tenemos un objeto InetAddress correctamente creado. N�tese el uso del m�todo send() de la clase DatagramSocket para enviar el datagrama:
int tam = 1024; InetAddress direcc = ...; byte[] datos = new byte[tam]; int puerto = 543; for (int n=0;n<tam;n++){ /* Generamos los datos que vamos a enviar */ datos[n] = ...; } DatagramSocket ds = new DatagramSocket(); DatagramPacket dp = new DatagramPacket(datos, tam, direcc, puerto); ds.send(dp); /* Aqu� enviamos el paquete */
Como se ve, la clase DatagramPacket solo nos da herramientas para enviar matrices de bytes a trav�s de UDP. Si queremos transmitir datos m�s complejos (cadenas de texto, enteros largos, n�meros en coma flotante, objetos Java, etc.) debemos ser nosotros los encargados de codificar esa informaci�n dentro de un array de bytes.
Si lo que queremos es recibir datos, solo necesitamos reservar espacio para la informaci�n entrante (un array de bytes), poner un objeto DatagramSocket escuchando en un puerto y esperar a recibir un paquete mediante el m�todo receive().
int tam = 1024; byte[] buffer = new byte[tam]; int puerto = 987; DatagramSocket ds = new Datagramsocket(puerto); DatagramPacket dp = new DatagramPacket(buffer,tam); ds.receive(dp); // Ahora tenemos en buffer la informaci�n que nos interesa
�La clase InetAddress
�C�mo se usa la clase java.net.InetAddress que aparec�a en uno de los ejemplos anteriores?.
La forma de crear un objeto InetAddress es mediante el m�todo est�tico InetAddress.getByName(String), que recibe un nombre de host en notaci�n alfanum�rica (por ejemplo "www.etsit.upv.es" o "209.41.57.70" y devuelve un objeto InetAddress con esa direcci�n. Si la direcci�n no existe o no puede ser encontrada, este m�todo lanza una UnknownHostException.
Por ejemplo, si queremos mandar una matriz de bytes al puerto 90 de la direcci�n "www.upv.es", tenemos que escribir:
int tam = ...; int puerto = 90; String maquina = "www.upv.es"; byte[] buffer = new byte[tam]; // ... // Generamos el contenido del buffer // ... InetAddress direcc = InetAddress.getByName(maquina); DatagramSocket ds = new DatagramSocket(); DatagramPacket dp = new DatagramPacket(buffer, tam, direcc, puerto); ds.send(dp); /* Aqu� enviamos el paquete */
Si queremos enviar paquetes a nuestra propia m�quina hay que usar como nombre de host la direcci�n "localhost" o "127.0.0.1". Tambi�n podemos usar el m�todo InetAddress.getLocalHost(), que devuelve un objeto InetAddress que "apunta" a la m�quina local.
�Un ejemplo cliente/servidor completo usando UDP
A continuaci�n se incluye una aplicaci�n completa, formada por un cliente y un servidor que se comunican mediante UDP. El cliente env�a n�meros enteros (32 bits) al servidor y este se los devuelve despu�s de procesarlos (simplemente los eleva al cuadrado), en forma de enteros largos (64 bits).
En ambos programas (cliente y servidor) se utilizan varias clases explicadas con anterioridad (es fundamental haber le�do previamente el cap�tulo de Entrada/Salida) y se realiza un extensivo control de excepciones.
Los datos que el cliente env�a al servidor los obtiene de la l�nea de comandos.
Veamos un ejemplo en el que el cliente manda los n�meros 43, 56 y 2 al servidor. Si ejecutamos el servidor en una m�quina con direcci�n IP 194.140.47.1 y ejecutamos el cliente en otra m�quina con direcci�n IP distinta, tendr�amos que teclear la siguiente en la l�nea de comandos:
Servidor:
java servidor
Cliente:
java cliente 194.140.47.1 43 56 2
Como cada paquete enviado es independiente de los dem�s, podr�amos tener varios clientes comunic�ndose con el servidor al mismo tiempo, con lo que los distintos paquetes se intercalar�an unos con otros.
Si ejecutamos ambos programas en la misma m�quina tendr�amos que abrir dos sesiones distintas y teclear las instrucciones anteriores en cada sesi�n. En el caso del cliente tendr�amos que poner:
java cliente localhost 43 56 2
Veamos el c�digo del programa:
import java.net.*; import java.io.*; class servidor{ public static void main(String args[]){ // Primero indicamos la direcci�n IP local try{ System.out.println("LocalHost = " + InetAddress.getLocalHost().toString()); } catch (UnknownHostException uhe){ System.err.println("No puedo saber la direcci�n IP local : " + uhe); } // Abrimos un Socket UDP en el puerto 1234. // A trav�s de este Socket enviaremos datagramas del tipo DatagramPacket DatagramSocket ds = null; try{ ds = new DatagramSocket(1234); } catch(SocketException se){ System.err.println("Se ha producido un error al abrir el socket : " + se); System.exit(-1); } // Bucle infinito while(true){ try{ // Nos preparamos a recibir un n�mero entero (32 bits = 4 bytes) byte bufferEntrada[] = new byte[4]; // Creamos un "contenedor" de datagrama, cuyo buffer // ser� el array creado antes DatagramPacket dp = new DatagramPacket(bufferEntrada,4); // Esperamos a recibir un paquete ds.receive(dp); // Podemos extraer informaci�n del paquete // N� de puerto desde donde se envi� int puerto = dp.getPort(); // Direcci�n de Internet desde donde se envi� InetAddress direcc = dp.getAddress(); // "Envolvemos" el buffer con un ByteArrayInputStream... ByteArrayInputStream bais = new ByteArrayInputStream(bufferEntrada); // ... que volvemos a "envolver" con un DataInputStream DataInputStream dis = new DataInputStream(bais); // Y leemos un n�mero entero a partir del array de bytes int entrada = dis.readInt(); long salida = (long)entrada*(long)entrada; // Creamos un ByteArrayOutputStream sobre el que podamos escribir ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Lo envolvemos con un DataOutputStream DataOutputStream dos = new DataOutputStream(baos); // Escribimos el resultado, que debe ocupar 8 bytes dos.writeLong(salida); // Cerramos el buffer de escritura dos.close(); // Generamos el paquete de vuelta, usando los datos // del remitente del paquete original dp = new DatagramPacket(baos.toByteArray(),8,direcc,puerto); // Enviamos ds.send(dp); // Registramos en salida estandard System.out.println( "Cliente = " + direcc + ":" + puerto + "\tEntrada = " + entrada + "\tSalida = " + salida ); } catch(Exception e){ System.err.println("Se ha producido el error " + e); } } } } class cliente { public static void main(String args[]){ // Leemos el primer par�metro, donde debe ir la direcci�n // IP del servidor InetAddress direcc = null; try{ direcc = InetAddress.getByName(args[0]); } catch(UnknownHostException uhe){ System.err.println("Host no encontrado : " + uhe); System.exit(-1); } // Puerto que hemos usado para el servidor int puerto = 1234; // Creamos el Socket DatagramSocket ds = null; try{ ds = new DatagramSocket(); } catch(SocketException se){ System.err.println("Error al abrir el socket : " + se); System.exit(-1); } // Para cada uno de los argumentos... for (int n=1;n<args.length;n++){ try{ // Creamos un buffer para escribir ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); // Convertimos el texto en n�mero int numero = Integer.parseInt(args[n]); // Lo escribimos dos.writeInt(numero); // y cerramos el buffer dos.close(); // Creamos paquete DatagramPacket dp = new DatagramPacket(baos.toByteArray(),4,direcc,puerto); // y lo mandamos ds.send(dp); // Preparamos buffer para recibir n�mero de 8 bytes byte bufferEntrada[] = new byte[8]; // Creamos el contenedor del paquete dp = new DatagramPacket(bufferEntrada,8); // y lo recibimos ds.receive(dp); // Creamos un stream de lectura a partir del buffer ByteArrayInputStream bais = new ByteArrayInputStream(bufferEntrada); DataInputStream dis = new DataInputStream(bais); // Leemos el resultado final long resultado = dis.readLong(); // Indicamos en pantalla System.out.println( "Solicitud = " + numero + "\tResultado = " +resultado ); } catch (Exception e){ System.err.println("Se ha producido un error : " + e); } } } }
En el c�digo anterior se usan m�todos no vistos hasta ahora, como DatagramPacket.getAddress(), que devuelve la direcci�n IP del remitente del mensaje, y DatagramPacket.getPort(), que devuelve el puerto. Existen otros m�todos importantes, como DatagramPacket.getLength(), que indican la longitud de datos recibidos.
�Consideraciones sobre UDP
UDP es un protocolo no orientado a la conexi�n. Esto significa que la comunicaci�n es m�s r�pida (porque no hay que establecer conexiones ni circuitos virtuales entre m�quinas) pero tambi�n menos segura.
Nadie nos garantiza que un paquete vaya a llegar a su destino. Tampoco est� garantizado que dos paquetes lleguen en el mismo orden en que se enviaron. Si nuestros programas usan una red local privada para comunicarse, es muy improbable que estos problemas aparezcan. Pero si deben usar una red p�blica como Internet, con m�ltiples pasarelas y enlaces distintos entre los punto de origen y destino, cualquiera de estos errores puede ocurrir (y ocurrir�, seguro).
En estos casos es responsabilidad del programador el comprobar que los paquetes llegan a sus destino y que la informaci�n se transmite en orden. Existen multitud de mecanismos para conseguir esto, pero la complejidad de su implementaci�n hace m�s recomendable la utilizaci�n de un protocolo orientado a la conexi�n como TCP.