Herramientas y Lenguajes de Programaci´ on Coromoto Le´ on Hern´ andez 1,5 cr´editos Universidad de La Laguna Programa de Doctorado de F´ısica e Inform´atica Dpto. de Estad´ıstica, I.O. y Computaci´on 2005-2006
Resumen El curso “Herramientas y Lenguajes de Programaci´on” del programa de doctorado de “F´ısica e Inform´atica” est´a clasificado como metodol´ogico (optativo). La Figura 1 muestra los distintos itinerarios que se pueden seguir en el programa de doctorado.
Figura 1: Itinerarios del Programa de Doctorado El curso est´a dividido en tres partes. Este material cubre la parte del curso relacionada con la manipulaci´on de herramientas inform´aticas y el desarrollo de aplicaciones.
´Indice general 1. Introducci´ on 1.1. Desarrollo de aplicaciones Inform´aticas . . . . . 1.2. Herramientas . . . . . . . . . . . . . . . . . . . 1.2.1. Proceso de Compilaci´on . . . . . . . . . 1.2.2. Compilaci´on de programas formados por 1.2.3. Creaci´on de Librer´ıas . . . . . . . . . . 1.2.4. Documentaci´on . . . . . . . . . . . . . . 1.3. Lenguajes de Programaci´on . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . varios m´odulos . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
2 . 2 . 3 . 4 . 5 . 6 . 8 . 11
2. Programas Java: Aplicaciones 14 2.1. Ejemplo de aplicaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3. Programas Java: applets 17 3.1. Ejemplo de applet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4. Creaci´ on de Threads 20 4.1. La clase Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.2. La interfaz Runnable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 4.3. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 5. Sincronizaci´ on de Threads 23 5.1. Exclusi´on mutua y secuencializaci´on . . . . . . . . . . . . . . . . . . . . . . 23 5.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 6. Direcciones IP y Nombres de Dominio 25 6.1. La clase InetAddress . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 6.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 7. Las 7.1. 7.2. 7.3.
clases Java DatagramPacket y Introducci´on . . . . . . . . . . . La clase DatagramPacket . . . La clase DatagramSocket . . .
DatagramSocket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
28 28 29 30
2
Herramientas y Lenguajes de Programaci´on 05-06
7.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 8. Las clases Java Socket y ServerSocket 8.1. Introducci´on . . . . . . . . . . . . . . . . . . . . . . 8.2. Sockets . . . . . . . . . . . . . . . . . . . . . . . . 8.2.1. La clase Socket . . . . . . . . . . . . . . . 8.2.2. La clase ServerSocket . . . . . . . . . . . 8.3. La clase Thread y la implementaci´on de servidores 8.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . 8.5. C´odigos Fuente del Servidor de chistes iterativo . . 8.6. C´odigos Fuente del Servidor de chistes concurrente
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
34 34 35 36 38 39 40 42 45
Cap´ıtulo 1
Introducci´ on El principal objetivo de est´a parte del curso de doctorado es dotar al alumno de las habilidades necesarias para la manipulaci´on de herramientas inform´aticas que le permitan realizar una labor investigadora eficaz. En el desarrollo de un proyecto inform´atico uno de los aspectos m´as importantes es la elecci´on del lenguaje m´as apropiado para su implementaci´on y las herramientas a utilizar. Existen m´ ultiples herramientas inform´aticas y m´ ultiples lenguajes de programaci´on dependiendo del a´rea de estudio. Una vez elegido el lenguaje, se ha de recabar informaci´on sobre las herramientas disponibles y realizar un estudio acerca de cu´al se adapta mejor a las necesidades del proyecto a realizar. As´ı pues, es necesario iniciar al alumnado en el desarrollo sistem´atico y ordenado de aplicaciones inform´aticas integradas en proyectos de investigaci´on. En este curso, se especifican de forma gen´erica las fases que componen el desarrollo e implementaci´on de una aplicaci´on inform´atica y se formalizan las mismas con ejemplos concretos de realizaci´on. Los objetivos concretos del curso son: Manejar los fundamentos b´asicos del sistema operativo UNIX (Linux). Conocer las herramientas inform´aticas UNIX (Linux) fundamentales para el desarrollo de aplicaciones. Comprender los principios b´asicos de la programaci´on imperativa, y oritentada a objetos. Reconocer la importacia de la generaci´on de documentaci´on que acompa˜ na a todo proyecto inform´atico. Comprender como dise˜ nar algoritmos eficientes utilizando distintas t´ecnicas algor´ıtmicas.
1.1.
Desarrollo de aplicaciones Inform´ aticas
En este apartado se expone de forma escueta cu´ales son las partes que constituyen el desarrollo de un programa, esto es, las etapas de desarrollo de software. 3
Herramientas y Lenguajes de Programaci´on 05-06
4
Figura 1.1: Etapas del desarrollo de software
Las etapas de desarrollo de software se pueden resumir en: 1. Dise˜ nar una soluci´on, proponiendo un algoritmo. 2. Traduccir la soluci´on a pseudoc´odigo. 3. Implementar un programa en un lenguaje de programaci´on (en el caso que estudiaremos Java). 4. Compilar el programa. 5. Realizar pruebas de ejecuci´on. Aunque las etapas se presentan de forma secuencial, es habitual cometer errores que provoquen el tener que regresar a fases anteriores. El esquema que se muestra en la Figura 1.1 es v´alido para programas no demasiado complejos. Para los grandes proyectos inform´aticos exite un conjunto de t´ecnicas de desarrollo que pueden estudiarse en cualquier libro de Ingenier´ıa del Software, por ejemplo, R.S. Pressman, Ingenier´ıa del Software: Un enfoque Pr´atico, McGraw-Hill, 4 edici´on, 1998. ISBN 84-481-1186-9.
1.2.
Herramientas
En el desarrollo de un proyecto inform´atico un aspecto importante es qu´e tipo de herramientas se han de utilizar y el lenguaje m´as apropiado para su implementaci´on. Existen
Herramientas y Lenguajes de Programaci´on 05-06
5
m´ ultiples herramientas inform´aticas y m´ ultiples lenguajes de programaci´on dependiendo del a´rea de estudio. El sistema operativo elegido para realizar los ejercicios pr´actico es este curso es Unix (Linux), debido a la diponibilidad de gran cantidad de herramientas que facilitan al programador el desarrollo y mantenimiento de sus programas. En este ep´ıgrafe, se har´a un repaso de algunas de la principales herramientas que existen en el entorno Unix para el desarrollo de programas en el lenguaje de programaci´on Java. Estas herramientas incluyen los compiladores - en los cuales se pueden encontrar diferencias seg´ un la versi´on Unix utilizada-, los int´erpretes de la m´aquina virtual de Java (l´ınea de comandos - stand-alone o herramientas de visualizaci´on - appletviewer ), la creaci´on de paquetes o bibliotecas, etc.
1.2.1.
Proceso de Compilaci´ on
Un traductor es un programa inform´atico que traduce de un lenguaje fuente a un lenguaje objeto. Un compilador es un traductor en el que el lenguaje fuente es un Lenguaje de Programaci´on de Alto Nivel, y el lenguaje objeto es Lenguaje Ensamblador o Lenguaje M´aquina. El proceso de traducci´on que tiene que realizar un compilador, se puede dividir en los siguientes pasos: Preprocesamiento Compilaci´on (propiamente dicho) y Optimizaci´on Generaci´on de C´odigo Objeto Enlace ( linker ) No es uno de los objetivos de este curso el estudiar en profundidad el proceso de compilaci´on sino utilizar las herramientas disponibles para llevarlo a cabo. Para un estudio en detalle de los compiladores recomendamos el libro: A. Aho, R. Sethi, J. Ullman, Compiladores. Principios, T´ecnicas y Herramientas, Adisson-Wesley Iberoamericana, ISBN 0-201-62903-8 La Figura 1.2 muestra el conjunto de herramientas que proporciona el paquete GCC de GNU para los compiladores de los lenguajes C y C++. El prepocesador ( cpp ) se invoca con el comando: g++ programa.C -E -o programa.i El compilador propiamente dicho ( comp ) se llama con la orden: g++ programa.C -S -o programa.s La llamada al ensamblador ( as ) utiliza la opci´on -c: g++ programa.C -c -o programa.o
Herramientas y Lenguajes de Programaci´on 05-06
6
Figura 1.2: Herramientas del compilador de GCC
El editor de carga y enlace (ld) se invoca autom´aticamente sin especificar ninguna opci´on: g++ programa.C Se obtiene el fichero a.out que ya es ejecutable. Los ficheros intermedios generados se almacenan en /tmp y se borran cuando termina todo el proceso de compilaci´on. En el caso de Java, se habla de un lenguaje de programaci´on y de una plataforma de ejecuci´on. Como lenguaje de programaci´on de alto nivel se ha de destacar que es completamente orientado a objetos y tiene definida una sintaxis y una sem´antica. La plataforma de ejecuci´on de Java est´a compuesta por: La M´aquina Virtual de Java (Java Virtual Machine - JVM) Los APIs (Application Programming Interfaces - Paquetes)
1.2.2.
Compilaci´ on de programas formados por varios m´ odulos
Consideremos la divisi´on de la implementaci´on de un problema C/C++ en dos ficheros: programa1.cc y programa2.cc. Seg´ un se muestra en la Figura 1.3, el primer paso consiste en editar dichos ficheros (con el editor vi, por ejemplo). A continuaci´on, la orden que se deber´ıa emitir para generar la aplicaci´on es:
Herramientas y Lenguajes de Programaci´on 05-06
7
Figura 1.3: Compilaci´on de varios m´odulos separados
g++ programa1.c programa2.c Se ha de tener en cuenta, que la llamada al enlazador (linker) se realiza de forma autom´atica. Por lo tanto, los dos ficheros intermedios, programa1.o y programa2.o se borraran al finalizar la operaci´on. Si se quieren conservar ambos es necesario compilar con los comandos: g++ -c programa1.c g++ -c programa2.c g++ programa1.o programa2.o La procedencia de los ficheros que aporta el sistema operativo al proceso de compilaci´on es la siguiente: Ficheros de Cabecera - directorio /usr/include Librer´ ıas - directorios /lib, /usr/lib
1.2.3.
Creaci´ on de Librer´ıas
La creaci´on de librer´ıas (o bibliotecas) aporta modularidad y portabilidad a los programas. Una librer´ıa es un fichero que est´a compuesto por una colecci´on de otros ficheros llamados miembros de la librer´ıa. La estructura de una librer´ıa posibilita la extracci´on de
Herramientas y Lenguajes de Programaci´on 05-06
8
sus miembros. Cuando se a˜ nade un fichero a una librer´ıa, los datos de ´este y su informaci´on administrativa (permisos, fechas, propietario, grupo, etc.) se introduce en ´el. Para una descripci´on detallada de las opciones de esta herrmienta visite la p´agina de manual (man ar). Las funciones b´asicas de ar son: crear, modificar y extraer miembros. ar [-] opciones [miembro] librer´ ıa [ficheros] Entre las opciones se deben distinguir aquellas que son obligatorias y los modificadores. Cuando se emite una orden ar es necesario que haya una obligatoria y s´olo una. Los paquetes Java agrupan las clases en librer´ıas (bibliotecas). Los paquetes Java se utilizan de forma similar a como se utilizan las librer´ıas en C++, s´olo que en Java se agrupan clases y/o interfaces. En los paquetes las clases son u ´nicas, comparadas con las de otros paquetes, y permiten controlar el acceso. Esto es, los paquetes proporcionan una forma de ocultar clases, evitando que otros programas o paquetes accedan a clases que son de uso exclusivo de una aplicaci´on determinada. Los paquetes se declaran utilizando la palabra reservada package seguida del nombre del paquete. Esta sentencia debe estar al comienzo del fichero fuente. Concretamente debe ser la primera sentencia ejecutable del c´odigo Java, excluyendo, los comentarios y espacios en blanco. Por ejemplo: package figuras; public class Circulo { . . . }
En este caso, el nombre del paquete es figuras. La clase Circulo se considera como parte del paquete. La inclusi´on de nuevas clases en el paquete es simple, se ha de colocar la misma sentencia al comienzo de los ficheros que contengan la declaraci´on de las clases. Cada uno de los ficheros que contengan clases pertenecientes a un mismo paquete, deben incluir la misma sentencia package, y solamente puede haber una sentencia package por fichero. La sentencia package colocada el comienzo de un fichero fuente afectar´a a todas las clases que se declaren en ese fichero. Se pueden referenciar paquetes precediendo con su nombre la clase que se quiere usar. Tambi´en se puede usar la palabra reservada import, si se van a colocar m´ ultiples referencias a un mismo paquete. La sentencia import se utiliza para incluir una lista de paquetes en los que buscar una clase determinada, y su sintaxis es: import nombre_paquete.Nombre_Clase; Esta sentencia, o grupo de ellas, deben aparecer antes de cualquier declaraci´on de clase en el c´odigo fuente.
Herramientas y Lenguajes de Programaci´on 05-06
1.2.4.
9
Documentaci´ on
La herramienta javadoc genera p´aginas HTML de documentaci´on del API a partir de ficheros con c´odigo fuente Java. En la l´ınea de comandos se le puede pasar a javadoc una serie de paquetes o ficheros Java para los que se desea generar documentaci´on. Se genera documentaci´on para el paquete especificado o para los ficheros fuentes Java individuales que se listen en la l´ınea de comandos. Se genera un fichero .html por cada fichero .java que se encuentre. Tambi´en se genera la jerarqu´ıa de clases (tree.html) y un ´ındice con todos los miembros que ha detectado (AllNames.html). La utilidad javadoc extrae informaci´on de los siguiente elementos: Paquetes Clases e interfaces p´ ublicas M´etodos p´ ublicos y protegidos Datos p´ ublicos y protegidos Se puede a˜ nadir documentaci´on para todos estos entes a trav´es de comentarios de documentaci´on en el c´odigo fuente. Estos comentarios pueden incluir marcas HTML. Un comentario de documentaci´on est´a formado por todos los caracteres incluidos entre /** que indica el comienzo del comentario y */ que indica el final. En todas las l´ıneas intermedias los caracteres * a la izquierda se ignoran, y tambi´en todos los espacios y tabuladores que precedan a ese car´acter *. /** * Este es un comentario de documentaci´ on */
Se pueden incluir etiquetas HTML dentro de un comentario de documentaci´on, aunque no deben utilizarse las etiquetas , ,... o l´ıneas horizontales , porque javadoc crea una estructura completa para el documento y estas marcas interfieren con el formato general de ese documento. Cada comentario de documentaci´on puede incluir texto libre seguido de etiquetas de documentaci´ on. Estas etiquetas comienzan siempre con el signo @ y deben situarse al principio de la l´ınea (tener en cuenta que todo lo que haya hasta el primer car´acter * se ignora) y todas las etiquetas con el mismo nombre deben agruparse juntas dentro del comentario de documentaci´on. Marcas de documentaci´ on de clases e interface @see nombre_de_clase
A˜ nade un enlace a la clase especificada en la zona “See Also”. Por ejemplo:
Herramientas y Lenguajes de Programaci´on 05-06
@see @see @see @see @see @see
10
java.lang.String String String#equals java.lang.Object#waint(int) Character#MAX_RADIX Especif. Java
Se utiliza el car´acter # para separar el nombre de una clase del nombre de uno de sus campos de datos, m´etodos o constructores. @version texto-version
A˜ nade una entrada “Version”. El texto no tiene que tener formato especial. @author texto-autor
A˜ nade una entrada “Author”. El texto no tiene que tener formato especial. @since texto
Este texto no tiene una estructura especial. Se utiliza para indicar desde qu´e fecha o desde qu´e versi´on se ha introducido el cambio o caracter´ıstica que indica el texto. @deprecated texto
A˜ nade un comentario indicando que el m´etodo est´a desautorizado y no deber´ıa utilizarse porque puede dejar de ser soportada por el API. La convenci´on que se sigue es indicar en el texto la funci´on o m´etodo por quien se ha sustituido. Ejemplo de comentario de una clase: /** * Clase que representa la figura geom´ etrica cilindro * Por ejemplo: * * Cilindro c = new Cilindro(1.0); * double d = c.volumen(); * * * @see figuras.Circulo * @see figuras.ObjetoGeometrico * @version 1.5 14 Mar 04 * @author Coromoto Leon Hernandez */ public class Cilindro extends Circulo { . . . }
Herramientas y Lenguajes de Programaci´on 05-06
11
Marcas de documentaci´ on de campos de datos La u ´nica marca especial que se puede incluir es la marca @see. Ejemplo de comentario de un campo de datos: /** * El palo de bastos */ public static final int BASTOS = 1;
Marcas de documentaci´ on de constructores y m´ etodos Pueden ser marcas @see y adem´as: @param parametro descripcion
A˜ nade un par´ametro a la secci´on “Parameters”. La descripci´on puede continuar en la l´ınea siguiente. @return descripcion
A˜ nade una secci´on “Return”, que debe contener la descripci´on del valor a devolver. @throws exception descripcion
A˜ nade una entrada “Throws”, que contiene el nombre de la excepci´on que puede ser lanzada por el m´etodo. La excepci´on estar´a enlazada con su clase en la documentaci´on. @see nombre_de_clase
A˜ nade un enlace a la clase en la zona “See Also”. @since texto
Indica desde qu´e fecha o desde qu´e versi´on se ha introducido el cambio o caracter´ıstica que indica el texto. @deprecated texto
Indica que no deber´ıa utilizarse el m´etodo, porque est´a desautorizado y puede dejar de ser soportado por el API en cualquier momento. Ejemplo de comentario de un m´etodo: /** * Devuelve el car´ acter de la posici´ on indicada entre * 0 y length()-1 * @param indice La posici´ on del car´ acter a obtener * @return El car´ acter situado en la posici´ on
Herramientas y Lenguajes de Programaci´on 05-06
12
Figura 1.4: Evoluci´on de la metodolog´ıa de programaci´on
* @exception StringIndexOutOfRangeException * Se prodcue cuando el indice no est´ a en * el rango 0 a length()-1 */ public char charAt( int indice ) { . . . }
1.3.
Lenguajes de Programaci´ on
El debate en este caso se centra en la programaci´on orientada a objetos frente a la programaci´on tradicional. La Figura 1.4 muestra la evoluci´on de la metodolog´ıa de la programaci´on desde los primeros tiempos de la inform´atica hasta hoy. En la historia de la programaci´on ha habido varias evoluciones sucesivas. Una de las principales fue la programaci´on estructurada, cuyo principio fundamental era dividir un programa en subprogramas m´as peque˜ nos y f´aciles de resolver, hasta llegar a niveles de complejidad elementales, siempre apoy´andose en la idea de ¿Qu´e debe hacer el programa?. Este m´etodo de dise˜ no, a pesar de haber dado resultados satisfactorios, tiene limitaciones. Algunas de ellas son: No favorece la reulitizaci´on del c´odigo. Si en la figura anterior f n1 y f n2 fueran id´enticas, este hecho seguramente pasar´ıa desapercibido y no se compartir´ıa una u ´nica funci´on f n. Si dos subprogramas comparten una misma funci´on f n reutilizando as´ı el c´odigo que define la misma, y m´as adelante queremos modificar f n porque hay un cambio en uno de los subprogramas que la utilizan, la modificaci´on afectar´a tambi´en al otro subprograma, raz´on por la que ahora tendremos que realizar dos funciones. Por lo tanto, se tiene que la programaci´on tradicional se desarrolla a partir de procedimientos y datos, sin delimitar qu´e procedimientos act´ uan sobre qu´e datos. El dise˜ no orientado a objetos se interesa en primer lugar por los datos, a los que se asocian posteriormente procedimientos. En este caso la idea principal es ¿de qu´e trata el
Herramientas y Lenguajes de Programaci´on 05-06
13
programa?. As´ı que el desarrollo se organiza en torno a los datos y no a como se debe funcionar. En la programaci´on orientada a objetos, un programa es una colecci´on de una s´ola entidad b´asica, el objeto, el cual combina los datos con los procedimientos que act´ uan sobre ellos. Durante la ejecuci´on los objetos reciben y env´ıan mensajes a otros objetos para ejecutar las acciones requeridas. La programaci´on orientada a objetos se puede llevar a cabo con lenguajes convencionales, pero esto exige al programador la construcci´on de los mecanismos de que disponen los lenguajes orientados a objetos, tales como: objetos, clases, m´etodos, mensajes, herencia. etc.
Herramientas y Lenguajes de Programaci´on 05-06
14
Cap´ıtulo 2
Programas Java: Aplicaciones El objetivo de este ejercicio pr´actico es mostrar el modo de funcionamiento de los distintos tipos de programas Java. En este caso se abordar´an las aplicaciones.
2.1.
Ejemplo de aplicaci´ on
El c´odigo que aparece a continuaci´on implementa en Java la aplicaci´on que muestra en la terminal la frase “Hola Mundo en Java”: 1 /** 2 * Applicacion Simple 3 */ 4 class AplicacionSimple { 5 public static void main(String[] args) { 6 System.out.println("Hola Mundo en Java"); 7 } 8 } El primer paso para manipular una aplicaci´on Java es compilarla ejecutando en la l´ınea de comandos la instrucci´on >javac AplicacionSimple.java Es importante que el nombre del fichero que contiene el c´odigo coincida exactamente con el nombre de la clase que aparece en la l´ınea n´ umero 3. Al realizar este paso se obtiene un fichero AplicacionSimple.class. Para ejecutar la aplicaci´on escribimos en la l´ınea de comandos >java AplicacionSimple y obtenemos el resultado que aparece en la figura 2.1. 15
Herramientas y Lenguajes de Programaci´on 05-06
16
Figura 2.1: Ejecuci´on de la aplicaci´on
2.2.
Ejercicios
1. Compile y ejecute el ejemplo de aplicaci´on. 2. Escriba una aplicaci´on Java que: Contemple la creaci´on de una clase (Clase1) que contengan un atributo entero y m´etodos para establecer y recuperar los valores de dicho atributo. Definir una segunda clase (Clase2) con un atributo que es un objeto de la Clase1 y con un m´etodo que establece diez valores diferentes en el atributo de la Clase1. Definir una tercera clase (Clase3) con un atributo que es un objeto de la Clase1 y con un m´etodo que recupera diez valores del atributo de la Clase1. Finalmente, crear una clase de prueba donde se instancien objetos de las clases anteriores y se invoque a sus m´etodos. 3. Escriba la jerarqu´ıa de clases del programa que ha desarrollado. 4. Dibuje una traza del flujo de ejecuci´on de la aplicaci´on. 5. Comente sus clases utilizando javadoc genere ficheros de descripci´on de su clase similares a los de la documentaci´on de las API de Java. En el documento de descripci´on de la herramienta Javadoc puede encontrar la forma de uso de las distintas etiquetas disponibles: @see, @author, @version, @param, @return (conexi´on a java.sun.com ) Ejecute el comando javadoc desde la l´ınea de comandos sin ning´ un argumento y obtendr´a una lista de las opciones con las que puede llamar a la herramienta.
Herramientas y Lenguajes de Programaci´on 05-06
17
N´otese que para que aparezcan en el fichero html los comentarios asociados a los campos de datos privados, tiene que compilarlos con la opci´on -private. De la misma forma, para que aparezcan el autor y la versi´on, se han de utilizar las opciones -author y -version.
Cap´ıtulo 3
Programas Java: applets El objetivo de esta pr´actica es mostrar el modo de funcionamiento de los distintos tipos de programas Java. En esta pr´actica se abordar´an los applets.
3.1.
Ejemplo de applet
El c´odigo que aparece a continuaci´on muestra la implementaci´on en Java del programa que muestra en la ventana principal del navegador la frase “Hola Mundo en Java”: /** * Applet Sencillo */ import java.applet.Applet; import java.awt.Graphics; public class AppletSimple extends Applet{ public void paint(Graphics g){ g.drawString("Hola Mundo en Java", 50, 25); } } El primer paso para manipular un applet Java es compilarlo ejecutando en la l´ınea de comandos la instrucci´on >javac AppletSimple.java Al realizar este paso se obtiene una fichero AppletSimple.class. El fichero .class resultante de la compilaci´on, se ha de incrustar en un fichero para ser ejecutado por un navegador. En este caso las etiquetas a utilizar son y .
18
Herramientas y Lenguajes de Programaci´on 05-06
19
El siguiente c´odigo html contine la estructura de la etiqueta para el ejemplo que nos ocupa (est´a almacenado en un fichero con nombre html.html). Un applet simple
A continuaci´ on est´ a la salida del programa
No hay disponible un int´ erprete de Java
N´otese que en el atributo asociado code de la etiqueta se ha especificado AppletSimple.class y no AppletSimple.java.
Figura 3.1: Ejecuci´on del applet en un navegador Finalmente, cuando se abre con un navegador el fichero html.html se obtiene el resultado que se muestra en la figura 3.1. El paquete de desarrollo que proporciona SUN tambi´en ofrece una herramienta de visualizaci´on. Para usarla se ha de ejecutar: >appletviewer html.html La herramienta appletviewer s´olo muestra el applet. Ignora el c´odigo html en el que est´a incrustado (v´ease la figura 3.2).
Herramientas y Lenguajes de Programaci´on 05-06
20
Figura 3.2: Ejecuci´on del applet con appletviewer
3.2.
Ejercicios
1. Compile y ejecute el ejemplo de applet. 2. Implemente un applet que: Defina la misma jerarqu´ıa de clases que en la pr´actica anterior, pero en la que la Clase1 se denomine CampoTextoEntero y extienda a la clase java.awt.TextField, proporcionando m´etodos para establecer y recuperar un valor entero en un campo de texto. Cree una clase de prueba que extienda a la clase java.awt.Applet, donde se instancien objetos de las clases definidas por usted y un objeto de la clase java.awt.Button. Para a˜ nadir las componentes al applet utilizar el m´etodo add en el la implementaci´on del m´etodo init del applet. Implemente los eventos de manera que cuando el usuario pulse el bot´on se invoquen a los m´etodos que permiten establecer y recuperar los diez valores del campo de texto. Para ello, la clase debe implementar la interfaz ActionListener que s´olo incluye al m´etodo public void actionPerformed(ActionEvent e). Para registrar al applet como oyente del objeto bot´on utilizar el m´etodo addActionListner. 3. Escriba la jerarqu´ıa de clases del applet que ha desarrollado. 4. Dibuje una traza del flujo de ejecuci´on del applet.
Cap´ıtulo 4
Creaci´ on de Threads El objetivo de esta pr´actica es introducir al uso de los threads (hilos) y trabajar con las clases que permiten su creaci´on.
4.1.
La clase Thread
Para crear y ejecutar un thread , en primer lugar, hay que definir una clase que extienda a la clase Thread. Esta clase debe sobreescribir el m´etodo run(), que le dice al sistema la tarea que debe ejecutar el thread . class AClass extends Thread { ... public void run() { ... } }
En una clase cliente se crea un objeto thread . A estos objetos se les denomina “objetos ejecutables”. El m´etodo start() le indica al sistema que el thread est´a listo para ejecutarse. public class Client { ... public static void main(String [] args) { ... AClass ut = new AClass(); ... ut.start(); ... } }
21
Herramientas y Lenguajes de Programaci´on 05-06
4.2.
22
La interfaz Runnable
Para crear un thread para una clase que hereda de otra, es necesario implementar la interfaz Runnable. Para ello podemos seguir los siguientes pasos generales: A˜ nadir en la declaraci´on de la clase que se va a implementar la interfaz Runnable: public class AClass extends MotherClass implements Runnable
Declarar un objeto Thread en la clase destino. Por ejemplo, las siguientes sentencias declaran una instancia de la clase Thread, t, con valor inicial nulo: private Thread t = null;
Por defecto, el valor inicial es nulo, as´ı que la asignaci´on al valor “null”no es necesaria. Crear un thread (con el operador new) y ponerla en marcha llamando a su m´etodo start(): t = new Thread(this); //crear el thread t.start(); //poner en marcha el thread
El argumento “this”en el constructor del thread es indispensable, puesto que especifica que el m´etodo run() de la clase actual es el que se debe llamar cuando se ejecute el thread . El m´etodo start() del thread provoca que el m´etodo run() se ejecute. Implementar en el m´etodo run() de la clase, la tarea que se quiere que ejecute el thread .
4.3.
Ejercicios
1. Escriba una aplicaci´on Java que implemente lo siguiente: Contemple la creaci´on de una clase (Mostrador) que contengan un atributo entero y m´etodos para establecer y recuperar los valores de dicho atributo. Escriba una aplicacion Productor/Consumidor en la que se instancian dos objetos: uno de tipo Productor y otro de tipo Consumidor. Estos objetos son threads que se encargan uno de poner un valor y el otro de recogerlo de un objeto de tipo Mostrador.
23
Herramientas y Lenguajes de Programaci´on 05-06
Consumidor
Productor
Mostrador
Figura 4.1: Ejemplo del Productor/Consumidor
El Productor genera un entero entre 0 y 1000, lo almacena en el mostrador y lo imprime. El Consumidor al contrario, consume el entero del mostrador, que es exactamente el mismo objeto en el que el Productor coloca los enteros. As´ı pues, el productor y el consumidor de este ejemplo comparten los datos a trav´es del objeto de tipo Mostrador (figura 8.3). 2. Dibuje la jerarqu´ıa de clases que ha implementado. 3. Dibuje el diagrama de ejecuci´on de la aplicaci´on (tiempo × m´etodo). 4. Escriba un applet que implemente lo mismo que el ejercicio 1. 5. Dibuje la jerarqu´ıa de clases para el applet. 6. Dibuje el diagrama de ejecuci´on del applet (tiempo × m´etodo).
Cap´ıtulo 5
Sincronizaci´ on de Threads El objetivo de esta pr´actica es trabajar con los principios b´asicos de los threads (hilos): la sincronizaci´on y la secuencializaci´on.
5.1.
Exclusi´ on mutua y secuencializaci´ on
La programaci´on se vuelve un poco m´as compleja cuando se tiene un programa con threads. Consid´erese la siguiente descripci´on aparentemente sencilla: Los threads A y B comparten un dato, contador. El thread A efect´ ua repetidamente algunos c´alculos que producen un entero y lo coloca en el contador. El thread B obtiene repetidamente el texto del contador y lo utiliza para sus propios c´alculos. Cuando el programa est´e en ejecuci´on, el sistema alterna la ejecuci´on de A y B. La forma en que ocurre esto var´ıa de un Sistema Operativo a otro y est´a completamente fuera del control del programador. Es posible que el sistema ejecute un thread hasta terminarlo, antes de iniciar el otro; que ejecute tres instrucciones de un thread antes de hacer lo mismo con una del otro, e incluso que deje un thread en medio de una instrucci´on, lo suspenda y empiece con el otro. Una vez adevertido que es imposible suponer nada acerca del orden de ejecuci´ on de dos o m´ as threads, aparecen los siguientes escenarios: 1. El thread A se ejecuta parcialmente en la actualizaci´on del contador y luego la ejecuci´on cambia a B. El resultado de B puede recibir basura cuando trata de inspeccionar el contador. 2. El thread A escribe nueva informaci´on en el contador antes de que B inspeccione el valor antiguo. Este u ´ltimo se pierde. 3. El thread B recibe un valor y luego accede a contador de nuevo antes de que A haya generado un nuevo valor. Se utiliza dos veces el valor antiguo. 24
Herramientas y Lenguajes de Programaci´on 05-06
25
El escenario 1 requiere exclusi´ on mutua, en que no permite que dos threads tengan acceso simult´aneo al recurso compartido contador. Los escenarios 2 y 3 requieren secuencializaci´ on, en que cada thread debe esperar a que el otro termine de usar el recurso compartido. Es importante se˜ nalar que estos escenarios son problem´aticos s´olo porque los threads A y B tienen acceso al objeto contador. Si el c´odigo que ejecutan A y B no hiciera referencia a un objeto compartido, estos subprocesos podr´ıan ejecutarse en el orden que decida el Sistema Operativo y dicho orden no tendr´ıa efecto en el resultado del programa. La aplicaci´on PCTest.java contiene la definici´on de una aplicaci´on Productor/Consumidor en la que se instancian dos objetos: uno de tipo Productor y otro de tipo Consumidor. Estos objetos son threads que se encargan uno de poner un valor y el otro de recogerlo de un objeto de tipo Mostrador. El Productor genera enteros entre 0 y 9, los almacena en el mostrador y los imprime. El Consumidor al contrario, consume todos los enteros del mostrador (que es exactamente el mismo objeto en el que el productor coloca los enteros) tan pronto como est´an disponibles. As´ı pues, el productor y el consumidor de este ejemplo comparten los datos a trav´es del objeto de tipo Mostrador.
5.2.
Ejercicios
1. Compile y ejecute la aplicaci´on Productor/Consumidor. 2. A˜ nada al c´odigo de la aplicaci´on Productor/Consumidor las sentencias necesarias para que los dos threads que se ejecutan pasen al estado de dormido durante un intervalo aleatorio de tiempo. ¿Cambia el resultado de la ejecuci´on? ¿Por qu´e? 3. Implemente los cambios necesarios para que el programa admita la creaci´on de m´as de un thread productor y m´as de un thread consumidor. Ejecute el nuevo programa lanzando varios productores y varios consumidores. Dibuje el diagrama de la ejecuci´on (m´etodos × tiempo). 4. A˜ nada los cambios necesarios al applet que se ha desarrollado en pr´acticas anteriores para que tenga el mismo funcionamiento que la aplicaci´on Productor/Consumidor. Adem´as: El Productor generar´a los enteros impares sucesivos y los colocar´a en el campo de texto compartido. El Consumidor ha de recoger los valores del campo de texto y sumarlos. El Productor indica el final de su tarea colocando el valor “-1” en el campo de texto, mientras que el Consumidor utiliza el “-1” como se˜ nal para informar de la suma. Finalmente cuando el usuario hace click en el bot´on del applet se lanzan los threads. Dibuje el diagrama de la ejecuci´on (m´etodos × tiempo).
Cap´ıtulo 6
Direcciones IP y Nombres de Dominio El objetivo de esta pr´actica es mostrar el modo de funcionamiento de las clase Java para definir nombres de recursos en Internet.
6.1.
La clase InetAddress
La clase InetAddress proporciona objetos que se pueden utilizar para manipular tanto direcciones IP como nombres de dominio.
Ejemplo El ejemplo TestInetAddress.java trata de ilustrar la utilizaci´on de varios de los m´etodos de la clase InetAddress. Para que el programa se ejecute correctamente y no aparezca una excepci´on del tipo “UnknownHostException”, hay que estar conectados convenientemente. En caso de conectarse a un proveedor de Internet, la asignaci´on de direcciones es autom´atica por parte del ISP (Internet Service Provider ), con lo cual se va a obtener una direcci´on diferente en cada conexi´on. import java.net.*; class TestInetAddress { public static void main( String[] args ) { try { System.out.println( "-> Direccion IP de una URL, por nombre" ); InetAddress address = InetAddress.getByName( "nereida.deioc.ull.es" ); System.out.println( address ); // Extrae la direcci´ on IP a partir de la cadena que se // encuentra a la derecha de la barra /, luego proporciona // esta direcci´ on IP como argumento de llamada al m´ etodo getByName() System.out.println( "-> Nombre a partir de la direccion" );
26
Herramientas y Lenguajes de Programaci´on 05-06
27
int temp = address.toString().indexOf( ’/’ ); address = InetAddress.getByName( address.toString().substring(temp+1) ); System.out.println( address ); System.out.println( "-> Direccion IP actual de LocalHost" ); address = InetAddress.getLocalHost(); System.out.println( address ); System.out.println( "-> Nombre de LocalHost a partir de la direccion" ); temp = address.toString().indexOf( ’/’ ); address = InetAddress.getByName( address.toString().substring(temp+1) ); System.out.println( address ); System.out.println( "-> Nombre actual de LocalHost" ); System.out.println( address.getHostName() ); System.out.println( "-> Direccion IP actual de LocalHost" ); // Coge la direcci´ on IP como un array de bytes byte[] bytes = address.getAddress(); // Convierte los bytes de la direcci´ on IP a valores sin // signo y los presenta separados por espacios for( int cnt=0; cnt < bytes.length; cnt++ ) { int uByte = bytes[cnt] < 0 ? bytes[cnt]+256 : bytes[cnt]; System.out.print( uByte+" " ); } System.out.println(); } catch( UnknownHostException e ) { System.out.println( e ); System.out.println( "Debes estar conectado para que esto funcione bien." ); } } }
6.2.
Ejercicios
1. Compile y ejecute el ejemplo TestInetAddress. 2. Existe un gran n´ umero de nombres de dominio y direcciones IP en Internet, por lo que es deseable un servicio que permita asociar un nombre con la direcci´on correspondiente. Dicho servicio se conoce con el nombre de DNS (Servicio de Nombres de Dominio - Domain Name Service). Un ejemplo de este servicio es la utilidad UNIX nslookup. Utilizando nslookup, se puede encontrar el nombre de dominio de una direcci´on IP o viceversa, la direcci´on IP de un nombre de dominio. Haciendo uso de la clase InetAddress escriba un programa Java Nslookup.java que muestre la direcci´on IP de una m´aquina dado su nombre. Usage: java Nslookup
Herramientas y Lenguajes de Programaci´on 05-06
28
3. Escriba un programa Java IPtoname.java que dada la direcci´on IP de una m´aquina muestre su nombre. Usage: java IPtoname 4. ¿Cu´al es la IP de la direcci´on de red asignada a la Universidad de La Laguna?. ¿Qu´e clase de red es (A hasta E)? 5. ¿Cu´al es el nombre de dominio del servidor web de la Universidad de La Laguna?. ¿Cu´al es su direcci´on IP? 6. Utilice los programas que ha implementado para completar la siguiente tabla: Direcci´on IP 127.0.0.1 193.145.98.254
Nombre de Dominio
cepba.upc.es 224.0.1.24 www.mit.edu
Cap´ıtulo 7
Las clases Java DatagramPacket y DatagramSocket El objetivo de esta sesi´on pr´actica es mostrar el modo de funcionamiento de las clases Java para definir datagramas y sockets de datagrama.
7.1.
Introducci´ on
Las redes actuales utilizan el packet switching para la transferencia de datos. Los datos se envuelven en paquetes que se transfieren desde un origen a un destino, donde se extraen de uno en uno los datos de uno o m´as paquetes para reconstruir el mensaje original. Los nodos que se comunican a trav´es de Internet utilizan principalmente dos protocolos: tcp - Transsmision Control Protocol udp - (Universal | User) Datagram Protocol El protocolo udp - (User | Universal) Datagram Protocol - se utiliza para comunicaciones en la que no se garantiza una transmisi´on fiable (reliable). udp no est´a orientado a conexi´on, por lo tanto no garantiza la entrega. udp env´ıa paquetes de datos independientes, denominados datagramas, desde una aplicaci´on a otra. El env´ıo de datagramas es similar a enviar una carta a trav´es del servicio postal: El orden de salida no es importante y no est´a garantizado, y cada mensaje es independiente de cualquier otro. En las comunicaciones basadas en datagramas como las udp, el paquete de datagramas contiene el n´ umero de puerto de su destino y udp encamina el paquete a la aplicaci´on apropiada, como ilustra la figura 8.3. El API Java para udp proporciona una abstraci´on del “paso de mensajes”, esto es, la forma m´as simple de comunicaci´on entre ordenadores. Esto hace posible a un proceso emisor transmitir un u ´nico mensaje a un proceso receptor. Los paquetes independientes que contienen esos mensajes se denominan datagramas. En Java, el emisor especifica el
29
30
Herramientas y Lenguajes de Programaci´on 05-06
Figura 7.1:
destino usando un socket (una referencia indirecta a un puerto particular usada por el proceso receptor en la m´aquina receptora). Un datagrama enviado mediante udp es trasmitido desde un proceso emisor a un proceso receptor sin reconocimiento o recomprobaciones. Si tiene lugar un fallo, el mensaje puede no llegar. Un datagrama es transmitido entre procesos cuando un proceso lo env´ıa y otro proceso lo recibe. Cualquier proceso que necesite enviar o recibir mensajes debe en primer lugar crear un socket a un direcci´on de Internet y a un puerto local. Un servidor enlazar´a ese socket a un puerto servidor - uno que se hace conocido a los clientes de manera que puedan enviar mensajes al mismo. Un cliente enlaza su socket a cualquier puerto local libre. El m´etodo receptor devuelve la direcci´on de Internet y el puerto del emisor, adem´as del mensaje, permitiendo a los receptores enviar una respuesta. Las clases Java para establecer comunicaciones mediante datagramas son: DatagramPacket y DatagramSocket.
7.2.
La clase DatagramPacket
La clase DatagramPacket proporciona un constructor que permite crear instancias de un array de bytes parar: el mensaje, la longitud del mensaje, la direcci´on Internet y el puerto local del socket de destino, de la siguiente forma: array de bytes que contiene el mensaje
longitud del mensaje
direcci´ on Intenet
n´ umero de puerto
Los objetos del tipo DatagramPacket se pueden transmitir entre procesos cuando un proceso los env´ıa y otro los recibe. Esta clase proporciona otro constructor para usarlo cuando se recibe un mensaje. Sus argumentos especifican un array de bytes en el que recibir el mensaje y la longitud del array. Cuando se recibe un mensaje se pone en el DatagramPacket junto con su longitud, la direcci´on de Internet y el puerto del socket de env´ıo. Se puede obtener el mensaje del objeto DatagramPacket mediante el m´etodo getData(). Los m´etodos getPort() y getAddress() permiten obtener el puerto y la direcci´on Internet del objeto de tipo DatagramPacket.
Herramientas y Lenguajes de Programaci´on 05-06
31
El proceso receptor del mensaje tiene que especificar un array de bytes de un tama˜ no determinado en el cual recibir el mensaje, esto es, ha de predecir el Tama˜ no del Mensaje. Si el mensaje es muy grande para el array se trunca cuando llega. El protocolo ip subyacente permite longitudes de paquetes de m´as de 216 bytes, que incluye tanto las cabeceras como los mensajes. Sin embargo, la mayor´ıa de los entornos imponen una restricci´on en el tama˜ no a 8 kilobytes. Cualquier aplicaci´on que necesite mensajes mayores que el m´aximo, debe fragmentarlos en pedazos de ese tama˜ no. Generalmente, una aplicaci´on decidir´a sobre un tama˜ no que no sea excesivamente grande pero que se adecue a su uso previsto.
7.3.
La clase DatagramSocket
La clase DatagramSocket da soporte a sockets para el env´ıo y recepci´on de datagramas udp. Se proporciona un constructor que toma un puerto como argumento, para que sea usado por los procesos que necesitan usar un puerto particular. Tambi´en se proporciona un constructor sin argumentos que permite al sistema escoger un puerto local libre. Estos constructores pueden lanzar una excepci´on del tipo SocketException si el puerto ya est´a en uso o si est´a reservado. Esta clase cuenta con los siguientes m´etodos: send() y receive(). Estos m´etodos permiten transmitir datagramas entre un par de sockets. El argumento del send es una instancia de un DatagramPacket que contiene un mensaje y su destino. El argumento del receive es un objeto DatagramPacket vac´ıo en el cual se pondr´a el mensaje, su longitud y su origen. Tanto el m´etodo send() como el receive() pueden lanzar una IOException. Las comunicaciones mediante datagramas de udp usan env´ıos no bloqueantes (nonblocking sends) y recepciones bloqueantes (blocking receives). Las operaciones de env´ıo retornan cuando estas han dado el mensaje a los protocolos ip o udp subyacentes, los cuales son responsables de trasmitirlos a su destino. En la llegada, el mensaje es puesto en una cola por el socket que est´a asociado al puerto de destino. El mensaje puede ser recogido de la cola por una excepci´on o llamadas futuras de recepcion (receive()) sobre ese socket. Los mensajes son descartados en el destino si ning´ un proceso tiene asociado un socket al puerto de destino. El m´etodo receptor (receive()) se bloquea hasta que se recibe un datagrama, a menos que se establezca un tiempo l´ımite (timeout) sobre el socket. Si el proceso que invoca al m´etodo receive() tiene otra tarea que hacer mientras espera por el mensaje, deber´ıa planificarse en un flujo de ejecuci´on (thread ) separado. setSoTimeout(). Este m´etodo permite establecer un tiempo de espera. Con un tiempo de espera establecido, el m´etodo receive() se bloquear´a por el tiempo especificado y entonces lanzar´a una InterruptedIOException().
Herramientas y Lenguajes de Programaci´on 05-06
32
connect(). Este m´etodo se utiliza para conectar a un puerto remoto particular y una direcci´on de Internet, en este caso el socket s´olo es capaz de enviar y recibir mensajes desde esa direcci´on.
7.4.
Ejercicios
El siguiente c´odigo utiliza sockets datagrama para intercambiar una u ´nica cadena de datos. La l´ogica del programa es lo m´as sencilla posible para subrayar la sint´axis b´asica de las comunicaciones entre procesos. El emisor crea un paquete datagrama que contiene una direcci´on de destino, mientras que el paquete datagrama del receptor no incluye una direcci´on de destino.
import java.net.*; import java.io.*; public class Example1Sender { public static void main(String[] args) { if (args.length != 3) System.out.println ("This program requires three command line arguments"); else { try { InetAddress receiverHost = InetAddress.getByName(args[0]); int receiverPort = Integer.parseInt(args[1]); String message = args[2]; // instantiates a datagram socket for sending the data DatagramSocket mySocket = new DatagramSocket(); byte[ ] buffer = message.getBytes( ); DatagramPacket datagram = new DatagramPacket(buffer, buffer.length, receiverHost, receiverPort); mySocket.send(datagram); mySocket.close( ); } // end try catch (Exception ex) { ex.printStackTrace( ); } } // end else } // end main } // end class
El socket el emisor se enlaza a un n´ umero de puerto no especificado, mientras que el socket del receptor se enlaza a un n´ umero de puerto espec´ıfico, para que el emisor pueda escribir este n´ umero de puerto en su datagrama como destino.
Herramientas y Lenguajes de Programaci´on 05-06
33
import java.net.*; import java.io.*; public class Example1Receiver { public static void main(String[] args) { if (args.length != 1) System.out.println("This program requires a command line argument."); else { int port = Integer.parseInt(args[0]); final int MAX_LEN = 10; // This is the assumed maximum byte length of the datagram to be received. try { DatagramSocket mySocket = new DatagramSocket(port); // instantiates a datagram socket for receiving the data byte[ ] buffer = new byte[MAX_LEN]; DatagramPacket datagram = new DatagramPacket(buffer, MAX_LEN); mySocket.receive(datagram); String message = new String(buffer); System.out.println(message); mySocket.close( ); } // end try catch (Exception ex) { ex.printStackTrace( ); } } // end else } // end main } // end class
1. Compile y ejecute el c´odigo del ejemplo en una m´aquina usando “localhost” como nombre de m´aquina. Por ejemplo se puede introducir el comando: java Example1Sender localhost 12345 Hola Ejecute los dos programas arrancando primero al receptor y despu´es al emisor. El mensaje que se env´ıe no deber´ıa exceder la longitud m´axima permitida que es de 10 caracteres. Describa el resultado de la ejecuci´on. 2. Repita el ejercicio anterior utilizando las m´aquinas manis.etsii.ull.es y timple.etsii.ull.es. 3. Vuelva a ejecutar las aplicaciones del apartado 1, esta vez ejecutando primero al emisor y luego al receptor. Describa y explique el resultado. 4. Repita el apartado 1, esta vez mandando un mensaje de longitud m´as grande que la m´axima longitud permitida. Describa y explique la salida producida.
Herramientas y Lenguajes de Programaci´on 05-06
34
5. A˜ nada c´odigo al proceso receptor de manera que el plazo m´aximo de bloqueo del receive sea de cinco segundos. Lance el proceso receptor pero no el proceso emisor. ¿Cu´al es el resultado? Descr´ıbalo y expl´ıquelo. 6. Modifique el c´odigo original de manera que el receptor ejecute indefinidamente un bucle que reciba y muestre los datos recibidos. Comp´ılelo y ejec´ utelo de la siguiente forma: lance al receptor ejecute el emisor enviando un mensaje “mensaje 1” en otra ventana, lanzar otra instancia del emisor, mandando un mensaje “mensaje 2”. Describa y explique el resultado. 7. Modifique el c´odigo original de manera que el emisor utilice el mismo socket para enviar el mismo mensaje a dos receptores diferentes. Primero lance los dos receptores y despu´es al emisor. ¿Cada receptor recibe el mensaje? Describa y explique el resultado. 8. Modifique el c´odigo original de manera que el emisor utilice dos socket distintos para enviar el mismo mensaje a dos receptores diferentes. Primero lance los dos receptores y despu´es al emisor. ¿Cada receptor recibe el mensaje? Describa y explique el resultado. 9. Modifique el c´odigo del u ´ltimo paso de modo que el emisor env´ıe de forma permanente, suspendi´endose durante 3 segundos entre cada env´ıo. Modifique el receptor de manera que ejecute un bucle que repetidamente reciba datos y luego los muestre. Compile y ejecute los programas durante unos cuentos segundos antes de teminarlos con “Ctrl-C”. Describa y explique el resultado. 10. Modifique el c´odigo original de modo que el emisor tambi´en reciba un mensaje del receptor. Utilizar s´olo un socket en cada proceso. Entregue este c´odigo.
Cap´ıtulo 8
Las clases Java Socket y ServerSocket El objetivo de esta sesi´on pr´actica es mostrar el modo de funcionamiento de las clases Java para definir sockets de flujo (stream).
8.1.
Introducci´ on
El paradigma Cliente/Servidor es quiz´as el m´as conocido de los paradigmas para aplicaciones de red. Se usa para describir un modelo de interacci´on entre dos procesos, que se ejecutan de forma simult´anea. Este modelo es una comunicaci´on basada en una serie de preguntas y respuestas, que asegura que si dos aplicaciones intentan comunicarse, una comienza la ejecuci´on y espera indefinidamente que la otra le responda y luego continua con el proceso.
Figura 8.1: Paradigma Cliente/Servidor
35
Herramientas y Lenguajes de Programaci´on 05-06
36
Los dos componentes del paradigma son: Cliente: aplicaci´on que inicia la comunicaci´on, es dirigida por el usuario. Servidor: es quien responde a los requerimientos de los clientes, son procesos que se est´an ejecutando indefinidamente. Los procesos clientes son m´as sencillos que los procesos de los servidores, los primeros no requieren de privilegios de sistemas para funcionar, en cambio los procesos servidores s´ı. Los usuarios cuando quieren acceder a un servicio de red, ejecutan un software cliente. El dise˜ no de los servidores debe ser muy cuidadoso, debe incluir c´odigo para la manipulaci´on de: autenticaci´ on: verificar la identidad del cliente. seguridad de datos: para que estos no puedan ser accedidos inapropiadamente. privacidad : garantizar que la informaci´on privada de un usuario, no sea accedida por alguien no autorizado. protecci´ on: asegurar que las aplicaciones no monopolicen los recursos del sistema. autorizaci´ on: verificar si el cliente tiene acceso al servicio proporcionado por el servidor. La mayor´ıa de las comunicaciones punto-a-punto en las redes (incluida Internet), est´an basadas en el modelo Cliente/Servidor. Desde el punto de vista Internet/Intranet, se tendr´ıa: Un servidor es un ordenador remoto – en alg´ un lugar de la red – que proporciona informaci´on seg´ un petici´on. Un cliente funciona en su ordenador local, se comunica con el servidor remoto, y pide a ´este informaci´on. El servidor env´ıa la informaci´on solicitada. Un u ´nico servidor t´ıpicamente sirve a una multitud de clientes, ahorrando a cada uno de ellos el problema de tener la informaci´on instalada y almacenada localmente.
8.2.
Sockets
Normalmente, un servidor se ejecuta en una m´aquina espec´ıfica y tiene un socket asociado a un n´ umero de puerto espec´ıfico. El servidor simplemente espera a la escucha en el socket a que un cliente se conecte con una petici´on. El cliente conoce el nombre de la
Herramientas y Lenguajes de Programaci´on 05-06
37
Figura 8.2: socket Servidor
m´ aquina sobre la que est´a ejecut´andose el servidor y el n´ umero de puerto al que est´a conectado. Solicitar una conexi´on consiste en intentar establecer una cita con el servidor en el puerto de la m´aquina servidora. Si todo va bien, el servidor acepta la conexi´on. Pero antes, el servidor crea un nuevo socket en un puerto diferente. Es necesario crear un nuevo socket (y consecuentemente un n´ umero de puerto diferente) de forma que en el socket original se continue a la escucha de las peticiones de nuevos clientes mientras se atiende a las necesidades del cliente conectado. En el cliente, si se acepta la conexi´on, el socket se crea satisfactoriamente y se puede utilizar para comunicarse con el servidor.
Figura 8.3: socket Cliente Un socket es el extremo final de un enlace punto-a-punto que comunica a dos programas ejecut´andose en una red. Los sockets siempre est´an asociados a un n´ umero de puerto que es utilizado por tcp para identificar la aplicaci´on a la que est´a destinada la solicitud y poder redirigirsela.
8.2.1.
La clase Socket
La clase Socket del paquete java.net es f´acil de usar comparada con la que proporcinan otros lenguajes. Java oculta las complejidades derivadas del establecimiento de la conexi´on de red y del env´ıo de datos a trav´es de ella. En esencia, el paquete java.net proporciona la misma interfaz de programaci´on que se utiliza cuando se trabaja con archivos. Ejemplo 1 El siguiente ejemplo, ClienteFecha.java, muestra la implementaci´on de un cliente que accede al servicio UNIX “fecha y hora”. El servidor concreto al que se conecta es al “localhost”. El servicio “fecha y hora”, por convenio, siempre est´a en el puerto 13. Lo que ocurre es que el software del servidor est´a ejecut´andose continuamente en la m´aquina
Herramientas y Lenguajes de Programaci´on 05-06
38
remota, esperando cualquier tr´afico de red que “hable con ´el ”en el puerto 13. Cuando el Sistema Operativo de este servidor recupera un paquete de red que contiene una petici´on para conectar con el puerto 13, activa el servicio de escucha del servidor y establece la conexi´on, que permanece activa hasta que es finalizada por alguna de las dos partes. import java.net.*; import java.io.*; import java.util.*; class ClienteFecha { public static void main( String[] args ) { String servidor = "localhost"; int puerto = 13; // puerto de daytime try { // Se abre un socket conectado al servidor y al // puerto est´ andar de echo Socket socket = new Socket( servidor,puerto ); System.out.println( "Socket Abierto." ); // Se consigue el canal de entrada BufferedReader entrada = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); System.out.println( System.out.println( System.out.println( System.out.println(
"Hora actual en localhost:" ); "\t"+entrada.readLine() ); "Hora actual con la clase date:" ); "\t" + new Date() );
// Se cierra el canal de entrada entrada.close(); // Se cierra el socket socket.close(); } catch( UnknownHostException e ) { System.out.println( e ); System.out.println( "Debes estar conectado para que esto funcione bien." ); } catch( IOException e ) { System.out.println( e ); } } }
Ejemplo 2 El ejemplo EchoClient.java muestra la implementaci´on de un cliente que accede al servicio UNIX “eco”. El servidor concreto al que se conecta es a “manis” en la Escuela. El servicio “eco”, por convenio, siempre est´a en el puerto 7. Aunque con frecuencia por razones de seguridad est´a cerrado.
Herramientas y Lenguajes de Programaci´on 05-06
39
import java.io.*; import java.net.*; public class EchoClient { public static void main(String[] args) throws IOException { String serverName = "exthost.csi.ull.es"; int portNumber = 7; Socket echoSocket = null; PrintWriter out = null; BufferedReader in = null; try { echoSocket = new Socket(serverName, portNumber); out = new PrintWriter(echoSocket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader( echoSocket.getInputStream())); } catch (UnknownHostException e) { System.err.println("Don’t know about host: " + serverName); System.exit(1); } catch (IOException e) { System.err.println("Couldn’t get I/O for " + "the connection to: " + serverName); System.exit(1); } BufferedReader stdIn = new BufferedReader( new InputStreamReader(System.in)); String userInput; while ((userInput = stdIn.readLine()) != null) { out.println(userInput); System.out.println("echo: " + in.readLine()); } out.close(); in.close(); stdIn.close(); echoSocket.close(); } }
8.2.2.
La clase ServerSocket
La clase ServerSocket es la que se utiliza a la hora de crear servidores, al igual que como se ha visto, la clase Socket se utilizaba para crear clientes. Ejemplo Este ejemplo muestra c´omo escribir un servidor y su cliente. Est´a sacado del Tutorial de Java de Sun. El servidor sirve chistes. Funciona de la siguiente forma:
Herramientas y Lenguajes de Programaci´on 05-06
40
miranda:~/clases/psd/> java KnockKnockClient Server: Knock! Knock! Who’s there? Client: Who’s there? Server: Turnip Turnip who? Client: Turnip who? Server: Turnip the heat, it’s cold in here! Want another? (y/n) n Client: n Server: Bye. miranda:~/clases/psd/> El ejemplo consta de dos programas Java ejecut´andose de forma independiente KnockKnockClient y KnockKnockServer. Sin embargo, est´a constituido por tres ficheros: KnockKnockServer.java (implementaci´on del Servidor) KnockKnockProtocol.java (implementaci´on del protocolo) KnockKnockClient.java (implementaci´on del cliente)
8.3.
La clase Thread y la implementaci´ on de servidores
Existe un problema con el ejemplo del servidor de chistes de la secci´on anterior. Suponga que queremos permitir que varios usuarios se conecten a la vez. Lo normal es que un servidor est´e ejecut´andose constantemente en un ordenador, y que los usuarios se conecten simult´aneamente al mismo. En el ejemplo que hemos visto, s´olo se admite la conexi´on de un usuario. Esto podemos arreglarlo usando threads. Cada vez que sepamos que el programa ha establecido una nueva conexi´on, esto es, siempre que una petici´on de servicio tenga ´exito, lanzaremos un nuevo thread que ser´a el encargado de monitorizar la conecci´on entre el servidor y ese cliente. El programa principal s´olo se encargar´a de seguir esperando nuevas conexiones. Para implementar esto, el bucle principal del servidor deber´ıa ser algo como: while (true) { Socket incoming = s.accept(); Thread t = new ThreadServerHandler(incoming); t.start(); }
La clase ThreadServerHandler extiende a la clase Thread y contiene el bucle de comunicaci´on entre el servidor y el cliente en su m´etodo run().
Herramientas y Lenguajes de Programaci´on 05-06
41
class ThreadServerHandler extends Thread { ... public void run() { try { // Establecer los flujos de entrada/salida para el socket // Procesar las entradas y salidas seg´ un el protocolo // cerrar el socket } catch (Excepction e) { // manipular las excepciones } } }
Ejemplo Este ejemplo ampl´ıa al de la secci´on anterior mostrando c´omo escribir un servidor que atiende a m´ ultiples clientes. El modo de funcionamiento es exactamente el mismo. El ejemplo consta de dos programas Java ejecut´andose de forma independiente KnockKnockClient y KKMultiServer. Para probarlo en una terminal lance al servidor, y en dos o m´as nuevas terminales lance a varios clientes. La aplicaci´on est´a constituida por los siguientes ficheros: KnockKnockProtocol.java (implementaci´on del protocolo, no cambia respecto al ejemplo anterior) KnockKnockClient.java (implementaci´on del cliente, no cambia respecto al ejemplo anterior) KKMultiServerThread.java (implementaci´on de un thread Servidor) KKMultiServer.java (implementaci´on del Servidor)
8.4.
Ejercicios
1. Modificar el ejemplo Cliente de Eco del enunciado de manera que: Se especifique en la l´ınea de comandos el nombre de la m´aquina servidora y el n´ umero de puerto. Si no se especifica nada, el servidor por defecto ser´a “localhost” y el puerto el n´ umero 7. Para indicar el final de una sesi´on cliente el usuario ha de introducir por teclado un punto “.”. Cuando se introduzca un punto se ha de salir del bucle de entrada y se ha de cerrar el socket de datos. Soluci´on: EchoClient.java
Herramientas y Lenguajes de Programaci´on 05-06
42
2. Implementar un servidor de “eco” como el que proporcionan los servidores Unix en el puerto 7. Dise˜ ne un servidor iterativo, para ello: Se ha de especificar en la l´ınea de comandos el n´ umero de puerto en el que el servidor acepta conexiones (por ejemplo, 8180). Si no se especifica nada, el n´ umero de puerto por defecto ser´a el 7. El servidor se ha de quedar esperando las solicitudes de conexi´on de los clientes. Utilizando la clase BufferedReader junto con la clase InputStreamReader se ha de abrir un flujo de entrada desde el socket servidor. Con la clase PrintWriter junto con la clase OutputStreamWriter abrir un flujo de salida al socket. El programa ha de actuar como repetidor, recogiendo las l´ıneas que llegan por el canal de entrada y escribi´endolas en el canal de salida, hasta que el usuario le indique que ha terminado, escribiendo un punto “.”. Cuando ya no haya m´as l´ıneas que leer, se recibir´a un punto, lo cual har´a que el servidor salga del bucle de entrada y cierre el socket servidor. Para probar que funciona, ejecute el programa servidor en un consola, y en otra terminal escriba: telnet 127.0.0.1 8180. Soluci´on: EchoServer.java MyStreamSocket.java 3. Con los dos programas anteriores realice las siguientes operaciones: Ejecute los programas empezando por el servidor y a continuaci´on el cliente. En una terminal diferente arranque a otro cliente. Dibuje el diagrama de secuencia. ¿Se pueden realizar las dos sesiones en paralelo? Explique su respuesta. 4. Modificar el servidor de “eco” del ejercicio 2, para que sea un servidor concurrente. Soluci´on: EchoServer.java 5. Con el programa anterior y el cliente de eco del ejercicio 1 realice las siguientes operaciones: Ejecute los programas empezando por el servidor y a continuaci´on el cliente. En una terminal diferente arranque a otro cliente. Dibuje el diagrama de secuencia. ¿Se pueden realizar las dos sesiones del cliente en paralelo? Explique su respuesta. 6. Describa las diferencias, desde el punto de vista del cliente, entre un servidor iterativo y un servidor concurrente para un servicio que involucre m´ ultiples rondas de intercambios de mensajes.
Herramientas y Lenguajes de Programaci´on 05-06
8.5.
C´ odigos Fuente del Servidor de chistes iterativo
import java.net.*; import java.io.*; public class KnockKnockProtocol { private static final int WAITING = 0; private static final int SENTKNOCKKNOCK = 1; private static final int SENTCLUE = 2; private static final int ANOTHER = 3; private static final int NUMJOKES = 5; private int state = WAITING; private int currentJoke = 0; private String[] clues = { "Turnip", "Little Old Lady", "Atch", "Who", "Who" }; private String[] answers = { "Turnip the heat, it’s cold in here!", "I didn’t know you could yodel!", "Bless you!", "Is there an owl in here?", "Is there an echo in here?" }; public String processInput(String theInput) { String theOutput = null; if (state == WAITING) { theOutput = "Knock! Knock!"; state = SENTKNOCKKNOCK; } else if (state == SENTKNOCKKNOCK) { if (theInput.equalsIgnoreCase("Who’s there?")) { theOutput = clues[currentJoke]; state = SENTCLUE; } else { theOutput = "You’re supposed to say \"Who’s there?\"! " + "Try again. Knock! Knock!"; } } else if (state == SENTCLUE) { if (theInput.equalsIgnoreCase(clues[currentJoke] + " who?")) { theOutput = answers[currentJoke] + " Want another? (y/n)"; state = ANOTHER; } else { theOutput = "You’re supposed to say \"" + clues[currentJoke] + " who?\"" + "! Try again. Knock! Knock!"; state = SENTKNOCKKNOCK; } } else if (state == ANOTHER) { if (theInput.equalsIgnoreCase("y")) { theOutput = "Knock! Knock!"; if (currentJoke == (NUMJOKES - 1)) currentJoke = 0;
43
Herramientas y Lenguajes de Programaci´on 05-06
else currentJoke++; state = SENTKNOCKKNOCK; } else { theOutput = "Bye."; state = WAITING; } } return theOutput; } } import java.io.*; import java.net.*; public class KnockKnockClient { public static void main(String[] args) throws IOException { Socket kkSocket = null; PrintWriter out = null; BufferedReader in = null; try { kkSocket = new Socket("localhost", 4444); out = new PrintWriter(kkSocket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(kkSocket.getInputStream())); } catch (UnknownHostException e) { System.err.println("Don’t know about host: manis.csi.ull.es."); System.exit(1); } catch (IOException e) { System.err.println("Couldn’t get I/O for the connection to: manis.csi.ull.es."); System.exit(1); } BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in)); String fromServer; String fromUser; while ((fromServer = in.readLine()) != null) { System.out.println("Server: " + fromServer); if (fromServer.equals("Bye.")) break; fromUser = stdIn.readLine(); if (fromUser != null) { System.out.println("Client: " + fromUser); out.println(fromUser); } } out.close(); in.close(); stdIn.close(); kkSocket.close(); } }
44
Herramientas y Lenguajes de Programaci´on 05-06
import java.net.*; import java.io.*; public class KnockKnockServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(4444); System.out.println("estoy despu´ es de crear el socket"); } catch (IOException e) { System.err.println("Could not listen on port: 4444."); System.exit(1); } Socket clientSocket = null; try { clientSocket = serverSocket.accept(); System.out.println("estoy despu´ es de aceptar un cliente"); } catch (IOException e) { System.err.println("Accept failed."); System.exit(1); } PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); BufferedReader in = new BufferedReader( new InputStreamReader( clientSocket.getInputStream())); String inputLine, outputLine; KnockKnockProtocol kkp = new KnockKnockProtocol(); outputLine = kkp.processInput(null); out.println(outputLine); while ((inputLine = in.readLine()) != null) { outputLine = kkp.processInput(inputLine); out.println(outputLine); if (outputLine.equals("Bye.")) break; } out.close(); in.close(); clientSocket.close(); serverSocket.close(); } }
45
Herramientas y Lenguajes de Programaci´on 05-06
8.6.
C´ odigos Fuente del Servidor de chistes concurrente
import java.net.*; import java.io.*; public class KKMultiServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = null; boolean listening = true; try { serverSocket = new ServerSocket(4444); } catch (IOException e) { System.err.println("Could not listen on port: 4444."); System.exit(-1); } while (listening) new KKMultiServerThread(serverSocket.accept()).start(); serverSocket.close(); } }
import java.net.*; import java.io.*; public class KKMultiServerThread extends Thread { private Socket socket = null; public KKMultiServerThread(Socket socket) { super("KKMultiServerThread"); this.socket = socket; } public void run() { try { PrintWriter out = new PrintWriter(socket.getOutputStream(), true); BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream())); String inputLine, outputLine; KnockKnockProtocol kkp = new KnockKnockProtocol(); outputLine = kkp.processInput(null); out.println(outputLine); while ((inputLine = in.readLine()) != null) { outputLine = kkp.processInput(inputLine); out.println(outputLine); if (outputLine.equals("Bye")) break; }
46
Herramientas y Lenguajes de Programaci´on 05-06
out.close(); in.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
47