Download Herramientas y Lenguajes de Programación

Document related concepts
no text concepts found
Transcript
Herramientas y Lenguajes de Programación
Coromoto León Hernández
1,5 créditos
Universidad de La Laguna
Programa de Doctorado de Fı́sica e Informática
Dpto. de Estadı́stica, I.O. y Computación
2005-2006
Resumen
El curso “Herramientas y Lenguajes de Programación” del programa de doctorado de
“Fı́sica e Informática” está clasificado como metodológico (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á dividido en tres partes. Este material cubre la parte del curso relacionada
con la manipulación de herramientas informáticas y el desarrollo de aplicaciones.
Índice general
1. Introducción
1.1. Desarrollo de aplicaciones Informáticas . . . . .
1.2. Herramientas . . . . . . . . . . . . . . . . . . .
1.2.1. Proceso de Compilación . . . . . . . . .
1.2.2. Compilación de programas formados por
1.2.3. Creación de Librerı́as . . . . . . . . . .
1.2.4. Documentación . . . . . . . . . . . . . .
1.3. Lenguajes de Programación . . . . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
varios módulos
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
. 2
. 3
. 4
. 5
. 6
. 8
. 11
2. Programas Java: Aplicaciones
14
2.1. Ejemplo de aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3. Programas Java: applets
17
3.1. Ejemplo de applet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4. Creación de Threads
20
4.1. La clase Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.2. La interfaz Runnable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.3. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5. Sincronización de Threads
23
5.1. Exclusión mutua y secuencialización . . . . . . . . . . . . . . . . . . . . . . 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ón . . . . . . . . . . .
La clase DatagramPacket . . .
La clase DatagramSocket . . .
DatagramSocket
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
1
28
28
29
30
2
Herramientas y Lenguajes de Programación 05-06
7.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
8. Las clases Java Socket y ServerSocket
8.1. Introducción . . . . . . . . . . . . . . . . . . . . . .
8.2. Sockets . . . . . . . . . . . . . . . . . . . . . . . .
8.2.1. La clase Socket . . . . . . . . . . . . . . .
8.2.2. La clase ServerSocket . . . . . . . . . . .
8.3. La clase Thread y la implementación de servidores
8.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . .
8.5. Códigos Fuente del Servidor de chistes iterativo . .
8.6. Códigos Fuente del Servidor de chistes concurrente
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
34
34
35
36
38
39
40
42
45
Capı́tulo 1
Introducción
El principal objetivo de está parte del curso de doctorado es dotar al alumno de las
habilidades necesarias para la manipulación de herramientas informáticas que le permitan
realizar una labor investigadora eficaz.
En el desarrollo de un proyecto informático uno de los aspectos más importantes es
la elección del lenguaje más apropiado para su implementación y las herramientas a utilizar. Existen múltiples herramientas informáticas y múltiples lenguajes de programación
dependiendo del área de estudio. Una vez elegido el lenguaje, se ha de recabar información
sobre las herramientas disponibles y realizar un estudio acerca de cuál se adapta mejor
a las necesidades del proyecto a realizar. Ası́ pues, es necesario iniciar al alumnado en el
desarrollo sistemático y ordenado de aplicaciones informáticas integradas en proyectos de
investigación. En este curso, se especifican de forma genérica las fases que componen el
desarrollo e implementación de una aplicación informática y se formalizan las mismas con
ejemplos concretos de realización.
Los objetivos concretos del curso son:
Manejar los fundamentos básicos del sistema operativo UNIX (Linux).
Conocer las herramientas informáticas UNIX (Linux) fundamentales para el desarrollo de aplicaciones.
Comprender los principios básicos de la programación imperativa, y oritentada a
objetos.
Reconocer la importacia de la generación de documentación que acompaña a todo
proyecto informático.
Comprender como diseñar algoritmos eficientes utilizando distintas técnicas algorı́tmicas.
1.1.
Desarrollo de aplicaciones Informáticas
En este apartado se expone de forma escueta cuáles son las partes que constituyen el
desarrollo de un programa, esto es, las etapas de desarrollo de software.
3
Herramientas y Lenguajes de Programación 05-06
4
Figura 1.1: Etapas del desarrollo de software
Las etapas de desarrollo de software se pueden resumir en:
1. Diseñar una solución, proponiendo un algoritmo.
2. Traduccir la solución a pseudocódigo.
3. Implementar un programa en un lenguaje de programación (en el caso que estudiaremos Java).
4. Compilar el programa.
5. Realizar pruebas de ejecución.
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álido para programas no demasiado complejos. Para los grandes proyectos informáticos exite un conjunto de técnicas 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ático, McGraw-Hill, 4 edición, 1998. ISBN 84-481-1186-9.
1.2.
Herramientas
En el desarrollo de un proyecto informático un aspecto importante es qué tipo de herramientas se han de utilizar y el lenguaje más apropiado para su implementación. Existen
Herramientas y Lenguajes de Programación 05-06
5
múltiples herramientas informáticas y múltiples lenguajes de programación dependiendo
del área de estudio.
El sistema operativo elegido para realizar los ejercicios práctico 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á un
repaso de algunas de la principales herramientas que existen en el entorno Unix para el desarrollo de programas en el lenguaje de programación Java. Estas herramientas incluyen
los compiladores - en los cuales se pueden encontrar diferencias según la versión Unix
utilizada-, los intérpretes de la máquina virtual de Java (lı́nea de comandos - stand-alone
o herramientas de visualización - appletviewer ), la creación de paquetes o bibliotecas, etc.
1.2.1.
Proceso de Compilación
Un traductor es un programa informático 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ón de Alto Nivel, y el lenguaje objeto es Lenguaje Ensamblador o Lenguaje Máquina.
El proceso de traducción que tiene que realizar un compilador, se puede dividir en los
siguientes pasos:
Preprocesamiento
Compilación (propiamente dicho) y Optimización
Generación de Código Objeto
Enlace ( linker )
No es uno de los objetivos de este curso el estudiar en profundidad el proceso de
compilación 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écnicas 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ón -c:
g++ programa.C -c -o programa.o
Herramientas y Lenguajes de Programación 05-06
6
Figura 1.2: Herramientas del compilador de GCC
El editor de carga y enlace (ld) se invoca automáticamente sin especificar ninguna
opción:
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ón.
En el caso de Java, se habla de un lenguaje de programación y de una plataforma
de ejecución. Como lenguaje de programación de alto nivel se ha de destacar que es
completamente orientado a objetos y tiene definida una sintaxis y una semántica. La
plataforma de ejecución de Java está compuesta por:
La Máquina Virtual de Java (Java Virtual Machine - JVM)
Los APIs (Application Programming Interfaces - Paquetes)
1.2.2.
Compilación de programas formados por varios módulos
Consideremos la división de la implementación de un problema C/C++ en dos ficheros:
programa1.cc y programa2.cc. Según se muestra en la Figura 1.3, el primer paso consiste
en editar dichos ficheros (con el editor vi, por ejemplo). A continuación, la orden que se
deberı́a emitir para generar la aplicación es:
Herramientas y Lenguajes de Programación 05-06
7
Figura 1.3: Compilación de varios módulos separados
g++ programa1.c programa2.c
Se ha de tener en cuenta, que la llamada al enlazador (linker) se realiza de forma
automática. Por lo tanto, los dos ficheros intermedios, programa1.o y programa2.o se
borraran al finalizar la operación. 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ón
es la siguiente:
Ficheros de Cabecera - directorio /usr/include
Librerı́as - directorios /lib, /usr/lib
1.2.3.
Creación de Librerı́as
La creación de librerı́as (o bibliotecas) aporta modularidad y portabilidad a los programas.
Una librerı́a es un fichero que está compuesto por una colección de otros ficheros llamados miembros de la librerı́a. La estructura de una librerı́a posibilita la extracción de
Herramientas y Lenguajes de Programación 05-06
8
sus miembros. Cuando se añade un fichero a una librerı́a, los datos de éste y su información administrativa (permisos, fechas, propietario, grupo, etc.) se introduce en él. Para
una descripción detallada de las opciones de esta herrmienta visite la página de manual
(man ar).
Las funciones básicas 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ólo 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ólo que en Java se
agrupan clases y/o interfaces.
En los paquetes las clases son ú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ón 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ódigo 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ón de nuevas clases en el paquete es simple, se ha de colocar
la misma sentencia al comienzo de los ficheros que contengan la declaración 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 todas
las clases que se declaren en ese fichero.
Se pueden referenciar paquetes precediendo con su nombre la clase que se quiere usar.
También se puede usar la palabra reservada import, si se van a colocar múltiples 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ón de clase
en el código fuente.
Herramientas y Lenguajes de Programación 05-06
1.2.4.
9
Documentación
La herramienta javadoc genera páginas HTML de documentación del API a partir de
ficheros con código 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ón. Se genera documentación 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én
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ón de los siguiente elementos:
Paquetes
Clases e interfaces públicas
Métodos públicos y protegidos
Datos públicos y protegidos
Se puede añadir documentación para todos estos entes a través de comentarios de
documentación en el código fuente. Estos comentarios pueden incluir marcas HTML. Un
comentario de documentación está 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én todos los espacios y tabuladores que
precedan a ese carácter *.
/**
* Este es un comentario de documentación
*/
Se pueden incluir etiquetas HTML dentro de un comentario de documentación, aunque
no deben utilizarse las etiquetas <h1>, <h2>,... o lı́neas horizontales <hr>, porque javadoc
crea una estructura completa para el documento y estas marcas interfieren con el formato
general de ese documento.
Cada comentario de documentación puede incluir texto libre seguido de etiquetas de
documentación. 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ácter *
se ignora) y todas las etiquetas con el mismo nombre deben agruparse juntas dentro del
comentario de documentación.
Marcas de documentación de clases e interface
@see nombre_de_clase
Añade un enlace a la clase especificada en la zona “See Also”. Por ejemplo:
Herramientas y Lenguajes de Programación 05-06
@see
@see
@see
@see
@see
@see
10
java.lang.String
String
String#equals
java.lang.Object#waint(int)
Character#MAX_RADIX
<a href="spec.html">Especif. Java</a>
Se utiliza el carácter # para separar el nombre de una clase del nombre de uno de
sus campos de datos, métodos o constructores.
@version texto-version
Añade una entrada “Version”. El texto no tiene que tener formato especial.
@author texto-autor
Añade 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é fecha
o desde qué versión se ha introducido el cambio o caracterı́stica que indica el texto.
@deprecated texto
Añade un comentario indicando que el método está desautorizado y no deberı́a utilizarse porque puede dejar de ser soportada por el API. La convención que se sigue
es indicar en el texto la función o método por quien se ha sustituido. Ejemplo de
comentario de una clase:
/**
* Clase que representa la figura geométrica cilindro
* Por ejemplo:
* <PRE>
*
Cilindro c = new Cilindro(1.0);
*
double d = c.volumen();
* </PRE>
*
* @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ón 05-06
11
Marcas de documentación de campos de datos
La ú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ón de constructores y métodos
Pueden ser marcas @see y además:
@param parametro descripcion
Añade un parámetro a la sección “Parameters”. La descripción puede continuar en
la lı́nea siguiente.
@return descripcion
Añade una sección “Return”, que debe contener la descripción del valor a devolver.
@throws exception descripcion
Añade una entrada “Throws”, que contiene el nombre de la excepción que puede
ser lanzada por el método. La excepción estará enlazada con su clase en la documentación.
@see nombre_de_clase
Añade un enlace a la clase en la zona “See Also”.
@since texto
Indica desde qué fecha o desde qué versión se ha introducido el cambio o caracterı́stica
que indica el texto.
@deprecated texto
Indica que no deberı́a utilizarse el método, porque está desautorizado y puede dejar
de ser soportado por el API en cualquier momento. Ejemplo de comentario de un
método:
/**
* Devuelve el carácter de la posición indicada entre
* <tt>0</tt> y <tt>length()-1</tt>
* @param indice La posición del carácter a obtener
* @return
El carácter situado en la posición
Herramientas y Lenguajes de Programación 05-06
12
Figura 1.4: Evolución de la metodologı́a de programación
* @exception
StringIndexOutOfRangeException
*
Se prodcue cuando el indice no está en
*
el rango <tt>0</tt> a <tt>length()-1</tt>
*/
public char charAt( int indice ) {
. . .
}
1.3.
Lenguajes de Programación
El debate en este caso se centra en la programación orientada a objetos frente a la
programación tradicional. La Figura 1.4 muestra la evolución de la metodologı́a de la
programación desde los primeros tiempos de la informática hasta hoy.
En la historia de la programación ha habido varias evoluciones sucesivas. Una de las
principales fue la programación estructurada, cuyo principio fundamental era dividir un
programa en subprogramas más pequeños y fáciles de resolver, hasta llegar a niveles de
complejidad elementales, siempre apoyándose en la idea de ¿Qué debe hacer el programa?.
Este método de diseño, a pesar de haber dado resultados satisfactorios, tiene limitaciones. Algunas de ellas son:
No favorece la reulitización del código.
Si en la figura anterior f n1 y f n2 fueran idénticas, este hecho seguramente pasarı́a
desapercibido y no se compartirı́a una única función f n.
Si dos subprogramas comparten una misma función f n reutilizando ası́ el código que
define la misma, y más adelante queremos modificar f n porque hay un cambio en
uno de los subprogramas que la utilizan, la modificación afectará también al otro
subprograma, razón por la que ahora tendremos que realizar dos funciones.
Por lo tanto, se tiene que la programación tradicional se desarrolla a partir de procedimientos y datos, sin delimitar qué procedimientos actúan sobre qué datos.
El diseño 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é trata el
Herramientas y Lenguajes de Programación 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ón orientada a objetos, un programa es una colección de una sóla
entidad básica, el objeto, el cual combina los datos con los procedimientos que actúan
sobre ellos. Durante la ejecución los objetos reciben y envı́an mensajes a otros objetos
para ejecutar las acciones requeridas.
La programación orientada a objetos se puede llevar a cabo con lenguajes convencionales, pero esto exige al programador la construcción de los mecanismos de que disponen los lenguajes orientados a objetos, tales como: objetos, clases, métodos, mensajes,
herencia. etc.
Herramientas y Lenguajes de Programación 05-06
14
Capı́tulo 2
Programas Java: Aplicaciones
El objetivo de este ejercicio práctico es mostrar el modo de funcionamiento de los
distintos tipos de programas Java. En este caso se abordarán las aplicaciones.
2.1.
Ejemplo de aplicación
El código que aparece a continuación implementa en Java la aplicación 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ón Java es compilarla ejecutando en la lı́nea
de comandos la instrucción
>javac AplicacionSimple.java
Es importante que el nombre del fichero que contiene el código coincida exactamente
con el nombre de la clase que aparece en la lı́nea número 3.
Al realizar este paso se obtiene un fichero AplicacionSimple.class.
Para ejecutar la aplicación 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ón 05-06
16
Figura 2.1: Ejecución de la aplicación
2.2.
Ejercicios
1. Compile y ejecute el ejemplo de aplicación.
2. Escriba una aplicación Java que:
Contemple la creación de una clase (Clase1) que contengan un atributo entero
y métodos 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étodo 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étodo 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étodos.
3. Escriba la jerarquı́a de clases del programa que ha desarrollado.
4. Dibuje una traza del flujo de ejecución de la aplicación.
5. Comente sus clases utilizando javadoc genere ficheros de descripción de su clase
similares a los de la documentación de las API de Java.
En el documento de descripción de la herramienta Javadoc puede encontrar la forma
de uso de las distintas etiquetas disponibles:
@see, @author, @version, @param, @return
(conexión a java.sun.com )
Ejecute el comando javadoc desde la lı́nea de comandos sin ningún argumento y
obtendrá una lista de las opciones con las que puede llamar a la herramienta.
Herramientas y Lenguajes de Programación 05-06
17
Nótese que para que aparezcan en el fichero html los comentarios asociados a los
campos de datos privados, tiene que compilarlos con la opción -private.
De la misma forma, para que aparezcan el autor y la versión, se han de utilizar las
opciones -author y -version.
Capı́tulo 3
Programas Java: applets
El objetivo de esta práctica es mostrar el modo de funcionamiento de los distintos tipos
de programas Java. En esta práctica se abordarán los applets.
3.1.
Ejemplo de applet
El código que aparece a continuación muestra la implementación 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ón
>javac AppletSimple.java
Al realizar este paso se obtiene una fichero AppletSimple.class.
El fichero .class resultante de la compilación, se ha de incrustar en un fichero para
ser ejecutado por un navegador.
En este caso las etiquetas a utilizar son <APPLET> y </APPLET>.
18
Herramientas y Lenguajes de Programación 05-06
19
El siguiente código html contine la estructura de la etiqueta para el ejemplo que nos
ocupa (está almacenado en un fichero con nombre html.html).
<html>
<head>
<title> Un applet simple </title>
</head>
<body>
<p>
A continuación está la salida del programa
</p>
<applet code="AppletSimple.class" width="300" height="100">
No hay disponible un intérprete de Java
</applet>
</body>
</html>
Nótese que en el atributo asociado code de la etiqueta <APPLET> se ha especificado
AppletSimple.class y no AppletSimple.java.
Figura 3.1: Ejecución 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én ofrece una herramienta de
visualización. Para usarla se ha de ejecutar:
>appletviewer html.html
La herramienta appletviewer sólo muestra el applet. Ignora el código html en el que
está incrustado (véase la figura 3.2).
Herramientas y Lenguajes de Programación 05-06
20
Figura 3.2: Ejecución 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áctica anterior, pero en la que la
Clase1 se denomine CampoTextoEntero y extienda a la clase java.awt.TextField,
proporcionando métodos 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ñadir las componentes al applet utilizar el método
add en el la implementación del método init del applet.
Implemente los eventos de manera que cuando el usuario pulse el botón se invoquen a los métodos que permiten establecer y recuperar los diez valores del campo de texto. Para ello, la clase debe implementar la interfaz ActionListener
que sólo incluye al método public void actionPerformed(ActionEvent e).
Para registrar al applet como oyente del objeto botón utilizar el método
addActionListner.
3. Escriba la jerarquı́a de clases del applet que ha desarrollado.
4. Dibuje una traza del flujo de ejecución del applet.
Capı́tulo 4
Creación de Threads
El objetivo de esta práctica es introducir al uso de los threads (hilos) y trabajar con
las clases que permiten su creación.
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étodo 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étodo start() le indica al sistema que el thread está listo para ejecutarse.
public class Client {
...
public static void main(String [] args) {
...
AClass ut = new AClass();
...
ut.start();
...
}
}
21
Herramientas y Lenguajes de Programación 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ñadir en la declaración 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ón al valor “null”no es necesaria.
Crear un thread (con el operador new) y ponerla en marcha llamando a su método
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étodo run() de la clase actual es el que se debe llamar cuando se ejecute
el thread .
El método start() del thread provoca que el método run() se ejecute.
Implementar en el método run() de la clase, la tarea que se quiere que ejecute el
thread .
4.3.
Ejercicios
1. Escriba una aplicación Java que implemente lo siguiente:
Contemple la creación de una clase (Mostrador) que contengan un atributo
entero y métodos 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ón 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és del objeto de tipo Mostrador (figura 8.3).
2. Dibuje la jerarquı́a de clases que ha implementado.
3. Dibuje el diagrama de ejecución de la aplicación (tiempo × método).
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ón del applet (tiempo × método).
Capı́tulo 5
Sincronización de Threads
El objetivo de esta práctica es trabajar con los principios básicos de los threads (hilos):
la sincronización y la secuencialización.
5.1.
Exclusión mutua y secuencialización
La programación se vuelve un poco más compleja cuando se tiene un programa con
threads. Considérese la siguiente descripción aparentemente sencilla:
Los threads A y B comparten un dato, contador. El thread A efectúa repetidamente algunos cálculos 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álculos.
Cuando el programa esté en ejecución, el sistema alterna la ejecución de A y B. La
forma en que ocurre esto varı́a de un Sistema Operativo a otro y está 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ón, lo suspenda y empiece con el otro.
Una vez adevertido que es imposible suponer nada acerca del orden de ejecución
de dos o más threads, aparecen los siguientes escenarios:
1. El thread A se ejecuta parcialmente en la actualización del contador y luego la ejecución cambia a B. El resultado de B puede recibir basura cuando trata de inspeccionar
el contador.
2. El thread A escribe nueva información en el contador antes de que B inspeccione el
valor antiguo. Este ú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ón 05-06
25
El escenario 1 requiere exclusión mutua, en que no permite que dos threads tengan
acceso simultáneo al recurso compartido contador. Los escenarios 2 y 3 requieren secuencialización, en que cada thread debe esperar a que el otro termine de usar el recurso
compartido.
Es importante señalar que estos escenarios son problemáticos sólo porque los threads A
y B tienen acceso al objeto contador. Si el código 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ón PCTest.java contiene la definición de una aplicación 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án disponibles. Ası́ pues, el productor y el consumidor de
este ejemplo comparten los datos a través del objeto de tipo Mostrador.
5.2.
Ejercicios
1. Compile y ejecute la aplicación Productor/Consumidor.
2. Añada al código de la aplicación 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ón? ¿Por qué?
3. Implemente los cambios necesarios para que el programa admita la creación de más
de un thread productor y más de un thread consumidor. Ejecute el nuevo programa lanzando varios productores y varios consumidores. Dibuje el diagrama de la
ejecución (métodos × tiempo).
4. Añada los cambios necesarios al applet que se ha desarrollado en prácticas anteriores
para que tenga el mismo funcionamiento que la aplicación Productor/Consumidor.
Además:
El Productor generará los enteros impares sucesivos y los colocará 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ñal para informar de
la suma.
Finalmente cuando el usuario hace click en el botón del applet se lanzan los
threads.
Dibuje el diagrama de la ejecución (métodos × tiempo).
Capı́tulo 6
Direcciones IP y Nombres de
Dominio
El objetivo de esta práctica 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ón de varios de los métodos de la clase InetAddress. Para que el programa se ejecute correctamente y no aparezca
una excepción del tipo “UnknownHostException”, hay que estar conectados convenientemente. En caso de conectarse a un proveedor de Internet, la asignación de direcciones es
automática por parte del ISP (Internet Service Provider ), con lo cual se va a obtener una
dirección diferente en cada conexión.
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ón IP a partir de la cadena que se
// encuentra a la derecha de la barra /, luego proporciona
// esta dirección IP como argumento de llamada al método getByName()
System.out.println( "-> Nombre a partir de la direccion" );
26
Herramientas y Lenguajes de Programación 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ón IP como un array de bytes
byte[] bytes = address.getAddress();
// Convierte los bytes de la dirección 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úmero de nombres de dominio y direcciones IP en Internet, por lo
que es deseable un servicio que permita asociar un nombre con la dirección 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ón IP o viceversa, la dirección IP de un nombre de dominio. Haciendo uso
de la clase InetAddress escriba un programa Java Nslookup.java que muestre la
dirección IP de una máquina dado su nombre.
Usage: java Nslookup <hostname>
Herramientas y Lenguajes de Programación 05-06
28
3. Escriba un programa Java IPtoname.java que dada la dirección IP de una máquina
muestre su nombre.
Usage: java IPtoname <IP address>
4. ¿Cuál es la IP de la dirección de red asignada a la Universidad de La Laguna?.
¿Qué clase de red es (A hasta E)?
5. ¿Cuál es el nombre de dominio del servidor web de la Universidad de La Laguna?.
¿Cuál es su dirección IP?
6. Utilice los programas que ha implementado para completar la siguiente tabla:
Dirección 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ón práctica es mostrar el modo de funcionamiento de las clases
Java para definir datagramas y sockets de datagrama.
7.1.
Introducción
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ás paquetes para reconstruir el mensaje original.
Los nodos que se comunican a través 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ón fiable (reliable). udp no está orientado a
conexión, por lo tanto no garantiza la entrega. udp envı́a paquetes de datos independientes,
denominados datagramas, desde una aplicación a otra.
El envı́o de datagramas es similar a enviar una carta a través del servicio postal: El
orden de salida no es importante y no está 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úmero de puerto de su destino y udp encamina el paquete a la aplicación
apropiada, como ilustra la figura 8.3.
El API Java para udp proporciona una abstración del “paso de mensajes”, esto es,
la forma más simple de comunicación entre ordenadores. Esto hace posible a un proceso
emisor transmitir un ú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ón 05-06
Figura 7.1:
destino usando un socket (una referencia indirecta a un puerto particular usada por el
proceso receptor en la máquina 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ón de Internet y a un puerto local. Un servidor
enlazará 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étodo receptor devuelve la dirección de Internet y el puerto del emisor, además
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ón Internet y el
puerto local del socket de destino, de la siguiente forma:
array de bytes que contiene el mensaje
longitud del mensaje
dirección Intenet
número 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ón de Internet y el puerto del socket de envı́o.
Se puede obtener el mensaje del objeto DatagramPacket mediante el método getData().
Los métodos getPort() y getAddress() permiten obtener el puerto y la dirección Internet
del objeto de tipo DatagramPacket.
Herramientas y Lenguajes de Programación 05-06
31
El proceso receptor del mensaje tiene que especificar un array de bytes de un tamaño
determinado en el cual recibir el mensaje, esto es, ha de predecir el Tamaño 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ás de 216 bytes, que incluye tanto las cabeceras como
los mensajes. Sin embargo, la mayorı́a de los entornos imponen una restricción en el tamaño
a 8 kilobytes. Cualquier aplicación que necesite mensajes mayores que el máximo, debe
fragmentarlos en pedazos de ese tamaño. Generalmente, una aplicación decidirá sobre un
tamaño 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ón 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én se proporciona
un constructor sin argumentos que permite al sistema escoger un puerto local libre. Estos
constructores pueden lanzar una excepción del tipo SocketException si el puerto ya
está en uso o si está reservado.
Esta clase cuenta con los siguientes métodos:
send() y receive().
Estos métodos 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á el mensaje, su longitud y su origen. Tanto el método 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á asociado al puerto de destino.
El mensaje puede ser recogido de la cola por una excepción o llamadas futuras de
recepcion (receive()) sobre ese socket. Los mensajes son descartados en el destino si ningún proceso tiene asociado un socket al puerto de destino. El método
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étodo receive() tiene otra tarea que hacer mientras espera por el mensaje,
deberı́a planificarse en un flujo de ejecución (thread ) separado.
setSoTimeout().
Este método permite establecer un tiempo de espera. Con un tiempo de espera
establecido, el método receive() se bloqueará por el tiempo especificado y entonces
lanzará una InterruptedIOException().
Herramientas y Lenguajes de Programación 05-06
32
connect().
Este método se utiliza para conectar a un puerto remoto particular y una dirección
de Internet, en este caso el socket sólo es capaz de enviar y recibir mensajes desde
esa dirección.
7.4.
Ejercicios
El siguiente código utiliza sockets datagrama para intercambiar una única cadena de
datos. La lógica del programa es lo más sencilla posible para subrayar la sintáxis básica
de las comunicaciones entre procesos. El emisor crea un paquete datagrama que contiene
una dirección de destino, mientras que el paquete datagrama del receptor no incluye una
dirección 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úmero de puerto no especificado, mientras que el
socket del receptor se enlaza a un número de puerto especı́fico, para que el emisor pueda
escribir este número de puerto en su datagrama como destino.
Herramientas y Lenguajes de Programación 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ódigo del ejemplo en una máquina usando “localhost” como
nombre de máquina. Por ejemplo se puede introducir el comando:
java Example1Sender localhost 12345 Hola
Ejecute los dos programas arrancando primero al receptor y después al emisor. El
mensaje que se envı́e no deberı́a exceder la longitud máxima permitida que es de 10
caracteres.
Describa el resultado de la ejecución.
2. Repita el ejercicio anterior utilizando las máquinas 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ás grande que la
máxima longitud permitida.
Describa y explique la salida producida.
Herramientas y Lenguajes de Programación 05-06
34
5. Añada código al proceso receptor de manera que el plazo máximo de bloqueo del
receive sea de cinco segundos. Lance el proceso receptor pero no el proceso emisor.
¿Cuál es el resultado? Descrı́balo y explı́quelo.
6. Modifique el código original de manera que el receptor ejecute indefinidamente un
bucle que reciba y muestre los datos recibidos. Compı́lelo y ejecútelo 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ódigo 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és al emisor. ¿Cada receptor recibe el mensaje? Describa y explique el
resultado.
8. Modifique el código 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és al emisor. ¿Cada receptor recibe el mensaje? Describa y explique
el resultado.
9. Modifique el código del último paso de modo que el emisor envı́e de forma permanente, suspendiéndose 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ódigo original de modo que el emisor también reciba un mensaje del
receptor. Utilizar sólo un socket en cada proceso. Entregue este código.
Capı́tulo 8
Las clases Java Socket y
ServerSocket
El objetivo de esta sesión práctica es mostrar el modo de funcionamiento de las clases
Java para definir sockets de flujo (stream).
8.1.
Introducción
El paradigma Cliente/Servidor es quizás el más conocido de los paradigmas para aplicaciones de red. Se usa para describir un modelo de interacción entre dos procesos, que
se ejecutan de forma simultánea. Este modelo es una comunicación basada en una serie
de preguntas y respuestas, que asegura que si dos aplicaciones intentan comunicarse, una
comienza la ejecución 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ón 05-06
36
Los dos componentes del paradigma son:
Cliente: aplicación que inicia la comunicación, es dirigida por el usuario.
Servidor: es quien responde a los requerimientos de los clientes, son procesos
que se están ejecutando indefinidamente.
Los procesos clientes son más 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ño de los servidores debe ser muy cuidadoso, debe incluir código para la manipulación de:
autenticación: verificar la identidad del cliente.
seguridad de datos: para que estos no puedan ser accedidos inapropiadamente.
privacidad : garantizar que la información privada de un usuario, no sea accedida por
alguien no autorizado.
protección: asegurar que las aplicaciones no monopolicen los recursos del sistema.
autorización: 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án
basadas en el modelo Cliente/Servidor. Desde el punto de vista Internet/Intranet, se tendrı́a:
Un servidor es un ordenador remoto – en algún lugar de la red – que proporciona
información según petición.
Un cliente funciona en su ordenador local, se comunica con el servidor remoto, y
pide a éste información.
El servidor envı́a la información solicitada.
Un único servidor tı́picamente sirve a una multitud de clientes, ahorrando a cada uno
de ellos el problema de tener la información instalada y almacenada localmente.
8.2.
Sockets
Normalmente, un servidor se ejecuta en una máquina especı́fica y tiene un socket asociado a un número de puerto especı́fico. El servidor simplemente espera a la escucha en
el socket a que un cliente se conecte con una petición. El cliente conoce el nombre de la
Herramientas y Lenguajes de Programación 05-06
37
Figura 8.2: socket Servidor
máquina sobre la que está ejecutándose el servidor y el número de puerto al que está conectado. Solicitar una conexión consiste en intentar establecer una cita con el servidor en el
puerto de la máquina servidora.
Si todo va bien, el servidor acepta la conexión. Pero antes, el servidor crea un nuevo
socket en un puerto diferente. Es necesario crear un nuevo socket (y consecuentemente un
número 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ón, 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ándose en una red.
Los sockets siempre están asociados a un número de puerto que es utilizado por tcp
para identificar la aplicación a la que está destinada la solicitud y poder redirigirsela.
8.2.1.
La clase Socket
La clase Socket del paquete java.net es fácil de usar comparada con la que proporcinan otros lenguajes. Java oculta las complejidades derivadas del establecimiento de la
conexión de red y del envı́o de datos a través de ella. En esencia, el paquete java.net proporciona la misma interfaz de programación que se utiliza cuando se trabaja con archivos.
Ejemplo 1
El siguiente ejemplo, ClienteFecha.java, muestra la implementación 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á en el puerto 13. Lo
que ocurre es que el software del servidor está ejecutándose continuamente en la máquina
Herramientas y Lenguajes de Programación 05-06
38
remota, esperando cualquier tráfico de red que “hable con él ”en el puerto 13. Cuando el
Sistema Operativo de este servidor recupera un paquete de red que contiene una petición
para conectar con el puerto 13, activa el servicio de escucha del servidor y establece la
conexión, 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ándar 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ón 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á en el puerto 7. Aunque con frecuencia por
razones de seguridad está cerrado.
Herramientas y Lenguajes de Programación 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ómo escribir un servidor y su cliente. Está sacado del Tutorial
de Java de Sun. El servidor sirve chistes. Funciona de la siguiente forma:
Herramientas y Lenguajes de Programación 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ándose de forma independiente KnockKnockClient
y KnockKnockServer. Sin embargo, está constituido por tres ficheros:
KnockKnockServer.java (implementación del Servidor)
KnockKnockProtocol.java (implementación del protocolo)
KnockKnockClient.java (implementación del cliente)
8.3.
La clase Thread y la implementación de servidores
Existe un problema con el ejemplo del servidor de chistes de la sección anterior. Suponga que queremos permitir que varios usuarios se conecten a la vez. Lo normal es que un
servidor esté ejecutándose constantemente en un ordenador, y que los usuarios se conecten
simultáneamente al mismo. En el ejemplo que hemos visto, sólo se admite la conexión de
un usuario. Esto podemos arreglarlo usando threads.
Cada vez que sepamos que el programa ha establecido una nueva conexión, esto es,
siempre que una petición de servicio tenga éxito, lanzaremos un nuevo thread que será el
encargado de monitorizar la conección entre el servidor y ese cliente. El programa principal
sólo se encargará 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ón entre el servidor y el cliente en su método run().
Herramientas y Lenguajes de Programación 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ún el protocolo
// cerrar el socket
}
catch (Excepction e) {
// manipular las excepciones
}
}
}
Ejemplo
Este ejemplo amplı́a al de la sección anterior mostrando cómo escribir un servidor que
atiende a múltiples clientes. El modo de funcionamiento es exactamente el mismo.
El ejemplo consta de dos programas Java ejecutándose de forma independiente KnockKnockClient y KKMultiServer. Para probarlo en una terminal lance al servidor, y en
dos o más nuevas terminales lance a varios clientes. La aplicación está constituida por los
siguientes ficheros:
KnockKnockProtocol.java (implementación del protocolo, no cambia respecto al
ejemplo anterior)
KnockKnockClient.java (implementación del cliente, no cambia respecto al ejemplo
anterior)
KKMultiServerThread.java (implementación de un thread Servidor)
KKMultiServer.java (implementación 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áquina servidora y el
número de puerto. Si no se especifica nada, el servidor por defecto será “localhost” y el puerto el número 7.
Para indicar el final de una sesión 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ón:
EchoClient.java
Herramientas y Lenguajes de Programación 05-06
42
2. Implementar un servidor de “eco” como el que proporcionan los servidores Unix en
el puerto 7. Diseñe un servidor iterativo, para ello:
Se ha de especificar en la lı́nea de comandos el número de puerto en el que
el servidor acepta conexiones (por ejemplo, 8180). Si no se especifica nada, el
número de puerto por defecto será el 7.
El servidor se ha de quedar esperando las solicitudes de conexión 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éndolas en el canal de salida, hasta que el usuario le
indique que ha terminado, escribiendo un punto “.”. Cuando ya no haya más
lı́neas que leer, se recibirá un punto, lo cual hará 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ón:
EchoServer.java
MyStreamSocket.java
3. Con los dos programas anteriores realice las siguientes operaciones:
Ejecute los programas empezando por el servidor y a continuación 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ón:
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ón 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últiples rondas de
intercambios de mensajes.
Herramientas y Lenguajes de Programación 05-06
8.5.
Códigos 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ón 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ón 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és 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és 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ón 05-06
8.6.
Códigos 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ón 05-06
out.close();
in.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
47