La aplicación cliente de este ejemplo es muy sencilla -- envía un datagrama al servidor que indica que le gustaría recibir una cita del momento. Entonces el cliente espera a que el servidor le envíe un datagrama en respuesta.
Dos clases implementan la aplicación servidor: QuoteServer y QuoteServerThread. Una sóla clase implementa la aplicación cliente: QuoteClient.
Investiguemos estas clases empezando con la clase que contiene el método main() de la aplicación servidor.
Contiene una versión de applet de la clase QuoteClient.
La Clase QuoteServer
La clase QuoteServer contiene un sólo método: el método main() para la
aplicación servidor de citas. El método main() sólo crean un nuevo objeto QuoteServerThread y lo arranca.
class QuoteServer {
public static void main(String[] args) {
new QuoteServerThread().start();
}
}
El objeto QuoteServerThread implementa la lógica principal del servidor de citas.
La clase QuoteServerThread es un Thread que se ejecuta contínuamente esperando peticiones a través del un socket de datagramas.QuoteServerThread tiene dos variables de ejemplar privadas. La primera, llamada socket, es una referencia a un objeto DatagramSocket object. Esta variable se inicializa a null. La segunda, qfs, es un objeto DataInputStream que se ha abierto sobre un fichero de texto ASCII que contiene una lista de citas. Cada vez que se llegue al servidor una petición de cita, el servidor recupera la sigueinte línea desde el stream de entrada.
Cuando el programa principal crea el QuoteServerThread que utiliza el único constructo disponible:
QuoteServerThread() { super("QuoteServer"); try { socket = new DatagramSocket(); System.out.println("QuoteServer listening on port: " + socket.getLocalPort()); } catch (java.net.SocketException e) { System.err.println("Could not create datagram socket."); } this.openInputFile(); }La primera línea de este cosntructor llama al constructor de la superclase (Thread) para inicializar el thread con el nombre "QuoteServer". La siguiente sección de código es la parte crítica del constructor de QuoteServerThread -- crea un DatagramSocket. El QuoteServerThread utiliza este DatagramSocket para escuchar y responder las peticiones de citas de los clientes.El socket es creado utilizando el constructor de DatagramSocket que no requiere arguementos:
socket = new DatagramSocket();Una vez creado usando este constructor, el nuevo DatagramSocket se asigna a algún puerto local disponible. La clase DatagramSocket tiene otro constructor que permite especificar el puerto que quiere utilizar para asignarle el nuevo objeto DatagramSocket. Deberías observar que ciertos puertos estás dedicados a servicios "bien-conocidos" y que no puede utilizados. Si se especifica un puerto que está siendo utilizado, fallará la creación del DatagramSocket.
Después de crear con éxito el DatagramSocket, el QuoteServerThread muestra un mensaje indicando el puerto al que se ha asignado el DatagramSocket. El QuoteClient necesita este número de puerto para construir los paquetes de datagramas destinados a este puerto. Por eso, se debe utilizar este número de puerto cuando ejecute el QuoteClient.
La última línea del constructor de QuoteServerThread llama a un método privado, openInputFile(), dentro de QuoteServerThread para abrir un fichero llamado one-liners.txt que contiene una lista de citas. Cada cita del fichero debe ser un línea en sí misma.
Ahora la parte interesante de QuoteServerThread -- es el método run(). (El método run() sobreescribe el método run() de la clase Thread y proporciona la implementación del thread. Para información sobre los Threads, puede ver Threads de Control.
El método run() QuoteServerThread primero comprueba que se ha creado un objeto DatagramSocket válido durante su construcción. Si socket es null, entonces el QuoteServerThread no podría desviar el DatagramSocket. Sin el socket, el servidor no puede operar, y el método run() retorna.
De otra forma, el método run() entra en un bucle infinito. Este bucle espera continuamente las peticiones de los clientes y responde estas peticiones. Este bucle contiene dos secciones críticas de código: la sección que escucha las peticiones y la que las responde, primero veremos la sección que recibe la peticiones:
packet = new DatagramPacket(buf, 256); socket.receive(packet); address = packet.getAddress(); port = packet.getPort();La primera línea de código crea un nuevo objeto DatagramPacket encargado de recibir un datagrama a través del socket. Se puede decir que el nuevo DatagramPacket está encargado de recibir datos desde el socket debido al constructor utilizado para crearlo. Este constructor requiere sólo dos argumentos, un array de bytes que contiene los datos específicos del cliente, y la longitud de este array. Cuando se construye un DatagramPacket para enviarlo a través de un DatagramSocket, también debe suministrar la dirección de internet y el puerto de destino del paquete. Verás esto más adelante cuando expliquemos cómo responde un servidor a las peticiones del cliente.La segunda línea de código recibe un datagrama desde el socket. La información contenida dentro del mensaje del datagrama se copia en el paquete creado en la línea anterior. El método receive() se bloquea hasta que se reciba un paquete. Si no se recibe ningún paquete, el servidor no hace ningún progreso y simplemente espera.
Las dos líneas siguientes obtienen la dirección de internet y el número de puerto desde el que se ha recibido el datagrama. La dirección Internet y el número de puerto indicado de donde vino el paquete. Este es donde el servidor debe responder. En este ejemplo, el array de bytes del datagrama no contiene información relevante. Sólo la llegada del paquete indica un petición por parte del cliente que puede ser encontrado en la dirección de Internet y el número de puertos indicados en el datagrama.
En este punto, el servidor ha recibido un petición de una cita desde un cliente. Ahora el servidor debe responder. Las seis líneas de código siguientes construyen la respuesta y la envian.
if (qfs == null) dString = new Date().toString(); else dString = getNextQuote(); dString.getBytes(0, dString.length(), buf, 0); packet = new DatagramPacket(buf, buf.length, address, port); socket.send(packet);Si el fichero de citas no se puede abrir por alguna razón, qfs es null. En este caso, el servidor de citas sirve la hora del día en su lugar. De otra forma, el servidor de citas obtiene la siguiente cita del fichero abierto. La línea de código de la sentencia if convierte la cadena en un array de bytes.La tercera línea de código crea un nuevo objeto DatagramPacket utilizado para enviar el mensaje a través del socket del datagrama.. Se puede decir que el nuevo DatagramPacket está destinado a enviar los datos a través del socket porque el constructor lo utiliza para eso. Este cosntructor requiere cuatro argumentos. El primer argumento es el mismo que el utilizado por el constructor utilizado para crear los datagramas receptores: un array de bytes que contiene el mensaje del emisor al receptor y la longitud de este array. Los dos siguientes argumentos son diferentes: una dirección de Internet y un número de puerto. Estos dos argumentos son la dirección completa del destino del datagrama y debe ser suministrada por el emisor del datagrama.
La cuarta línea de código envía el DatagramPacket de esta forma.
El último método de interés de QuoteServerThread es el método finalize(). Este método hace la limpieza cuando el QuoteServerThread recoge la basura cerrando el DatagramSocket. Los puertos son recursos limitados y los sockets asignados a un puerto deben cerrarse cuando no se utilizan.
La clase QuoteClient implementa una aplicación cliente para el QuoteServer. Esta aplicación sólo envía una petición al QuoteServer, espera una respuesta, y cuando ésta se recibe la muestra en la salida estandard. Echemos un vistazo al código.La clase QuoteClient contiene un método -- el método main() para la plicación cliente. La parte superior de main() declara varias variables locales para su utilización:
int port; InetAddress address; DatagramSocket socket = null; DatagramPacket packet; byte[] sendBuf = new byte[256];La siguiente sección procesa los argumentos de la línea de comandos utilizados para invocar la aplicación QuoteClient.if (args.length != 2) { System.out.println("Usage: java DatagramClient <hostname> <port#>"); return; }Esta aplicación requiere dos argumentos: el nombre de la máquina en la que se está ejecutando QuoteServer, y el número de puerto por que el QuoteServer está escuchando. Cuando arranca el QuoteServer muestra un número de puerto. Este es el número de puerto que debe utilizar en la línea de comandos cuando arranque QuoteClient.Luego, el método main() contiene un bloque try que contiene la lógica principal del programa cliente. Este bloque try contiene tres sección principales: una sección que crea un DatagramSocket, una sección que envía una petición al servidor, y una sección que obtiene la respuesta del servidor.
Primero veremos el código que crea un DatagramSocket:
socket = new DatagramSocket();El cliente utiliza el mismo constructor para crear un DatagramSocket que el servidor. El DatagramSocket es asignado a cualquier puerto disponible.Luego, el programa QuoteClient envía una petición al servidor:
address = InetAddress.getByName(args[0]); port = Integer.parseInt(args[1]); packet = new DatagramPacket(sendBuf, 256, address, port); socket.send(packet); System.out.println("Client sent request packet.");La primera línea de código obtiene la dirección Internet del host nombrado en la línea de comandos. La segunda línea de código obtiene el número de puerto de la línea de comandos. Estas dos piezas de información son utilizadas para crear un DatagramPacket destinado a esa dirección de Internet y ese número de puerto. La dirección Internet y el número de puertos deberían indicar la máquina en la que se arrancó el servidor y el puerto por el que el servidor está escuchando.La tercera línea del código anterior crea un DatagramPacket utilizado para envíar datos. El paquete está construido con un array de bytes vacíos, su longitud, y la dirección Internet y el número de puerto de destino del paquete. El array de bytes está vacío porque este datagrama sólo pide la información del servidor. Todo lo que el servidor necesita saber para responder -- la dirección y el número de puerto donde responder -- es una parte automática del paquete.
Luego, el cliente obtiene una respuesta desde el servidor:
packet = new DatagramPacket(sendBuf, 256); socket.receive(packet); String received = new String(packet.getData(), 0); System.out.println("Client received packet: " + received);Para obtener una respuesta del servidor, el cliente crea un paquete receptor y utiliza el método receive() del DatagramSocket para recibir la respuesta del servidor. El método receive() se bloquea hasta que un datagrama destinado al cliente entre a través del socket. Observa que si, por alguna razón, se pierde la respuesta del servidor, el cliente quedará bloqueado débido a la política de no garantías del modelo de datagrama. Normalmente, un cliente selecciona un tiempo para no estár esperando eternamente una respuesta -- si la respuesta no llega, el temporizador se cumple, y el servidor retransmite la petición.Cuando el cliente recibe una respuesta del servidor, utiliza el método getData() para recuperar los datos del paquete. El cliente convierte los datos en una cadena y los muestra.
Después de haber compilado con éxito los programas cliente y servidor, puedes ejecutarlos. Primero debes ejecutar el servidor porque necesitas conocer el número de puerto que muestra antes de poder arrancar el cliente. Cuando el servidor asigna con éxito su DatagramSocket, muestra un mensaje similar a este:QuoteServer listening on port: portNumberportNumber es el número de puerto al que está asignado el DatagramSocket. Utiliza este número para arrancar el cliente.
Una vez que has arrancado el servidor y mostrado el mensaje que indica el puerto en el que está escuchando, puedes ejecutar el programa cliente. Recuerda ejecutar el programa cliente con dos argumentos en la línea de comandos: el nombre del host en el se está ejecutando el QuoteServer, y el número de puerto que mostró al arrancar.Después de que el cliente envíe una petición y reciba una respuesta desde el servidor, deberías ver una salida similar a ésta:
Quote of the Moment: Life is wonderful. Without it we'd all be dead.