Laboratorio de Fundamentos de Sistemas Distribuidos Chat en Java - 1 Tipo de entrega: por grupos de prácticas Fecha límite: sesiones de laboratorio Lugar: Atenea (nou campus) Objetivos del proyecto: • Crear y utilizar objetos • Crear y utilizar variables y métodos de clases • Utilizar try, catch y finally para detectar y manejar excepciones • Implementar clientes y servidores en Java que se comuniquen entre sí utilizando sockets • Implementar un servidor con subprocesamiento múltiple Resultados a entregar: • Los resultados esperados de esta práctica es la carpeta del Workspace del Eclipse que contiene los códigos de las aplicaciones. Coloca los códigos de cada sesión en un package llamado Chat_X Descripción de la aplicación En este proyecto vamos a desarrollar una aplicación distribuida con arquitectura cliente/servidor. La aplicación distribuida es un Chat que permite a los clientes darse de alta y de baja en el Chat, enviar mensajes a todos los clientes en línea y pedir la lista de clientes en línea. El código del cliente se programará en la clase ClienteChat (ClienteChat.java) y el código del servidor en las clases ServidorChat (ServidorChat.java) y Cliente (Cliente.java) Estudio Previo 1. Estudio de los apartados (“Sockets, Clients, and Servers”, y “Programming Examples”) de la sección 10.4 (“Networking”) del libro “Introduction to Programming Using Java Version 4.0, July 2002” de David J. Eck (en el apartado comentarios tienes cómo acceder on-line al contenido del libro) 2. Compila, ejecuta y analiza detenidamente el ejemplo DateServer.java y DateClient.java. Esta aplicación utiliza un objeto de la clase PrintWriter para escribir datos en un socket y un objeto de la clase Reader para leer datos de un socket. Nota: Para compilar DateServer.java y DateClient.java necesitas la clase TextIO.class que es una clase que no pertenece al SDK. Coloca el código fuente de TextIO.java en el mismo package que el código DateServer.java y DateClient.java para que puedas usar la clase TextIO. La clase TextIO sólo se usa como clase de soporte (no es necesario que la entiendas para realizar la práctica) 3. Prueba a NO arrancar el servidor de la aplicación DateServer.java y DateClient.java y arranca el cliente. Analiza la secuencia de sentencias que se ejecutan cuando ejecutas el cliente (tráelo a la sesión de laboratorio en papel) 4. Compila, ejecuta y analiza detenidamente el ejemplo CLChatClient.java y CLChatServer.java. Esta aplicación utiliza un objeto de la clase PrintWriter para 1 escribir datos en un socket y un objeto de la clase TextReader para leer datos de un socket. Nota: Para compilar estos ejemplos sucede lo mismo que en los ejemplos del apartado anterior (aquí necesitas la clase TextReader.class) Coloca el código fuente de TextReader.java en el mismo package que el código de CLChatClient.java y CLChatServer.java para que puedas compilar los ejemplos. En el apartado comentarios tienes donde hay una explicación de esta clase y de sus métodos. 5. Prueba a arrancar el servidor de la aplicación CLChatClient.java y CLChatServer.java y NO arranques el cliente de la aplicación. Analiza la secuencia de sentencias que se ejecutan en el servidor (tráelo a la sesión de laboratorio en papel) 6. Analiza el protocolo que utiliza el servidor de nuestro chat formado por las clases ServidorChat.java y Cliente.java. Si programamos el cliente en una clase ClienteChat.java, ¿Qué deberá enviar ClienteChat por el socket para registrarse? (tráelo a la sesión de laboratorio en papel) Comentarios Introduction to Programming Using Java Version 4.0, July 2002 El libro “Introduction to Programming Using Java Version 4.0, July 2002” de David J. Eck (eck@hws.edu) está accessible on-line desde http://www.faqs.org/docs/javap/index.html El código fuente de las aplicaciones http://www.faqs.org/docs/javap/source/index.html lo puedes bajar de También puedes bajarte el manual y los ejemplos en un archivo comprimido para “Windows”, “Linux/UNIX and MacOS X” y “Linux/UNIX” ( apartado “Downloading Links” ) o sólo el manual en un archivo pdf. Clase TextReader Se describe la clase y cómo usarla en la sección 10.1 (“Streams, Readers, and Writers”) del manual Introduction to Programming Using Java Version 4.0, July 2002. Cómo debugar en el Eclipse Tienes un ejemplo gráfico de cómo hacerlo en la ayuda del Eclipse (abre el Help del Eclipse y busca el tema “Debugging your programs”) Streams, Readers and Writers Puedes consultar información de estas clases en las secciones 10.1 (“Streams, Readers, and Writers”), 10.2 (“Files”) y 10.3 (“Programming with Files”) del manual Introduction to Programming Using Java Version 4.0, July 2002. Sockets En Java la programación de aplicaciones que se comunican en Internet a través del protocolo TCP (Transmisión Control Protocol) se realiza desde la capa de aplicación, sin necesidad de interactuar con la capa de transporte. Para ello se utilizan las clases Socket y ServerSocket, que proporcionan comunicación en red independiente de la plataforma. 2 Un socket es un punto final de una comunicación bidireccional entre 2 programas ejecutándose en la red. Un socket se asocia a un número de puerto de forma que el protocolo de la capa de transporte (en nuestro caso TCP) pueda identificar la aplicación a la que están destinados los datos. Así, los sockets permiten crear la ilusión al programador de que mandar/recibir datos por la red es algo similar a escribir/leer datos de un disco. El servidor corre sobre una máquina y tiene un socket asociado a un puerto determinado, por el que está esperando la petición de una conexión de un cliente. El cliente conoce el nombre de la máquina donde se está ejecutando el servidor y el número de puerto por el que está escuchando el servidor. El cliente realiza una petición de conexión al servidor con el nombre de máquina del servidor y el puerto. Si no se produce ningún error el servidor acepta la conexión. Crea un nuevo socket para atender la petición de ese cliente y mantiene el socket original que está escuchando para atender peticiones de nuevos clientes. A partir de este momento el cliente y el servidor pueden comunicarse leyendo o escribiendo en sus sockets. La clase Socket implementa un lado de la conexión bidireccional entre dos aplicaciones Java en la red. Permite crear conexiones de clientes con servidores. La clase ServerSocket implementa el otro lado de la conexión bidireccional. En concreto permite crear un socket que puede escuchar en un puerto y aceptar conexiones de clientes. Ambas son implementaciones que ocultan los detalles de las plataformas concretas (hardware y sistema operativo) donde se ejecutan las aplicaciones cliente y servidor. Sesión de laboratorio Ejercicio 1: sockets Escribe un programa cliente llamado ClienteChat.java que se conecte y desconecte del servidor formado por las clases ServidorChat.java y Cliente.java que te damos. El cliente debe realizar esta secuencia: 1. 2. 3. 4. pedir el nick al usuario del Chat registrarse en el servidor (enviando el comando REGISTER y el nick) pedir al usuario un comando y ejecutar el comando Si el comando es CLOSE finalizar la aplicación. En caso contrario volver a 3.- De momento los comandos que soporta nuestra aplicación son conectarse al servidor y desconectarse. Si el usuario introduce cualquier otro comando distinto se printará un mensaje por pantalla informativo en la consola de ClienteChat y se pedirá al usuario que introduzca un nuevo comando. Utiliza a clase BufferedReader para leer de un socket. Utiliza la clase PrintWriter para escribir en un socket. Utiliza la clase JOptionPane para introducir el nick del usuario y para leer los comandos que introduce el usuario. 3 Chat en Java - 2 Estudio Previo 1. Estudio de los apartados (“ArrrayLists ”, y “Vectors”) de la sección 8.3 (“Dynamic Arrays, ArrayLists, and Vectors”) del libro “Introduction to Programming Using Java Version 4.0, July 2002” de David J. Eck (en el apartado comentarios tienes cómo acceder on-line al contenido del libro) 2. Estudio del apartado (“The Special Variables this and super”) de la sección 5.5 (“this and super”) del libro “Introduction to Programming Using Java Version 4.0, July 2002” de David J. Eck (en el apartado comentarios tienes cómo acceder on-line al contenido del libro) 3. Programa una clase llamada MyClass (en un archivo MyClass.java) que tenga un método llamado myMethod. El método myMethod imprime por pantalla el mensaje “Soy un método de la clase MyElement”. Programa una clase llamada CheckAL (en un archivo CheckAL.java) que almacene en un objeto de la clase ArrayList objetos de la clase MyClass, recorra el ArrayList y ejecute para cada elemento el método myMethod programado en la clase MyClass. (tráelo a la sesión de laboratorio en papel) Comentarios Introduction to Programming Using Java Version 4.0, July 2002 El libro “Introduction to Programming Using Java Version 4.0, July 2002” de David J. Eck (eck@hws.edu) está accessible on-line desde http://www.faqs.org/docs/javap/index.html El código fuente de las aplicaciones http://www.faqs.org/docs/javap/source/index.html lo puedes bajar de También puedes bajarte el manual y los ejemplos en un archivo comprimido para “Windows”, “Linux/UNIX and MacOS X” y “Linux/UNIX” (apartado “Downloading Links” ) o sólo el manual en un archivo pdf. La clase Vector La clase Vector permite almacenar un array dinámico de objetos e ir añadiendo objetos o eliminándolos, así como llamar a otros métodos que se necesitan cuando trabajas con cualquier conjunto de objetos Analiza este fragmento de código que crea un Vector de objetos, añade un objeto de la clase Client al Vector y llama al atributo name de cada objeto Client que hay en el vector, sin necesidad de conocer cuantos clientes hay en el vector (método size() de la clase Vector): import java.util.* ; Vector clients = new Vector(); Client cl =new Client(); clients.addElement(cl); for (int i=0; i<clients.size();i++){ ((Client)(clients.elementAt(i))).name; } clients.removeElement(cl); clients.toString(); Es importante hacer notar varios aspectos en el código: 4 • La sentencia import java.util.*; permite utilizar todo lo que contiene el package java.util de Java y la clase Vector se encuentra en ese package(similar al include de C) • El método addElement() introduce un elemento en el Vector • El método removeElement() elimina un elemento del Vector • El método size() de la clase Vector permite conocer el número de objetos que contiene el objeto Vector sobre el que se aplica (clients). • El método element.At(i) devuelve una referencia que apunta al objeto que hay en la posición i del objeto Vector sobre el que se aplica (en nuestro caso apunta un objeto Client). • Como los elementos de un Vector pertenecen por defecto a la clase padre más genérica (Object) para acceder al atributo name de la clase Cliente hay que forzar la conversión del tipo (como en C) ((Client)(clients.elementAt(i))).name; Sesión de laboratorio Ejercicio 2: La clase Vector En este ejercicio vamos a añadir a nuestro servidor la funcionalidad de almacenar la información de los clientes que están conectados en el servidor y vamos a añadir a nuestra aplicación un nuevo comando (comando SEND_CLIENT_LIST) que permite a un usuario del chat saber los nicks de los usuarios registrados en el servidor. Vector clientList Cada vez que un usuario se conecta (comando REGISTER), se añade un objeto de la clase Cliente en el Vector clientList. Cada vez que un usuario se desconecta (comando CLOSE), se elimina el objeto que representa a ese usuario del Vector clientList. Comando SEND_CLIENT_LIST El comando lo declaramos como una constante llamada SEND_CLIENT_LIST de tipo char que contiene el carácter ‘:’ El cliente envía SEND_CLIENT_LIST (‘:’) al servidor y recibe una línea por cada usuario registrado en el servidor “< + el atributo name de ese cliente” más una línea con “>” que indica fin de comando SEND_CLIENT_LIST (‘:’). Por ejemplo, si el servidor tuviera dos clientes registrados “Pablo” y “Toñi”, se recibiría: >Pablo >Toñi < Los caracteres ‘>’ y ‘<’ los declaramos como constantes de tipo char llamadas CLIENT_INFO y END_CLIENT_INFO . 5 a) Modifica el código de nuestra aplicación añadiendo el Vector clientList en la parte de servidor (se declara e inicializa en ServidorChat y se pasa por parámetro cuando se crea un objeto Cliente), actualizando el Vector clientList cada vez que un usuario se conecte (comando REGISTER) o se desconecte (comando CLOSE) y programando el nuevo comando en nuestro Chat (tanto en la parte de cliente como en la parte de servidor) En la parte de ClienteChat crea un nuevo Vector cada vez que se pida un SEND_CLIENT_LIST al servidor y almacena en él los nicks de los usuarios que vas recibiendo. Cuando recibas fin del comando SEND_CLIENT_LIST (<) printa por pantalla la lista de usuarios que tienes en el vector Como nuestro Chat sólo atiende a un ClienteChat (es secuencial), sólo permite un ClienteChat conectado al Chat no podemos probar que SEND_CLIENT_LIST devuelva más de un name. Nota: Existen varias formas de compartir la lista de usuarios. Una es utilizando el comando ‘static’ y otra es pasando una referencia del objeto ‘clientList’. Cualquiera de las dos formas es valida. Chat en Java - 3 Estudio Previo 1. Estudio del tutorial “Threads (Scheme)” de javaHispano. 2. Programa una clase llamada MyThread (en un archivo MyThread.java) que sume enteros de 1 a 10.000 y una clase llamada Prueba (en un archivo Prueba.java) que cree un thread y lo ponga en marcha (tráelo a la sesión de laboratorio en papel) Comentarios javaHispano El web javaHispano (http://www.javahispano.org) mantiene noticias, manuales, tutoriales de Java en castellano. El manual de esta parte de la práctica se encuentra en la sección Java / Tutoriales / J2SE (http://www.javahispano.org/tutorials.type.action?type=j2se) bajo el nombre “Threads (Scheme)” Threads Los threads o hilos de ejecución son segmentos de código de un programa que se ejecutan secuencialmente de modo independiente de otras partes del programa. Un proceso puede estar constituido por uno o más threads. Un thread esta compuesto por: una CPU virtual, el código que ejecuta el procesador y los datos sobre los que trabaja el código. Dos threads comparten código si ejecutan código de objetos que pertenecen a la misma clase acceso a un objeto común. Los datos pueden ser o no compartidos por diferentes threads. Eso ocurre cuando tienen 6 Los threads se utilizan para aislar y coordinar tareas. Sin el uso de threads hay aplicaciones que son casi imposibles de programar: • Las que tienen tiempos de espera importantes entre etapas • Las que consumen muchos recursos de CPU e impiden que el procesador atienda simultáneamente otros eventos o peticiones del usuario (por ejemplo, un chat) Java soporta la ejecución paralela de varios threads. Los threads en una misma máquina virtual comparten recursos (por ejemplo memoria). Los threads en varias máquinas virtuales necesitan mecanismos de comunicación para compartir información. Hay dos modos de conseguir threads en Java: • Extender la clase Thread, • Implementando la interface Runnable. Creación extendiendo la clase Thread. El nuevo thread se crea extendiendo la clase Thread y redefiniendo el método run(). public class ClienteThread extends Thread { // Atributos y métodos de la clase . . . public void run() { // código propio del thread } } public class Test { public static void main(String[] args){ while (true) { socketClient = listener.accept(); ClienteThread c =new ClienteThread(socketClient); c.start(); } } } Creación implementando la interface Runnable. El constructor de la clase Thread recibe un argumento que debe ser una instancia de una clase que implementa la interface Runnable, implementando el método run(). public class ClienteThread implements Runnable { // Atributos y métodos de la clase . . . public void run(){ // código propio del thread } } public class Test{ public static void main(String args[]){ ClienteThread r = new ClienteThread(); Thread t = new Thread(r); t.start(); } } 7 Sesión de laboratorio Ejercicio 3: Threads-1 En este ejercicio vamos a añadir a nuestro servidor la funcionalidad de atender a varios clientes al mismo tiempo (concurrentemente). Así podremos comprobar que los comandos REGISTER y CLOSE, programados anteriormente, funcionan tal y como se esperaba. También podremos añadir más comandos a nuestro Chat. Tal como funciona nuestra aplicación en este momento para cada ClienteChat que se conecta a ServidorChat se crea un objeto de la clase Cliente donde se almacena su nick (name) y el socket asignado en tiempo de ejecución en la máquina servidor para comunicarse con la máquina cliente (ClienteChat.java), etc. a) Modifica el código de ServidorChat.java y de Cliente.java de forma que se cree un thread para cada ClienteChat.java que se conecte a ServidorChat.java. Nota: Si dentro del método run() de un thread utilizas métodos que generan excepciones, es necesario poner ese código dentro de un bloque try/catch b) Comprueba que los comandos REGISTER y CLOSE, programados anteriormente, funcionan tal y como se esperaba. Chat en Java – 4 Estudio Previo 3. Estudio del ejemplo de uso de Threads “Ejemplo.java” (que te facilitamos junto con el código y que también puedes encontrar en la documentación de Wikipedia). 4. Modifica el ejemplo para que muestre por pantalla el nombre del thread que se está ejecutando. 5. Ejecuta el nuevo ejemplo varias veces, analízalo y describe cuándo cada uno de los threads ejecutan código, si aunque no printen por pantalla tienen asignada la CPU, qué printa por pantalla cada thread y si ambos threads finalizan cuando se ejecuta la sentencia System.exit(0) (tráelo a la sesión de laboratorio en papel) Nota: Si es necesario repasa el tutorial “Threads (Scheme)” de javaHispano. Comentarios javaHispano El web javaHispano (http://www.javahispano.org) mantiene noticias, manuales, tutoriales de Java en castellano. El manual de esta parte de la práctica se encuentra en la sección Java / Tutoriales / J2SE (http://www.javahispano.org/tutorials.type.action?type=j2se) bajo el nombre “Threads (Scheme)” Sesión de laboratorio Ejercicio 4: Threads-2 En este ejercicio vamos a añadir a nuestro chat que la parte de cliente sea capaz de leer del socket y de leer de teclado concurrentemente (en un chat en cualquier momento alguien 8 puede enviarte un mensaje mientras estás escribiendo tú un nuevo mensaje o pudiendo un nuevo comando al servidor). a) Basándote en el código de Ejemplo.java modifica el código de ClienteChat.java, para que en el main se cree un objeto ClienteChat, se creen dos threads de ese objeto, uno de los threads ejecute el método sendCommand() y el otro thread el método listenServer(). El método sendCommand() tendrá el código del ClienteChat.java del ejercicio 3 que se encarga de leer comando del usuario y enviarlo a la parte Servidor de nuestra aplicación El método listenServer() leerá continuamente lo que le envía el servidor (Cliente.java) y cuando el servidor envíe el mensaje de despedida (“Bye Bye …”) cerrará el socket y la aplicación de ClienteChat.java. Chat en Java – 5 Estudio Previo 1. Estudio de la clase String de la sección “4.2. Strings and Characters” del libro “Java in a Nutshell, 4th Edition” de David Flanagan (en el apartado comentarios tienes cómo acceder on-line al contenido del libro). Nota: No es necesario para realizar la práctica estudiar ninguno de los subapartados 4.2.X. 2. Estudio a. b. c. d. los siguientes métodos de la clase String: int indexOf(int ch) boolean startsWith(String prefix) String substring(int beginIndex) String substring(int beginIndex, int endIndex) Nota: Tienes un ejemplo de código de la sección “4.2. Strings and Characters” del libro “Java in a Nutshell, 4th Edition” de David Flanagan. También puedes utilizar la “Documentación en línea de las clases del SDK versión 1.4” (en el apartado comentarios tienes cómo acceder on-line) Comentarios Java in a Nutshell, 4th Edition El libro “Java in a Nutshell, 4th (http://proquest.safaribooksonline.com/0596002831) biblioteca de la UPC (http://bibliotecnica.upc.edu/). Edition” de está accesible David on-line Flanagan desde la Puedes consultar el libro tanto desde un PC conectado a la red de la UPC como desde fuera de la UPC (en este caso debes configurar tu navegador tal y como explica el link “Accés remot” http://bibliotecnica.upc.es/remot/) Documentación en línea de las clases del SDK versión 1.4 API del SDK http://java.sun.com/j2se/1.4/docs/api/index.html) 9 Sesión de laboratorio Ejercicio 5: La clase String En este ejercicio vamos a añadir a nuestro chat nuevos comandos. Los comandos son los siguientes: mensaje Este comando envía un mensaje a todos los usuarios conectados en este momento al chat. El mensaje no debe empezar ni por el carácter del comando SEND_CLIENT_LIST (‘:’), ni por el REGISTER (‘[’), ni por el CLOSE (‘]’), ni por el PRIVADO (‘/’, reservado para un futuro comando) Este mensaje aparece en cada una de las pantallas de los usuarios conectados y la pantalla del usuario conectado muestra: nickOrigen: mensaje /nickDestino mensaje Este comando envía un mensaje privado al usuario cuyo nick equivale al nickDestino especificado en el comando. Este mensaje únicamente aparece en la pantalla del usuario que envía el mensaje y la pantalla del usuario destinatario muestra: Privado de nickOrigen a nickDestino: mensaje a) Modifica el código de ServidorChat.java, Cliente.java y de ClienteChat.java para que soporte estos nuevos comandos. Ejercicio 6: Mejorando la aplicación En este ejercicio se trata de mejorar la aplicación del chat que tenemos con algún o algunos detalles que se te ocurran. Por ejemplo: que no se permita a un usuario registrarse con un nick de un cliente ya registrado, añadir un nuevo comando, añadir interficie gráfica a nuestra aplicación, etc… 10