Está en la página 1de 152

Programación de servicios

y procesos
Desarrollo de Aplicaciones Multiplataforma
Inazio Claver
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Índice
Presentación .......................................................................................................................4
Introducción a los procesos ..................................................................................................5
Estados del proceso .................................................................................................................. 5
Procesos en los SSOO ................................................................................................................ 6
Procesos en C ......................................................................................................................8
EXECL ......................................................................................................................................... 8
System ....................................................................................................................................... 8
PID ............................................................................................................................................. 8
PIPE.......................................................................................................................................... 11
FIFO (o PIPE con nombre) ....................................................................................................... 13
Señales y sincronización .......................................................................................................... 16
Programación concurrente................................................................................................. 20
Programación paralela ............................................................................................................ 21
Programación distribuida .................................................................................................... 22
Hilos.................................................................................................................................. 24
Hilos en Java ............................................................................................................................ 24
Runnable. Heredar de la clase Thread ................................................................................ 27
Applets ................................................................................................................................ 28
Programación multihilo ..................................................................................................... 34
Estados de un hilo ................................................................................................................... 34
Gestión de un hilo ................................................................................................................... 35
Prioridad en los hilos ........................................................................................................... 38
Ejercicios de gestión de hilos .............................................................................................. 39
Procesos concurrentes ............................................................................................................ 49
Algoritmo de Dekker ........................................................................................................... 49
Algoritmo de Peterson ........................................................................................................ 54
Programación en red ........................................................................................................ 61
TCP/IP ..................................................................................................................................... 61
Nivel de Transporte ............................................................................................................. 61
Paquete java.net....................................................................................................................... 61

1
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Clase InetAddress.................................................................................................................... 61
Clase URL ............................................................................................................................... 63
Clase URLConnection ............................................................................................................ 66
Scripts del lado del servidor ................................................................................................ 68
Programación de sockets .................................................................................................. 71
¿Qué son los sockets?.............................................................................................................. 71
Funcionamient en general de un socket .................................................................................. 71
Clase ServerSocket.................................................................................................................. 72
Clase Socket ............................................................................................................................ 73
Gestión de Sockets TCP .......................................................................................................... 74
Conexión de múltiples clientes. Hilos ..................................................................................... 76
Clases para Sockets UDP ........................................................................................................ 77
Clase DatagramPacket......................................................................................................... 78
Clase DatagramSocket ........................................................................................................ 78
Gestión de sockets UDP .......................................................................................................... 79
MulticastSocket ....................................................................................................................... 84
Envío de objetos a través de Socket TCP ................................................................................ 87
ObjectInputStream & ObjectOutputStream ....................................................................... 87
Serialización......................................................................................................................... 87
Envío de objetos a través de Sockets UDP .............................................................................. 89
RMI .......................................................................................................................................... 90
Introducción a las aplicacione RMI ..................................................................................... 90
Objetos distribuidos ............................................................................................................ 90
Pasaje de objetos ................................................................................................................ 91
Ejemplo RMI ........................................................................................................................ 92
Ejemplo RMI. Código ........................................................................................................... 93
Conclusión ........................................................................................................................... 95
Resumen rápido .................................................................................................................. 95
Ejercicio simple. La hipoteca ............................................................................................... 97
Programación segura ....................................................................................................... 103
Buenas prácticas en programación ....................................................................................... 103
Criptografía............................................................................................................................ 105
Certificados digitales ............................................................................................................. 108
Control de acceso .................................................................................................................. 110
Seguridad en entorno Java .................................................................................................... 111
APIs JAVA para seguridad .................................................................................................. 113
Ficheros de políticas en Java ................................................................................................. 114

2
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Localización ....................................................................................................................... 114


Entrada grant..................................................................................................................... 114
Herramienta PolicyTool ..................................................................................................... 118
Criptografía con Java ............................................................................................................. 119
Proveedores de servicios criptográficos ........................................................................... 119
Resúmenes de mensajes ................................................................................................... 120
Generando y verificando firmas digitales ......................................................................... 123
Almacenar las claves pública y privada en ficheros .......................................................... 126
Recuperar las claves pública y privada de ficheros ........................................................... 127
Firmar los datos de un fichero con la clave privada .......................................................... 127
Verificar la firma de un fichero con la clave pública ......................................................... 128
Herramientas para firmar ficheros.................................................................................... 129
Pasos para encriptar y desencriptar con clave secreta ..................................................... 132
Almacenar la clave secreta en un fichero ......................................................................... 134
Encriptar y desencriptar con clave pública ....................................................................... 135
Encriptar y desencriptar flujos de datos ........................................................................... 137
Comunicaciones seguras con JAVA. JSSE .............................................................................. 139
SSL ..................................................................................................................................... 139
Control de acceso con Java. JAAS .......................................................................................... 144
Autenticación .................................................................................................................... 144
Autorización ...................................................................................................................... 148
Epílogo ............................................................................................................................ 151

3
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Presentación
Bienvenido a los apuntes del módulo de Programación de servicios y procesos, estudiado en el
Ciclo Superior de Desarrollo de Aplicaciones Multiplataforma.

En las siguientes páginas intentaré explicar de manera clara y concisa las bases que te
permitirán entender la programación basada en varios hilos y procesos, usada para obtener un
mejor rendimiento del sistema y permitir crear aplicaciones que optimicen los tiempos de
respuesta para el usuario.

Además, nos introduciremos en el mundo de la computación distribuida para aprovechar la


comunicación entre aplicaciones a través de la red para soluciones complejas, y veremos los
conceptos de seguridad de la información para realizar aplicaciones que se comuniquen de
una forma segura, todo ello amenizado con ejemplos bien documentos y ejercicios que
servirán para asentar las bases.

Esta información también estará pubicada en mi página web,


programandoapasitos.blogspot.com, además de poder ver en ella mucho más material
concerniente a otros aspectos de la programación.

Espero que os resulte de interés.

Inazio Claver

4
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Introducción a los procesos


Proceso = Programa en ejecución

Cada una de las CPU se tienen que compartir a ratos con todos los procesos cargados en la
memoria RAM.

Cuando un programa usa la CPU y sale, hay que hacer una especie de instantánea para guardar
el contador de procesos pendientes, donde se ha quedado, etc.

Esto se conoce como el BCP (bloque de control de procesos). Es una tabla del sistema
operativo que guarda la información de la situación de como se había quedado el programa en
el momento de finalizar su tiempo en la CPU y dejar espacio a otro proceso.

Estados del proceso

Un proceso puede estar en la CPU, es decir, estará en ejecución.


Puede ser que esté cargado en la RAM, preparado para que el SO le de paso a la CPU. Estará
listo.
O puede ser que esté esperando a utilizar los recursos que otro proceso ha cogido y hasta que
no finalice el sistema operativo no le va a dejar continuar. Entonces estará bloqueado.

La contienda es como se llama a la pugna que hacen los procesos listos para conquistar la CPU.

Hay distintos modos de realizar la asignación de la contienda. Entre otros:


Round-Robin. Método circular
Por prioridades. Es decir, el antivirus tendrá más prioridad que la calculadora, por
ejemplo. O el Word al estar tecleando conseguirá más prioridad de la ventana activa
para no dar sensación de lentitud o ir a saltitos… Pero esto tiene varios problemas,
como la inacción. Es decir, si mientras P1 (proceso 1) está en ejecución por ganar a la
P2 pero entra P3 que tiene mayor prioridad, saldrá P1 y ganará la contienda P3.
Por tiempos. Mayor prioridad el que menor tiempo estimado tiene para terminar.

Eso sí, al programar no podremos controlar los métodos de ordenación de la contienda. No


podemos garantizar que los procesos vayan a coger la CPU en el momento que vayamos a
querer.

5
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Procesos en los SSOO

Para ver los procesos en Windows, iremos al Administrador de Tareas Procesos

Para verlos desde la línea de comandos, en CMD escribiremos tasklist. Además al hacerlo por
línea de comandos veremos el PID, el identificador del proceso.

En Ubuntu podemos verlo en la terminal con el comando ps.

Si hacemos ps –f también aparecerá el PPID, el identificador del proceso padre.

6
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Con ps –AF aparecen todos los procesos lanzados en el sistema.

7
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Procesos en C
Vamos a realizar las primeras prácticas con los procesos, los PID y los PPID en el lenguaje C.
Para ello hemos instalado un entorno VitaLinux que trae ya incorporado el compilador gcc,
pero podéis utilizar cualquier herramienta de vuestra elección que os permita trabajar
programando en C.

EXECL

Execl sirve para ejecutar comandos del sistema y cualquier programa. Los argumentos son
(const char *fichero, cons char *arg0, …, char *argN, (char*)NULL).
Devolverá -1 si hay condición de error.

Veamos un código de ejemplo

#include <stdio.h>
#include<unistd.h>

void main(){

printf(“Los archivos del directorio son: \n”);


execl(“/bin/ls”, “ls”, “-l”, (char *)NULL);
printf(“ERROR!!!”);
}
Aquí estaremos haciendo un ls –l para ver todos los archivos del directorio desde donde
ejecutemos este programa.

System

A diferencia de execl, system ejecutará sólo comandos del sistema, como podemos ver abajo.
Vamos a ver el siguiente programa para hacernos una idea.

#include <stdio.h>
#include <stdlib.h>

void main(){

system(“ls –l > ficSalida”);


printf(“FIN”);
}

Lo que hace el código es guardar el resultado de un ls –l de la carpeta actual del PATH a un


fichero salida.

PID

En la arquitectura cliente – servidor tenemos dentro de un servidor web un proceso que está
escuchando, en listen. Le hacen una petición y este proceso crea un hijo que sirva la página
web al cliente. Una vez finalizada la tarea, el hijo sale de la memoria al finalizar su tarea.

8
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Durante todo la ejecución, el proceso padre se ha mantenido activo permaneciendo a la


escucha de otras peticiones.

Vamos a ver un programa que nos muestre los identificadores del proceso actual y de su
padre.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void main(){

pid_t id_pactual, id_padre;

id_pactual = getpid();
id_padre = getppid();

printf(“PID actual: %d \n”, id_pactual);


printf(“PID padre: %d \n”, id_padre);
}

Analicemos este código.


La línea de declaración pid_t nos indica el tipo de variable que será, una que nos sirva
para almacenar el número de proceso que tenemos.
La función getpid() devuelve el id del proceso actual
Y la función getppid() devuelve el id del padre del proceso actual.
Ahora ya sabemos ver los id de los procesos, pero… ¿cómo se crean los procesos hijos?
Para eso tenemos la instrucción fork(), que es la encargada de crear un proceso hijo. Es decir,
una copia exacta del proceso padre, pero a partir de ahora independientes.
Estos dos procesos, junto con todos los del sistema, entrarán en la contienda por la puja de la
CPU independientemente. Y por tanto el control de tiempo también será independiente.

Para poder diferenciar uno de otro lo conseguimos con el valor devuelto por fork(). Con un
ejemplo se verá más claro.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void main(){

pid_t pid, hijo_pid;

pid = fork(); // Aquí crea el proceso hijo

if (pid == -1){
printf(“Ha habido un error”);
exit(-1);
}
if(pid == 0){
// Nos encontramos dentro del proceso hijo
printf("soy el proceso hijo \n\t Mi PID es %d. El PID
de mi padre es: %d. \n", getpid(), getppid());

9
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

}
else{
hijo_pid = wait(NULL);
printf("Soy el proceso padre: \n\t Mi PID es %d. El
PID de mi padre es: %d. \n\t Mi hijo %d terminó. \n", getpid(),
getppid(), pid); }
}

Hay varios puntos que comentar. El primero es que como ya hemos dicho, los procesos pese a
estar en un mismo programa, tanto el hijo como el padre son independientes en el momento
de ejecutar fork().
Si nos devuelve un valor igual a 0, sabremos que estamos tratando con el hijo, mientras que
cualquier otro valor que no indique una condición de error nos hará saber que está
funcionando el padre.

Dentro del padre tenemos la línea


hijo_pid = wait(NULL);

Con ésta línea indicaremos que va a esperar a la finalización del proceso hijo, y la variable pid
guardará el PID del padre.

¿Ha quedado claro? Atrevámonos con un ejercicio sencillico.

Ejercicio. Coged un proceso, cread una variable y guardad un valor. 7, por ejemplo. El proceso
crea un hijo que le suma 5, y el padre le resta 5. Visualiza por pantalla el resultado de ambas
operaciones así como el PID y el PPID de ambos procesos.
Solución:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void main(){

pid_t pid, hijo;


int n = 7;

pid = fork();

if (pid == -1){
printf(“Error \n”);
exit(-1);
}
if (pid == 0){
n = n + 5;
printf("Soy el hijo. Valor de n = %d.\n Proceso %d,
padre %d \n\n", n, getpid(), getppid());
}
else{
n = n – 5;
printf("Soy el padre. Valor de n = %i.\n Proceso %d,
padre %d \n\n", n, getpid(), getppid());
}

10
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

PIPE

Las PIPE (tuberías) son un mecanismo para poner en comunicación los procesos padre e hijo.
Se comporta como un falso fichero en el que ambos pueden leer y escribir.

Conceptualmente hablando, tendremos dos procesos, padre e hijo. Entre ellos se creará una
tubería que emplearán para leer y escribir.
Para ello usaremos un array de enteros de dos posiciones. El [0] será de lectura, y el [1] de
escritura. Es bidireccional pero sólo puede usarse en uno de los dos sentidos.

Si necesitamos una comunicación que circule en los dos sentidos, habrá que crear dos PIPE,
uno para cada dirección.

Para poder utilizar este método de comunicación deberemos cargar la librería unistd, y tener
en cuenta que se escribe como los ficheros, usando la función write y read.

Veamos un código de ejemplo para pillar la idea.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void main(){

int fd[2];
char buffer[30];
pid_t pid;

//#include<unistd.h>
// int pipe(int fd[2]);
// fd[0] contiene el descriptor para lectura

pipe(fd); // Se crea el PIPE


pid = fork();

switch(pid){

case -1: // Error


printf("No se ha podido crear un hijo \n");
exit(-1);
break;
case 0: // Hijo
close(fd[0]); // Cierra el descriptor que no va
a usar. El de lectura
printf("El hijo escribe en el PIPE... \n");
write(fd[1], "Hola papi", 10);
break;
default: // Padre

11
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

close(fd[1]); // Cierra el descriptor de


escritura
wait(NULL); // Espera a que finalice el hijo
printf("El padre lee el PIPE \n");
read(fd[0], buffer, 10);
printf("\t Mensaje leido: %s \n", buffer);
}
}

En el código podemos ver que después de la declaración de cada proceso, tenemos una lína
que reza:

close(fd[1]);

Con esto lo que hacemos es cerrar la parte de la tubería que no vamos a utilizar. Me explico, si
el que escribe es el hijo, en su lado cerraremos la posición cero, y si el padre es el que va a leer,
cerraremos la posición 1. Así conseguiremos algo más de seguridad en nuestro programa. Es
una práctica recomendable, aunque es verdad que nuestro código funcionará exactamente
igual sin hacerlo.

Ejercicio. Haz tres procesos (padre, hijo y nieto) que se comuniquen entre ellos a través de
PIPEs.

Solución.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void main(){

int fd1[2], fd2[2];


char buffer[30], buffer2[30];

pid_t pid, pidNieto;

pipe(fd1);
pipe(fd2);

pid = fork();

switch(pid){

case -1: // Error


printf("Ha habido un error \n");
exit(-1);
break;
case 0: // Hijo
close(fd1[0]);
printf("Escribe el padre: \n");
write(fd1[1], "El padre dice hola", 20);

//Creación de nieto
pidNieto = fork();

12
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

switch(pidNieto){
case -1: // Error
printf("Ha habido un error \n");
exit(-1);
break;
case 0:
close(fd2[0]);
printf("Escribe el nieto \n");
write(fd2[1], "Soy el nieto", 13);
break;
default:
close(fd2[1]);
wait(NULL);
printf("El padre lee \n");
read(fd2[0], buffer2, 13);
printf("Mensaje leído: %s", buffer2);
break;
}
break;
default: // Padre
close(fd1[1]);
wait(NULL); // Espero que finalice el nieto
printf("\nEl abuelo lee: \n");
read(fd1[0], buffer, 20);
printf("Mensaje leído: %s \n", buffer);
break;
}
}

FIFO (o PIPE con nombre)

Las PIPE en principio solo sirven para comunicaciones padre – hijo, por lo que para
comunicarnos entre procesos independientes necesitamos otro tipo estructura.

Esta son los FIFO, archivos que ya no se crean en el proceso padre, sino que lo genera el
sistema operativo. Es un ente que genera el SO y que podrá ser utilizado por diversos procesos
estén o no emparentados.
Es decir, los mecanismos de comunicación no tienen que estar necesariamente emparentados.

Se llama FIFO porque es una cola (first in, first out).

Para operar con ellos en C usaremos comandos de ficheros de bajo nivel (write, open…)

El comando que permite crear una FIFO se mknod, al igual que la función de C que permite
generarlas en ese lenguaje.

En Linux, el comando para generar un FIFO sigue esta estructura:

mknod [opciones] nombreFichero p

13
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Con el parámetro mode indicamos los permisos que tendrá nuestro FIFO.
Una vez generado podemos comprobarlos haciendo un ls-l

Hecho esto, y sabiendo que funciona como una pila, podemos meterle información para que lo
vaya leyendo otro proceso que acceda al FIFO.

Vamos a introducirle el contenido de visualizar los directorios, con un ls, para luego visualizar
por pantalla su contenido con un cat.

En C podemos programar nuestros procesos para que mientras uno crea y está a la escucha
para leer el FIFO, otro introduzca información en él y sea leído por el primero.

Para ello usaremos la función mknod (tiene el mismo nombre que el comando usado para
generar el fichero).

La función mknod tiene la siguiente estructura:

int mknod (const char *pathname, mode_t modo, dev_t dev);

Donde:
Pathname: Nombre del dispositivo
Modo: Especifica tanto los permisos de uso como el tipo de nodo que se creará
Dev: Debe ser una combinación (utilizando OR bit a bit) de uno de los tipos de fichero
que se enumeran a continuación.
• S_IFREG
• S_IFCHR
• S_IFBLK
• S_IFIFO – Para crear un FIFO

El código para la creación y lectura de un FIFO es el siguiente

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

14
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

#include <fcntl.h>

int main(void){

int fp;
int p, bytesLeidos;
char saludo[] = "Un saludo!!!\n", buffer[10];

p = mknod("FIFO3", S_IFIFO|0666, 0); // Permiso de lectura


y escritura

if (p == -1){
printf("Ha ocurrido un error \n");
exit(0);
}

while(1){
fp = open("FIFO3", 0);
bytesLeidos = read(fp, buffer, 1);
printf("Obteniendo información... \n");
while(bytesLeidos != 0){
printf("%s", buffer);
bytesLeidos = read(fp, buffer, 1); // lee otro
byte
}
close(fp);
}

return 0;
}

Y el programa para escribir en el FIFO:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){

int fp;
char saludo[] = "Un saludo!!!\n";
fp = open("FIFO3", 1); // Abre el fichero FIFO ya creado

if(fp == -1){
printf("Error al abrir el fichero...\n");
exit(1);
}

printf("Mandando información al FIFO...\n");


write(fp, saludo, strlen(saludo)); // Manda al FIFO fp la
cadena de texto saludo con los caracteres contados por strlen

close(fp);
return 0;
}

15
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Para comprobar el funcionamiento compilamos y ejecutamos primero el programa para la


lectura (es el que crea el FIFO) y se mantiene a la espera.

Mientras, abrimos otra terminal y lanzamos el programa para escribir en el FIFO.


Podremos ver el desarrollo en tiempo real, siendo algo tal que así

Al mostrar el texto por pantalla lo vemos de esta forma porque uno trabaja en ASCII y otro en
UNICODE, pero para hacernos una idea de que se comunican correctamente sirve.

Señales y sincronización

En un sistema, los procesos que se ejecutan simultáneamente interactúan entre sí. Esta
interacción se produce incluso en el caso de procesos independientes, es decir, los que no
necesitan cooperar para completar sus tareas.

Esto ocurre cuando varios procesos quieren acceder a los mismos recursos, y para resolver
esta situación el SO dispone de un gestor de procesos para determinar el orden de acceso a
estos recursos.

Las señales pueden considerarse un tipo de mensajes, aunque, comparado con otros medios
de comunicación (sockets, pipes, etc.) resultan un mecanismo más pobre porque no permiten
transmitir datos, pero sí proporcionan dos servicios fundamentales:

Defensa del proceso establecido frente a incidencias comunicadas por el kernel. Si las
señales no son gestionadas (o ignoradas, o capturadas) por el proceso al que van
dirigidas, éste concluye inmediatamente lo que puede provocar una pérdida
irrecuperable de datos.
Mecanismo de comunicación entre dos procesos. Resulta útil y sencillo para avisar a
un proceso de la aparición de eventos excepcionales, aunque no debe ser la forma
habitual de que se comuniquen. Por ejemplo, cuando un usuario desea interrumpir un
proceso de impresión que ha mandado por error.

Es decir, el uso de señales es un método sencillo de aviso de incidencias ya sea por


circunstancias del propio proceso o por la intervención del otro.

Las señales pueden llegar en cualquier momento, por lo que los procesos no pueden limitarse
a verificar una variable para comprobar si ha llegado una señal, sino que deben lanzar una
rutina de tratamiento de la señal para gestionar automáticamente su recepción en el
momento que aparezca.

16
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Hay distintos tipos de señales, que podemos ver en éste gráfico

17
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Una de las principales utilidades de las señales es la sincronización entre dos procesos. Esto es,
un proceso realizará un conjunto de instrucciones cuando otro proceso se lo indique, o
paralizará su actividad hasta que se cumpla una condición determinada, teniendo en cuenta la
región crítica, que sería el trozo de código de un proceso que puede interferir con otro
proceso.
Esta secuencia de acciones se ejecutan en paralelo y pueden repetirse infinitamente, con lo
que el bucle tendría una traza tal que así:

1. El proceso padre crea un proceso hijo


2. El proceso padre ejecuta un conjunto de acciones a partir de las cuales se desea que el
proceso hijo continúe
3. Si no hay error y se desea que el hijo ejecute sus acciones:
3.1. El proceso padre envía una señal SIGUSR1 al hijo para que comiece.
3.2. El proceso hijo realiza un conjunto de acciones
3.3. El proceso hijo envía la señal SIGUSR1 al padre
3.4. Volvemos a 2
4. En caso contrario:
4.1. El proceso padre envía una señal SIGTERM al hijo para que termine
4.2. El proceso hijo termina
4.3. El proceso padre termina

El comando kill en Linux envía una señal a un proceso, indicando primero la señal y luego el
PID.
Por ejemplo,termina kill -9 PID el proceso con el PID que hemos indicado.

Para crear una señal en C debemos tener en cuenta la estructura de las funciones que
podemos utilizar

Esto envía una señal invocando a un manejador por puntero para que la reciba y la trate:
void (*signal(int señal, void(*Func)(int)(int));

Esto detiene el proceso hasta recibir una señal.

int pause(void);

Aquí dormimos el proceso en un tiempo indicado en segundos. También se interrumpe si


recibe una señal.

unsigned int sleep(unsigned int seconds);

Y esto envía una señal para matar un proceso.

int kill(int pid, int señal);

Veamos un ejemplo de como un padre invoca a una señal que recibirá el hijo.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

18
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

void manejador (int segnal){

printf("Hijo recibe señal... %d \n", segnal);


}

int main(){

int pid_hijo;
pid_hijo = fork(); // creamos hijo

switch(pid_hijo){
case -1:
printf("ERROR AL CREAR EL PROCESO HIJO... \n");
exit(-1);
break;
case 0: // HIJO
signal(SIGUSR1, manejador); // Invocamos al
puntero al que referencia la función
while(1){};
break;
default: // PADRE
sleep(1);
kill(pid_hijo, SIGUSR1);
sleep(1);
kill(pid_hijo, SIGUSR1);
sleep(1);
break;
}
return 0;
}

19
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Programación concurrente
Recordemos dos definiciones antes de seguir con la programación concurrente.

Programa. Conjunto de instrucciones que se aplican a un conjunto de datos de entrada para


obtener una salida.
Proceso. Un programa en ejecución. La actividad asíncrona susceptible de ser asignada a un
procesador. Una unidad de actividad que se caracteriza por la ejecución de una secuencia de
instrucciones, un estado actual y un conjunto de recursos del sistema asociados.

Ahora ya sí, podemos avanzar con este concepto.

¿Qué es la programación concurrente?

Es la existencia simultánea de varios procesos en ejecución. Es la disciplina que se encarga del


estudio de las notaciones que permiten especificar la ejecución concurrente de las acciones de
un programa, así como las técnicas para resolver los problemas inherentes a la ejecución
concurrente (comunicación y sincronización).

Beneficios

Mejor aprovechamiento de la CPU. Un proceso puede aprovechar ciclos de CPU


mientras otro realiza una operación de entrada / salida.
Velocidad de ejecución. Al subdividir un programa en procesos, estos se pueden
“repartir” entre procesadores o gestionar en un único procesador según importancia.
Solución a problemas de naturaleza concurrente. Existen algunos problemas cuya
solución es más fácil utilizando esta metodología.
• Sistemas de control. Son sistemas en los que hay captura de datos,
normalmente a través de sensores, análisis y actuación en función del análisis.
Un ejemplo son los sistemas en tiempo real
• Tecnologías web. Los servidores web son capaces de atender múltiples
peticiones de usuarios concurrentemente, también los servidores de chat,
correo, los propios navegadores web, etc.
• Aplicaciones basadas en GUI. El usuario puede interactuar con la aplicación
mientras la aplicación está realizando otra tarea. Por ejemplo el navegador
web puede estar descargando un archivo mientras el usuario navega por las
páginas.
• Simulación. Programas que modelan sistemas físicos con autonomía.
• Sistemas gestores de bases de datos. Los usuarios interactúan con el sistema,
cada usuario puede ser visto como un proceso.

Problemas inherentes

Exclusión mutua. En programación concurrente es muy típico que varios procesos


accedan a la vez a una variable compartida para actualizarla. Esto se debe evitar, ya
que puede producir inconsistencia de datos. Uno puede estar actualizando la variable
a la vez que otro la puede estar leyendo. Por ello es necesario conseguir la exclusión
mutua de los procesos respecto a la variable compartida. Para ello se propuso la
región crítica.

20
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Cuando dos p más procesos comparten una variable, el acceso a dicha variable debe
efectuarse siempre dentro de una región crítica asociada a la variable. Solo uno de los
procesos podrá acceder para actualizarla y los demás deberán esperar. El tiempo de
estancia es finito.
Ejemplo:

int var = 7;
while(var <= 100){
printf(“%i”, var);
var++;
}

Si mientras sale ese proceso entra otro y aumentar var en 71 (es un decir), el primer
proceso ya no se llegará a ejecutar 93 veces, porque la variable ha sido alterada por
otro proceso. Este trozo de código es la región crítica.
Condición de sincronización. Hace referencia a la necesidad de coordinar los procesos
con el fin de sincronizar sus actividades. Puede ocurrir que un proceso P1 llegue a un
estado X que no pueda continuar su ejecución hasta que otro proceso P2 haya llegado
a un estado Y de su ejecución. La programación concurrente proporciona mecanismos
para bloquear procesos a la espera de que ocurra un evento y para desbloquearlos
cuando este ocurra.

Además, dentro de la programación concurrente tenemos otro tipo de programación.

Programación paralela

¿Qué es la programación paralela?

El procesamiento paralelo permite que muchos elementos de procesos independientes


trabajen simultáneamente para resolver un problema. Estos elementos pueden ser un número
arbitrario de equipos conectados por una red, un único equipo con varios procesadores o una
combinación de ambos.

Ventajas

Proporciona ejecución simultánea de tareas.


Disminuye el tiempo total de ejecución de una aplicación.
Resolución de problemas complejos y de grandes dimensiones.
Utilización de recursos no locales, por ejemplo los recursos que están en una red
distribuida, una WAN o la propia red Internet.
Disminución de costos, en vez de gastar en un supercomputador muy caro se pueden
utilizar otros recursos más baratos disponibles remotamente.

Inconvenientes

Los compiladores y entornos de programación para sistemas paralelos son más


difíciles de desarrollar.
Los programas paralelos son más difíciles de escribir.
El consumo de energía de los elementos que forman el sistema.
Mayor complejidad en el acceso a los datos.

21
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

La comunicación y la sincronización entre diferentes subtareas.

Asimismo también dentro de la programación paralela tenemos otro tipo de programación.

Programación distribuida

Un sistema distribuido es aquel en el que los componentes hardware o software, localizaos en


computadores unidos mediante una red, comunican y coordinan sus acciones mediante el
paso de mensajes.

Consecuencias

Concurrencia. Lo normal en una red de ordenadores es la ejecución de programas


concurrentes.
Inexistencia de reloj global. Cuando los programas necesitan cooperar coordinan sus
acciones mediante el paso de mensajes.
Fallos independientes. Cada componente del sistema puede fallar
independientemente, permitiendo que los demás continúen su ejecución.

Modelos de programación para la comunicación

Sockets. Proporcionan los puntos externos para la comunicación entre procesos. Es


actualmente la base de la comunicación. Pero al ser de muy bajo nivel de abstracción,
son adecuados a nivel de aplicación. Posteriormente veremos su tratamiento en Java.
Llamada de procedimientos remotos o RPC (Remote Procedure Call). Permite a un
programa cliente llamar a un procedimiento de otro programa en ejecución en un
proceso servidor. El proceso servidor define en su interfaz de servicio los
procedimientos disponibles para ser llamados remotamente.
Invocación remota de objetos. El modelo de programación basado en objetos ha sido
extendido para permitir que los objetos de diferentes procesos se comuniquen uno
con otro por medio de una invocación a un método remoto o RMI (Remote Method
Invocation). Un objeto que vive en un proceso puede invocar métodos de un objeto
que reside en otros procesos. Java RMI extiende el modelo de objetos de Java para
proporcionar soporte de objetos distribuidos en lenguaje Java (si quieremos un
intermediario entre servidor – cliente independientemente del lenguaje del objeto,
usaremos CORBA).

Ventajas

Se puede compartir recursos y datos


Capacidad de crecimiento incremental
Mayor flexibilidad al poderse distribuir la carga de trabajo entre diferentes
ordenadores.
Alta disponibilidad
Soporte de aplicaciones inherentemente distribuidas
Carácter abierto y heterogéneo.

Inconvenientes

Aumento de la complejidad, se necesita un nuevo tipo de software.


Problemas con las redes de comunicación: pérdida de mensajes, saturación del tráfico.

22
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Problemas de seguridad como por ejemplo ataques de denegación de servicio en la


que se bombardea un servicio con peticiones inútiles de forma que un usuario
interesado en usarlo no pueda emplearlo.

23
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Hilos
¿Qué es?

Es una secuencia de código en ejecución dentro del contexto de un proceso.

Diferencia con procesos

La diferencia entre un hilo y un proceso es que el proceso, al crear un hijo, se duplica y tiene su
espacio de direcciones, compitiendo en igualdad de condiciones con el proceso padre. Sin
embargo, en un hilo (o proceso ligero) lo que hace el proceso es crear un espacio de
direcciones dentro de su espacio de direcciones, por lo que la pugna por competir por el
tiempo de procesador se verá en “inferioridad de condiciones” respecto a un proceso padre –
hijo.

Ventajas

Al tener un marco común es fácil crear una variable y que varios procesos accedan a él.

Desventajas

Le tocará menos tiempo de CPU que a un proceso padre – hijo.

Hilos en Java

En Java hay dos formas de implementar hilos.


Una es mediante la clase Thread y otra mediante la Interface Runnable. En cualquiera de los
dos casos hay que implementar el método run(), que será el que contendrá la acción que debe
realizar el hilo.

Aparte del método run() hay otros métodos dentro de la clase Thread que tendremos que
usar:

start() Es el método que invocaremos para que se cree el hilo


stop() Para detener el hilo
sleep(long milisegundos) Duerme el hilo durante el tiempo pasado en milisegundos

24
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

yield() Hace que el hilo en ejecución deje la ejecución para permitir que otros hilos se
ejecuten.

Ver más métodos en http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html

Ejemplo de código en Java

En el siguiente código vamos a ver una clase que herede de Thread (HiloEjemplo) donde
estarán los métodos y propiedades que estimemos oportunos y que tendrá que implementar
obligatoriamente el método run().
La segunda clase será la que contenga el código principal (Main). Creará un objeto de esta
clase y será donde se lance el objeto.

ARCHIVO HILOEJEMPLO.JAVA

public class HiloEjemplo extends Thread {

// Propiedades
private int c; // Contador de cada hilo
private int hilo;

// Constructor
public HiloEjemplo(int hilo){
this.hilo = hilo;
System.out.println("CREANDO HILO: " + hilo);
}

// Métodos
public void run(){
c = 0;
while (c <= 5){
System.out.println("Hilo: " + hilo + " C = " + c);
c++;
}
} // fin del run
}

ARCHIVO MAIN.JAVA

public class Main {


public static void main(String[] argrs){
HiloEjemplo h = null;

for (int i = 0; i < 3; i++){


h = new HiloEjemplo(i+1); // Se crea el hijo
h.start(); // Se inicia el hijo
}
System.out.println("3 HILOS CREADOS...");

} // fin del main


} // fin de clase

Si pruebas a lanzar la ejecución varias veces verás que el resultado que se muestra por pantalla
es variable. Esto es porque actualmente no controlamos el orden en el que los hilos entrán en
la CPU.

25
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Ejercicio. Crea dos clases (hilos) Java que extiendan la clase Thread. Uno de los hilos debe
visualizar por pantalla en un bucle infinito la palabra TIC y el otro hilo la palabra TAC.
Dentro del bucle utiliza el método sleep() para que nos dé tiempo a ver las palabras que se
imprimen cuando lo ejecutamos (tendrás que añadir un bloque try-catch para capturar la
excepción InterruptedException). Crea después la clase Main que haga uso de los hilos
anteriores. ¿Se visualizan los textos de forma ordenada (es decir TIC TAC TIC TAC TIC TAC…)?

ARCHIVO TIC.JAVA

public class Tic extends Thread{

// Propiedades
private int hilo;

// Constructor
public Tic(int hilo){
this.hilo = hilo;
}

// Métodos
public void run(){
while(true){
try{
//this.notify();
System.out.println("TIC");
sleep(1000);
//yield();
//this.wait();
}
catch(InterruptedException e){
e.printStackTrace();
}

}
}
}

ARCHIVO TAC.JAVA

public class Tac extends Thread{

// Propiedades
private int hilo;

// Constructor
public Tac(int hilo){
this.hilo = hilo;
}

// Métodos
public void run(){
while(true){
try{
//this.notify();
System.out.println("TAC");
sleep(1000);

26
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

yield();
//this.wait();
}
catch(InterruptedException e){
e.printStackTrace();
}
}
}
}

ARCHIVO MAIN.JAVA

public class Main {

public static void main(String[] args) {


Tic tic = new Tic(4);
Tac tac = new Tac(2);
tic.start();
tac.start();
}
}

Runnable. Heredar de la clase Thread

Además de extender la clase Thread podemos crear hilos implementando la interface


Runnable(), en la que deberemos escribir cómo se compartará el método run() para tratar los
hilos.

En Java solo puede heredarse de una clase, de modo que implementaremos la interface
Runnable para conseguir algo parecido a la multiherencia.
La usaremos cuando ya hayamos heredado de la clase Thread para poder crear un segundo
hilo.

Un ejemplo sería este:

ARCHIVO PRIMERHILOR.JAVA

public class PrimerHiloR implements Runnable{

// Propiedades
int x;

// Constructor
public PrimerHiloR(int x){
this.x = x;
}

// Método que hay que implementar


public void run(){
for (int i = 0; i < x; i++)
System.out.println("En hilo " + x + ". Paso número " + i);
}
}

27
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

ARCHIVO MAIN.JAVA

public class Main {


public static void main(String[] args){
PrimerHiloR p = new PrimerHiloR(10);
new Thread(p).start();
}
}

Applets

Un Applet es una aplicación Java que se incrusta en una página HTML, aunque los
navegadores, por defecto, no permiten este tipo de programas en las webs porque son una
brecha de seguridad.

Estos applets se extienden de la clase Applet, así que la creación de los hilos, en caso de que
queramos hacerlo todo en una única clase, habrá que dejarlo a Runnable.

Un ejemplo sería este Applet para crear un reloj:

import java.applet.Applet;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class Reloj extends Applet implements Runnable {

private Thread hilo = null; //hilo


private Font fuente; //Tipo de letra para la hora
private String horaActual = "";

public void init(){


fuente = new Font ("Verdana", Font.BOLD, 26);
}//fin de init

public void start(){


if(hilo == null){
hilo = new Thread(this);
hilo.start();
}
}//fin de start

@Override
public void run() {
Thread hiloActual = Thread.currentThread();
while (hilo == hiloActual){
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
Calendar cal = Calendar.getInstance();
horaActual = sdf.format(cal.getTime());
repaint(); //se actualiza el contenido del Applet
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();

28
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

}
}
}//fin de run

public void paint (Graphics g){


g.clearRect(1, 1, getSize().width, getSize().height);
setBackground(Color.yellow); //Color de fondo
g.setFont(fuente);
g.drawString(horaActual, 20, 50);//Muestra la hora
}//fin de paint

public void stop(){


hilo=null;
}
}

O este otro para realizar un contador:

import java.applet.Applet;
import java.awt.Button;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ContadorApplet extends Applet implements Runnable,


ActionListener {
private Thread h;
long CONTADOR = 0;
private boolean parar;
private Font fuente;
private Button b1,b2; //botones del Applet

public void start() {}

public void init() {

setBackground(Color.yellow);//color de fondo
add(b1=new Button("Iniciar contador"));
b1.addActionListener(this);
add(b2=new Button("Parar contador"));
b2.addActionListener(this);
fuente = new Font("Verdana", Font.BOLD, 26);//tipo letra

public void run() {

parar=false;
Thread hiloActual = Thread.currentThread();
while (h == hiloActual && !parar) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();

29
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

}
repaint();
CONTADOR++;
}
}

public void paint(Graphics g) {

g.clearRect(0, 0, 400, 400);


g.setFont(fuente); //fuente
g.drawString(Long.toString((long)CONTADOR),80,100);

//para controlar que se pulsan los botones


public void actionPerformed(ActionEvent e) {

b1.setLabel("Continuar");
if(e.getSource()==b1) //comienzo
{
if(h!=null && h.isAlive()) {
} //Si el hilo está corriendo no hago nada,
else { //lo creo
h = new Thread(this);
h.start();
}
}else if(e.getSource()==b2) //parada
parar=true;

}//fin de actionPerformed

public void stop() {


h = null;
}

}//fin applet

Pero no tiene porqué ser así. Partiendo del ejemplo anterior, podemos realizar un applet que
separe el hilo en una clase aparte dentro del applet que extienda Thread. El applet que ahora
no implementará Runnable, debe quedar así:

public class actividad2_2 extends Applet implements ActionListener {


class HiloContador extends Thread {
//atributos y métodos
. . .
}//fin clase
//atributos y métodos
. . .
}//fin applet

30
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Para hacernos una idea, vamos a crear la misma aplicación de contador que antes pero que
lance dos hilos y muestre dos botones para finalizarlos. Algo así

Es decir, definimos en la clase HiloContador un constructor que reciba el valor inicial del
contador a partir del cual empezará a contar; y el método getContador() que devuelva el valor
actual del contador.

El applet debe crear e iniciar dos hilos de esta clase, cada uno debe empezar con un valor.
Mostrará dos botones, uno para detener el primer hilo y el otro el segundo. Para detener los
hilos usa el método stop(): hilo.stop() (Veremos más adelante que este método está en desuso
y no se debe emplear). Cambiamos también el texto de los botones cuando se pulsen, para
que muestre algún mensaje dependiendo del botón pulsado.

El código queda tal que así:

import java.applet.Applet;
import java.awt.Button;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class AppletContador extends Applet implements ActionListener{

class HiloContador extends Thread{

// Propiedades
long CONTADOR = 0;
boolean parada = false;

// Constructor
public HiloContador(int hilo){
CONTADOR = hilo;
}

// Métodos
public void pararHilo(){
parada = true;
}

31
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

public void activarHilo(){


parada = false;
}

public void run(){


while(!parada){
try{
Thread.sleep(1000);
}
catch(InterruptedException e){
e.printStackTrace();
}
CONTADOR++;
repaint();
}
}
} // fin de clase HiloContador

// Propiedades
private Font fuente;
private Button btn1, btn2;
HiloContador t1, t2;
int a, b;

// Métodos
public void init(){
// Los genero con inicio aleatorio
t1 = new HiloContador((int) (Math.random()*100+1));
t2 = new HiloContador((int) (Math.random()*100+1));
setBackground(Color.orange);
fuente = new Font("Verdana", Font.BOLD, 26);
add(btn1 = new Button("Parar contador 1"));
btn1.addActionListener(this);
add(btn2 = new Button("Para contador 2"));
btn2.addActionListener(this);
t1.start();
t2.start();
}

public void paint(Graphics g){


g.clearRect(0, 0, 400, 400);
g.setFont(fuente); //fuente
g.drawString("Hilo 1: " +
Long.toString((long)t1.CONTADOR),40,100);
g.drawString("Hilo 2: " +
Long.toString((long)t2.CONTADOR),40,150);
//repaint();
}

public void actionPerformed(ActionEvent e) {

if(e.getSource() == btn1)
{
if(t1.isAlive()) {
t1.pararHilo();
a = (int) t1.CONTADOR;
btn1.setLabel("Activar contador 1");

32
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

}
else {
t1.activarHilo();
t1 = new HiloContador(a+1);
t1.start();
btn1.setLabel("Parar contador 1");
}
}else if(e.getSource() == btn2){
if(t2.isAlive()){
t2.pararHilo();
b = (int) t2.CONTADOR;
btn2.setLabel("Activar contador 2");
}
else{
t2.activarHilo();
t2 = new HiloContador(b+1);
t2.start();
btn2.setLabel("Parar contador 2");
}
}
} // Fin actionPerformed
}

33
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Programación multihilo

Estados de un hilo

New (Nuevo). Es el estado cuando se crea un objeto hilo con el operador new. En este
estado el hilo aún no se ejecuta; es decir, el programa no ha comenzado la ejecución
del código del método run() del hilo.
Runnable (Ejecutable). Cuando se invoca al método start(), el hilo pasa a este estado.
El sistema operativo tiene que asignar tiempo de CPU al hilo para que se ejecute; por
tanto, en este estado el hilo puede estar o no en ejecución.
Dead (Muerto). Un hilo muere por varias razones:
• De muerte natural, porque el método run() finaliza con normalidad.
• Repentinamente debido a alguna excepción no capturada en el método run().
• Invocando al método stop(), pero este método está en desuso.

public class HiloEjemploDead extends Thread{

// Propiedades
private boolean stopHilo = false;

// Métodos
public void pararHilo(){
stopHilo = true;
}

public void run(){


while(!stopHilo){
System.out.println("En el hilo");
}
}

// Main

34
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

public static void main(String[] args){


HiloEjemploDead h = new HiloEjemploDead();
h.start();
for (int i = 0; i < 100000; i++); // No hago nada
h.pararHilo();
}
}

Blocked (Bloqueado). En este estado podría ejecutarse el hilo, pero hay algo que lo
evita. Un hilo entra en estado bloquedo cuando ocurre una de las siguientes acciones:
• Alguien llama al método sleep() del hilo, es decir, se ha puesto a dormir.
• El hilo está esperando a que se complete una operación de entrada / salida.
• El hilo llama al método wait(). El hilo no se volverá ejecutable hasta que reciba
los mensajes notify() o notifyAll().
• El hilo intenta bloquear un objeto que está actualmente bloqueado por otro
hilo.
• Alguien llama al método suspend() del hilo. No se volverá a ejecutar de nuevo
hasta que recia el mensaje resume().

Gestión de un hilo

Crear y arrancar

Para crear un hilo extendemos la clase Thread o implementamos la interfaz Runnable.

// Se crea un hilo
MiHilo h = new MiHilo(“Hilo 1”, 200);

// Se arranca el hilo
h.start(); // Si la clase extienda a Thread
new Thread(h).start(); // Si implementa a Runnable

Lo que hace el método start() es llamar al método run() del hilo que es donde se colocan las
acciones que queremos que haga el hilo, cuando finalice el método finalizará también el hilo.

Suspensión

El método suspend() permite detener la actividad del hilo durante un intervalo de tiempo
indeterminado.

Para volver a activar el hilo se necesita invocar al método resume().

El método suspend() es un método obsoleto y tiende a no utilizarse porque puede producir


situaciones de interbloqueos. Por ejemplo, si un hilo está bloqueando un recurso y este hilo se
suspende, puede dar lugar a que otros hilos que esperaban el recurso queden “congelados” ya
que el hilo suspendido mantiene los recursos bloqueados. Igualmente el método resume()
también está en desuso.

Para suspender de forma segura el hilo se debe introducir dentro de este una variable, por
ejemplo suspender, y comprobar su valor dentro del método run().

35
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

class MyThread extends Thread{

// Propiedades
private SuspendRequestor suspender = new SuspendRequestor();

// Métodos
public void requestSuspend(){
suspender.set(true);
}

public void requestResume(){


suspender.set(false);
}

public void run(){


try{
while(/* haya trabajo por hacer */ true){
suspender.waitForResume(); // Realizar el trabajo
}
}
catch(InterruptedException exception){
e.printStackTrace();
}
}
}

class SuspendRequestor{

// Propiedades
private boolean suspendRequested;

// Métodos
public synchronized void set(boolean b){
suspendRequested = b;
notifyAll();
}

public synchronized void waitForResume() throws InterruptedException{


while(suspendRequested)
wait();
}
}

Parada

Los métodos stop(), suspend(), resume() y destroy() han sido abolidos en Java 2 para reducir la
posibilidad de interbloqueo.

El método isAlive() devuelve true si el hilo está vivo, es decir, ha llamado a su método run() y
aún no ha terminado su ejecución o no ha sido detenido con stop(); en caso contrario devuelve
false.

El método interrupt() envía una petición de interrupción a un hilo. Si el hilo se encuentra


bloqueado por una llamada a sleep() o wait() se lanza una excepción InterruptedException.

36
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

El método isInterrupted() devuelve true si el hilo ha sido interrumpid, en caso contrario


devuelve false.

public class HiloEjemploInterrupt extends Thread{

public void run(){


try{
while (!isInterrupted()){
System.out.println("En el hilo");
Thread.sleep(10);
}
}
catch(InterruptedException e){
System.out.println("Ha ocurrido una excepción");
}
System.out.println("Fin de hilo");
}

public void interrumpir(){


interrupt();
}

public static void main(String[] args){


HiloEjemploInterrupt h = new HiloEjemploInterrupt();
h.start();
try{
sleep(20);
}
catch(InterruptedException e){
h.interrumpir();
}
}
}

El método join() provoca que el hilo que hace la llamada espere la finalización de otros hilos.

public class HiloJoin extends Thread{

private int n;

public HiloJoin(String nom, int n){


super(nom);
this.n = n;
}

public void run(){


for (int i = 1; i <= n; i++){
System.out.println(getName() + ":" + i);
try{
sleep(1000);
}
catch(InterruptedException ignore){}
}
System.out.println("Fin bucle " + getName());
}
}

37
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

public class EjemploJoin{


public static void main(String[] args){
HiloJoin h1 = new HiloJoin("Hilo 1", 2);
HiloJoin h2 = new HiloJoin("Hilo 2", 5);
HiloJoin h3 = new HiloJoin("Hilo 3", 7);

h1.start();
h2.start();
h3.start();

try{
h1.join();
h2.join();
h3.join();
}
catch(InterruptedException e){}

System.out.println("Final del programa");


}
}

Prioridad en los hilos

En el lenguaje de programación Java, cada hilo tiene una prioridad. Por defecto un hilo hereda
la prioridad del hilo padre que lo crea.

El método setPriority() permite aumentar o disminuir la prioridad. getPriority() retorna la


prioridad del hilo.

La prioridad va de 1 a 10
• MIN_PRIORITY 1
• MAX_PRIORITY 10
• NORM_PRIORITY 5

Si dos o más hilos tienen la misma prioridad, la máquina virtual va cediendo la prioridad de
forma cíclica (round-robin).

En Windows los valores del contador dependerán de la prioridad asignada al hilo, y en


sistemas Linux (Ubuntu) los valores de los contadores no dependen de la prioridad asignada al
hilo.

Cuando un hilo entra en ejecución y no cede voluntariamente el control para que puedan
ejecutarse otros hilos, se dice que es un “hilo egoísta”. Windows combate esta situación con
una estrategia de planificación por división de prioridad que compiten por la CPU.

A la hora de programar hilos con prioridades hemos de tener en cuenta que el


comportamiento no está garantizado y dependerá de la plataforma en la que se ejecutan los
programas.

En la práctica casi nunca hay que establecer a mano las prioridades.

38
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Ejercicios de gestión de hilos

Un ejercicio típico para gestión de hilos y su sincronización es el problema del productor


consumidor.

Vamos a ver cuatro tipos de este problema. Intentad haced cada uno por vuestra cuenta y
comprobad el resultado posteriormente.

1. Modifica la clase Productor para que envíe las cadenas PING y PONG (de forma
alternativa, una vez PING y otra vez PONG) a la cola y la clase Consumidor tome la cadena de
la cola y la visualice.

La salida tiene que mostrar lo siguiente: PING PONG PING PONG PING PONG PING PONG
PING PONG PING PONG PING PONG PING PONG PING PONG PING PONG PING PONG PING
PONG PING PONG PING....

ARCHIVO COLA.JAVA

package Actividad26_productorConsumidor1;

public class Cola {

private int numero;


private boolean disponible = false; //inicialmente cola vacía

public synchronized int get() {

while(disponible == false){
try{
wait();
}catch (InterruptedException e){}
}//fin de while
System.out.println("PONG");
disponible=false;
notifyAll();
return numero; //se devuelve

}//Fin de get

public synchronized void put() {

while (disponible == true){


try {
wait();
}catch (InterruptedException e){}
}
System.out.println("PING");
disponible=true; //disponible para consumir
notifyAll();

}//Fin de put

}//Fin de Cola

39
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

ARCHIVO PRODUCTOR.JAVA

package Actividad26_productorConsumidor1;

public class Productor extends Thread {

private Cola cola;


private int n;

public Productor(Cola c, int n) {


cola = c;
this.n = n;
}

public void run() {

while(true){
cola.put();
try {
sleep(100);
} catch (InterruptedException e) { }
}

}//Fin de run

}//Fin de Productor

ARCHIVO CONSUMIDOR.JAVA

package Actividad26_productorConsumidor1;

public class Consumidor extends Thread {

private Cola cola;


private int n;

public Consumidor(Cola c, int n) {

cola = c;
this.n = n;
}

public void run() {

int valor = 0;

while(true) {

valor = cola.get(); //recoge el número


}

}//Fin de run

}//Fin de Consumidor

40
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

ARCHIVO PRODUC_CONSUM.JAVA

package Actividad26_productorConsumidor1;

public class Produc_Consum {

public static void main(String[] args) {

Cola cola = new Cola();


Productor p = new Productor(cola, 1);
Consumidor c = new Consumidor(cola, 1);

p.start();
c.start();
}
}

2. Modifica la clase Productor para que el constructor reciba un argumento String que será el
mensaje que debe enviar a la cola y la clase Consumidor toma la cadena de la cola y la
visualice. El programa principal creará dos hilos Productor, uno que escribirá el mensaje
"TIC", y otro que escriba el mensaje "TAC".

La salida tiene que mostrar lo siguiente: TIC TAC TIC TAC TIC TAC TIC TAC TIC TAC TIC TAC TIC
TAC TIC TAC TIC TAC TIC TAC TIC TAC TIC TAC TIC TAC TIC TAC TIC TAC TIC TAC TIC TAC TIC
TAC TIC TAC….

“Refactoriza” el atributo disponible como turnoConsumidor.

ARCHIVO COLA.JAVA

package Actividad26_productorConsumidor2;

import java.util.Stack;

public class Cola {

private int numero;


private boolean turnoConsumidor = false; //inicialmente cola vacía
private Stack<String> contenido = new Stack<String>();
private String resultado;

public synchronized String get() {


while(turnoConsumidor == false){
try{
wait();
}catch (InterruptedException e){}
}//fin de while
resultado = contenido.pop();
turnoConsumidor=false;
notifyAll();
return resultado; //se devuelve

}//Fin de get

public synchronized void put(String cadena) {

41
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

while (turnoConsumidor == true){


try {
wait();
}catch (InterruptedException e){}
}
contenido.push(cadena);
turnoConsumidor=true; //disponible para consumir
notifyAll();

}//Fin de put

}//Fin de Cola

ARCHIVO PRODUCTOR.JAVA

package Actividad26_productorConsumidor2;

public class Productor extends Thread {

private Cola cola;


private int n;
private String cadena;

public Productor(Cola c, int n, String cadena) {


cola = c;
this.n = n;
this.cadena = cadena;
}

public void run() {

while(true){
cola.put(cadena);
try {
sleep(100);
} catch (InterruptedException e) { }
}

}//Fin de run

}//Fin de Productor

ARCHIVO CONSUMIDOR.JAVA

package Actividad26_productorConsumidor2;

public class Consumidor extends Thread {

private Cola cola;


private int n;
private String r;

public Consumidor(Cola c, int n) {

cola = c;
this.n = n;
}

42
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

public void run() {

while(true) {
r = cola.get(); //recoge el número
System.out.println(r);
}

}//Fin de run

}//Fin de Consumidor

ARCHIVO PRODUC_CONSUM.JAVA

package Actividad26_productorConsumidor2;

public class Produc_Consum {

public static void main(String[] args) {

Cola cola = new Cola();


Productor p1 = new Productor(cola, 1, "TIC");
Productor p2 = new Productor(cola, 1, "TAC");
Consumidor c = new Consumidor(cola, 1);

p1.start();
c.start();
p2.start();
}
}

3. “Refactoriza” el nombre de la clase Cola por con un nuevo nombre Monitor. Modifica la
clase Monitor para que el constructor reciba el número de productores que se van a
sincronizar. El programa principal creará 7 hilos Productor, cada uno respectivamente
escribirá el nombre de un día de la semana.

ARCHIVO MONITOR.JAVA

package Actividad26_productorConsumidor3;

import java.util.Stack;

public class Monitor {

// Propiedades
private int numero;
private boolean turnoConsumidor = false; //inicialmente cola vacía
private int turnoProductor = 1;
private Stack<String> contenido = new Stack<String>();
private String resultado;
private int contador = 0;

// Constructor
public Monitor(int numero){
this.numero = numero;
}

43
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

// Métodos
public synchronized String get() {
while(turnoConsumidor == false){
try{
wait();
}catch (InterruptedException e){}
}//fin de while
resultado = contenido.pop();
turnoConsumidor=false;
notifyAll();
return resultado; //se devuelve

}//Fin de get

public synchronized void put(String cadena, int turno) {

while (turnoConsumidor == true || turno != turnoProductor){


try {
wait();
}catch (InterruptedException e){}
}
if (turno == turnoProductor){
contenido.push(cadena);
turnoConsumidor=true; //disponible para consumir
if (turnoProductor == 7)
turnoProductor = 1;
else
turnoProductor++;
}
notifyAll();
}//Fin de put

}//Fin de Cola

ARCHIVO PRODUCTOR.JAVA

package Actividad26_productorConsumidor3;

public class Productor extends Thread {

private Monitor monitor;


private int n;
private String cadena;

public Productor(Monitor monitor, int n, String cadena) {


this.monitor = monitor;
this.n = n;
this.cadena = cadena;
}

public void run() {

while(true){
monitor.put(cadena, n);
try {
sleep(100);
} catch (InterruptedException e) { }

44
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

}//Fin de run

}//Fin de Productor

ARCHIVO CONSUMIDOR.JAVA

package Actividad26_productorConsumidor3;

public class Consumidor extends Thread {

private Monitor monitor;


private int n;
private String r;

public Consumidor(Monitor monitor, int n) {

this.monitor = monitor;
this.n = n;
}

public void run() {

while(true) {
r = monitor.get(); //recoge el número

System.out.println(r);
}

}//Fin de run

}//Fin de Consumidor

ARCHIVO PRODUC_CONSUM.JAVA

package Actividad26_productorConsumidor3;

public class Produc_Consum {

public static void main(String[] args) {

Monitor monitor = new Monitor(7);


Consumidor c = new Consumidor(monitor, 1);
Productor p1 = new Productor(monitor, 1, "Lunes");
Productor p2 = new Productor(monitor, 2, "Martes");
Productor p3 = new Productor(monitor, 3, "Miercoles");
Productor p4 = new Productor(monitor, 4, "Jueves");
Productor p5 = new Productor(monitor, 5, "Viernes");
Productor p6 = new Productor(monitor, 6, "Sabado");
Productor p7 = new Productor(monitor, 7, "Domingo");

c.start();
p1.start();
p2.start();
p3.start();
p4.start();

45
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

p5.start();
p6.start();
p7.start();
}
}

4. Modifica el programa principal anterior para que se creen 12 hilos Productor con los
nombres de los meses. Si no lo has hecho ya, crea un vector de productores.

ARCHIVO MONITOR.JAVA

package Actividad26_productorConsumidor4;

import java.util.Stack;

public class Monitor {

// Propiedades
private int numeroProductores = 0;
private boolean turnoConsumidor = false; //inicialmente cola vacía
private int turnoProductor = 1;
private Stack<String> contenido = new Stack<String>();
private String resultado;
private int contador = 0;

// Constructor
public Monitor(int numeroProductores){
this.numeroProductores = numeroProductores;
}

// Métodos
public void setNumProductores(int n){
this.numeroProductores = n;
}

public synchronized String get() {


while(turnoConsumidor == false){
try{
wait();
}catch (InterruptedException e){}
}//fin de while
resultado = contenido.pop();
turnoConsumidor=false;
notifyAll();
return resultado; //se devuelve

}//Fin de get

public synchronized void put(String cadena, int turno) {

while (turnoConsumidor == true || turno != turnoProductor){


try {
wait();
}catch (InterruptedException e){}
}
if (turno == turnoProductor){
contenido.push(cadena);
turnoConsumidor=true; //disponible para consumir

46
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

if (turnoProductor == numeroProductores)
turnoProductor = 1;
else
turnoProductor++;
}
notifyAll();
}//Fin de put

}//Fin de Cola

ARCHIVO PRODUCTOR.JAVA

package Actividad26_productorConsumidor4;

public class Productor extends Thread {

private Monitor monitor;


private int n;
private String cadena;

public Productor(Monitor monitor, int n, String cadena) {


this.monitor = monitor;
this.n = n;
this.cadena = cadena;
}

public void run() {

while(true){
monitor.put(cadena, n);
try {
sleep(100);
} catch (InterruptedException e) { }
}

}//Fin de run

}//Fin de Productor

ARCHIVO CONSUMIDOR.JAVA

package Actividad26_productorConsumidor4;

public class Consumidor extends Thread {

private Monitor monitor;


private int n;
private String r;

public Consumidor(Monitor monitor, int n) {

this.monitor = monitor;
this.n = n;
}

public void run() {

47
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

while(true) {
r = monitor.get(); //recoge el número

System.out.println(r);
}

}//Fin de run

}//Fin de Consumidor

ARCHIVO PRODUC_CONSUM.JAVA

package Actividad26_productorConsumidor4;
import java.util.Vector;

public class Produc_Consum {

public static void main(String[] args) {

Monitor monitor = new Monitor(7);


Vector<Productor> productores = new Vector<Productor>();
Consumidor c = new Consumidor(monitor, 1);
productores.addElement(new Productor(monitor, 1, "Enero"));
productores.addElement(new Productor(monitor, 2, "Febrero"));
productores.addElement(new Productor(monitor, 3, "Marzo"));
productores.addElement(new Productor(monitor, 4, "Abril"));
productores.addElement(new Productor(monitor, 5, "Mayo"));
productores.addElement(new Productor(monitor, 6, "Junio"));
productores.addElement(new Productor(monitor, 7, "Julio"));
productores.addElement(new Productor(monitor, 8, "Agosto"));
productores.addElement(new Productor(monitor, 9, "Septiembre"));
productores.addElement(new Productor(monitor, 10, "Octubre"));
productores.addElement(new Productor(monitor, 11, "Noviembre"));
productores.addElement(new Productor(monitor, 12, "Diciembre"));
monitor.setNumProductores(productores.size());
c.start();
for (int i = 0; i < productores.size(); i++){
productores.elementAt(i).start();
}
}
}

48
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Procesos concurrentes

Esta sección ha sido extraída del libro Sistemas Operativos, de William Stallings.

Requisitos para la exclusión mutua

El uso adecuado de la concurrencia entre procesos exige la capacidad de definir secciones


críticas y hacer cumplir la exclusión mutua. Esto es fundamental para cualquier esquema de
proceso concurrente. Cualquier servicio o capacidad que dé soporte para la exclusión mutua
debe cumplir los requisitos siguientes:

1. Debe cumplirse la exclusión mutua: Sólo un proceso, de entre todos los que poseen
secciones críticas por el mismo recurso u objeto compartido, debe tener permiso para
entrar en ella en un instante dado.
2. Un proceso que se interrumpe en una sección no crítica debe hacerlo sin estorbar a los
otros procesos.
3. Un proceso no debe poder solicitar acceso a una sección crítica para después ser
demorado indefinidamente; no puede permitirse el interbloqueo o la inanición.
4. Cuando ningún proceso está en su sección crítica, cualquier proceso que solicite entrar en
la suya debe poder hacerlo sin dilación.
5. No se pueden hacer suposiciones sobre la velocidad relativa de los procesos o su número.
6. Un proceso permanece en su sección crítica sólo por un tiempo finito.

Hay varias formas de satisfacer los requisitos de exclusión mutua. Una manera es dejar la
responsabilidad a los procesos que deseen ejecutar concurrentemente. Así pues, tanto si son
programas del sistema como de aplicación, los procesos deben coordinarse unos con otros
para cumplir la exclusión mutua, sin ayuda por parte del lenguaje de programación o del
sistema operativo. Estos métodos se conocen corno soluciones por software. Aunque las
soluciones por software son propensas a errores y a una fuerte carga de proceso, resulta útil
estudiar estos métodos para tener un mejor entendimiento de la complejidad del proceso
concurrente. Un segundo método propone el uso de instrucciones de la máquina a tal efecto.
Estas tienen la ventaja de reducir la sobrecarga pero, sin embargo, no son interesantes. El
tercer método consiste en dar algún tipo de soporte en el sistema operativo.

Exclusión mutua. Soluciones por software

Pueden implementarse soluciones de software para los procesos concurrentes que ejecuten
en máquinas monoprocesador o multiprocesador con una memoria principal compartida.
Formalmente, estas soluciones suponen que existe una exclusión mutua elemental en el
acceso a memoria ([LAMP91]). Es decir, los accesos simultáneos (lecturas y/o escrituras) a la
misma posición de memoria se hacen en serie, por medio de algún tipo de árbitro de memoria,
aunque el orden en el que se conceden los accesos no se conoce por adelantado. Aparte de
esto, no se requiere ningún soporte del hardware, del sistema operativo o del lenguaje de
programación.

Algoritmo de Dekker

Dijkstra [DIJK65] presentó un algoritmo de exclusión mutua para dos procesos que diseñó el
matemático holandés Dekker. Según Dijkstra, la solución se desarrolla por etapas. Este método

49
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

tiene la ventaja de ilustrar la mayoría de los errores habituales que se producen en la


construcción de programas concurrentes. A medida que se construya el algoritmo, se
emplearán algunas ilustraciones pintorescas tomadas de Ben-Ari para escenificar la acción
[BEN82].

Primer intento
Como se mencionó anteriormente, cualquier intento de exclusión mutua debe depender de
algunos mecanismos básicos de exclusión en el hardware. El más habitual es la restricción de
que sólo se puede realizar un acceso a una posición memoria en cada instante. Como metáfora
de este arbitrio de la memoria, la figura 4.3 muestra el "protocolo del iglú". Tanto la entrada
como el mismo iglú son tan pequeños que sólo puede entrar una persona a la vez en el iglú.
Dentro, hay una pizarra en la que se puede escribir un único valor.

El protocolo es el siguiente. Un proceso (P0 o P1) que desee ejecutar su sección crítica entra
primero en el iglú y examina la pizarra. Si su número está escrito en ella, el proceso puede
abandonar el iglú y continuar con su sección crítica. En otro caso, abandona el iglú y se ve
obligado a esperar. De vez en cuando, el proceso vuelve a entrar en el iglú para mirar la
pizarra. Esta operación la repite hasta que se le permite entrar en su sección crítica. Este
procedimiento se denomina espera activa porque un proceso frustrado no puede hacer nada
productivo hasta que obtiene permiso para entrar en su sección crítica. En su lugar, debe
persistir y comprobar periódicamente el iglú; así pues, consume tiempo del procesador (está
activo) mientras espera su oportunidad.
Después de que un proceso haya obtenido acceso a su sección crítica y una vez que termine
con ella, debe volver al iglú y escribir el número del otro proceso en la pizarra.

En términos formales, hay una variable global compartida:


var turno: 0 .. 1;

El programa para los dos procesos:

PROCESO 0 PROCESO 1
… …
… …
while turno != 0 do {nada} while turno != 1 do {nada}
<sección crítica> <sección crítica>
turno := 1 turno := 0

Esta solución garantiza el cumplimiento de la exclusión mutua. Hay dos inconvenientes en esta
solución. Primero, los procesos deben alternarse de forma estricta en el uso de sus secciones

50
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

críticas; así pues, el ritmo de ejecución viene dictado por el más lento. Si P0 usa su sección
crítica sólo una vez por hora, pero P1 quiere usarla con una tasa de 1000 veces por hora, P1
está obligado a adoptar el ritmo de P0. Un problema mucho más serio es que si un proceso
falla (por ejemplo, se lo come un oso polar en su camino hacia el iglú), el otro proceso se
bloquea permanentemente. Esto es cierto tanto si un proceso falla en su sección crítica como
fuera de ella.
La estructura anterior es la de una corrutina. Las corrutinas se diseñaron para poder pasar el
control de la ejecución de un proceso a otro. Aunque es una técnica de estructuración útil para
un solo proceso, no resulta apropiada para dar soporte al proceso concurrente.

Segundo intento
El problema de la primera tentativa es que se almacenaba el nombre del proceso que podía
entrar en su sección crítica cuando, de hecho, lo que hace falta es tener información del
estado de ambos procesos. En realidad, cada proceso debería tener su propia llave de la
sección crítica para que, si un oso polar elimina a uno de ellos, el otro pueda seguir accediendo
a su sección crítica.
Esta filosofía queda ilustrada en la figura 4.4. Cada proceso tiene ahora su propio iglú y puede
mirar la pizarra del otro, pero no modificarla. Cuando un proceso debe entrar en su sección
crítica, comprueba periódicamente la pizarra del otro hasta que encuentra escrito en ella
"falso", lo que indica que el otro proceso no está en su sección crítica. Entonces, se dirige
rápidamente hacia su propio iglú, entra y escribe "cierto" en la pizarra. El proceso puede ahora
continuar con su sección crítica. Cuando deja su sección crítica, cambia su pizarra para que
ponga "falso".

La variable global compartida es ahora:


var señal: array [0 .. 1] of booleano;

Que está inicializada con falso. El programa para los dos procesos es:

PROCESO 0 PROCESO 1
while señal[1] do {nada}; while señal[0] do {nada};
señal[0] := cierto; señal[1] := cierto;
<sección crítica> <sección crítica>
señal[0] := falso; señal[1] := falso;

Ahora, si uno de los procesos falla fuera de la sección crítica, incluyendo el código para dar
valor a las señales, el otro proceso no se queda bloqueado. De hecho, el otro proceso puede
entrar en su sección crítica tantas veces como quiera, porque la señal del otro proceso está

51
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

siempre puesta a falso. Sin embargo, si un proceso falla en su sección crítica, el otro proceso
está bloqueado permanentemente.
En realidad, esta solución es, si acaso, peor que el primer intento, pues no siempre se garantiza
la exclusión mutua. Considérese la siguiente secuencia:

• P0 ejecuta la sentencia while y encuentra señal [1] a falso.


• P1 ejecuta la sentencia while y encuentra señal [0] a falso.
• P0 pone señal [0] a cierto y entra en su sección crítica.
• P1 pone señal [1] a cierto y entra en su sección crítica.

Puesto que ambos procesos están en sus secciones críticas, el programa es incorrecto. El
problema está en que la solución propuesta no es independiente de la velocidad de ejecución
relativa de los procesos.

Tercer intento
El segundo intento falla porque un proceso puede cambiar su estado después de que el otro
proceso lo ha comprobado pero antes de que pueda entrar en su sección crítica. Quizá se
pueda arreglar este problema con un simple intercambio de dos líneas:

PROCESO 0 PROCESO 1
… …
… …
señal[0] := cierto; señal[1] := cierto;
while señal[1] do {nada}; while señal[0] do {nada};
<sección crítica> <sección crítica>
señal[0] := falso; señal[1] := falso;

Como antes, si un proceso falla dentro de la sección crítica, incluyendo el código para dar valor
a las señales que controlan el acceso a la sección crítica, el otro proceso se bloquea y si un
proceso falla fuera de su sección crítica, el otro proceso no se bloquea.

A continuación, se comprobará que la exclusión mutua está garantizada desde el punto de


vista del proceso P0. Una vez que P0 ha puesto señal [0] a "cierto", P1 no puede entrar a su
sección crítica hasta que P0 haya entrado y abandonado la suya. Puede ser que P1 esté todavía
en su sección crítica cuando P0 activa su señal. En ese caso, P0 se bloqueará en la sentencia
while hasta que P1 deje su sección crítica. El mismo razonamiento es aplicable desde el punto
de vista de Pl.

Esta solución garantiza la exclusión mutua, pero origina un problema más. Si ambos procesos
ponen sus señales a "cierto" antes de que ambos hayan ejecutado la sentencia while, cada uno
pensará que el otro ha entrado en su sección crítica. El resultado es un interbloqueo.

Cuarto intento
En el tercer intento, un proceso fijaba su estado sin conocer el estado del otro. El interbloqueo
se produce porque cada proceso puede insistir en su derecho para entrar en la sección crítica;
no hay opción para volver atrás desde esta situación. Se puede intentar arreglar esto haciendo
que los procesos sean más educados: Deben activar su señal para indicar que de¬sean entrar
en la sección crítica, pero deben estar listos para desactivar la señal y ceder la preferencia al
otro proceso:

52
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

PROCESO 0 PROCESO 1
… …
… …
señal[0] := cierto; señal[1] := cierto;
while señal[1] while señal[0]
begin begin
señal[0] := falso; señal[1] := falso;
<espera cierto tiempo> <espera cierto tiempo>
señal[0] := cierto señal[1] := cierto
end; end;
<sección crítica> <sección crítica>
señal[0] := falso; señal[1] := falso;

Esta solución se aproxima a la correcta pero todavía es defectuosa. La exclusión mutua aún
está garantizada con un razonamiento similar al seguido en el estudio del tercer intento. Sin
embargo, considérese la siguiente secuencia de sucesos:

• P0 pone señal [0] a cierto


• P1 pone señal [1] a cierto
• P0 comprueba señal [1]
• P1 comprueba señal [0]
• P0 pone señal [0] a falso
• P1 pone señal [1] a falso
• P0 pone señal [0] a cierto
• P1 pone señal [1] a cierto

Esta secuencia podría prolongarse indefinidamente y ningún proceso podría entrar en su


sección crítica. Estrictamente hablando, esto no es un interbloqueo, porque cualquier cambio
en la velocidad relativa de los dos procesos rompería este ciclo y permitiría a uno entrar en la
sección crítica. Aunque no es probable que esta secuencia se mantenga por mucho tiempo, es
una situación posible. Así pues, se rechaza el cuarto intento.

Una solución correcta


Hay que poder observar el estado de ambos procesos, que viene dado por la variable señal.
Pero, como demuestra el cuarto intento, esto no es suficiente. De algún modo, se hace
necesario imponer algún orden en la actividad de los dos procesos para evitar el problema de
"cortesía mutua" que se acaba de observar. La variable turno del primer intento puede usarse
en esta labor; en este caso, la variable indica qué proceso tiene prioridad para exigir la entrada
a la sección crítica.

53
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Es posible describir esta solución en términos de iglúes fijándose en la figura 4.5. Ahora hay un
iglú "árbitro" con una pizarra llamada "turno". Cuando P0 quiere entrar en su sección crítica,
pone su señal a "cierto". A continuación, va y mira la señal de P1. Si ésta está puesta a falso, P0
puede entrar inmediatamente en su sección crítica. En otro caso, P0 va a consultar al árbitro. Si
encuentra el turno = 0, sabe que es momento de insistir y comprueba periódicamente el iglú
de P1. Este otro se percatará en algún momento de que es momento de ceder y escribirá
"falso" en su pizarra, permitiendo continuar a P0. Después de que P0 haya ejecutado su
sección crítica, pone su señal a "falso" para liberar la sección crítica y pone turno a 1 para
traspasar el derecho de insistir a P1.

La figura 4.6 ofrece una especificación del algoritmo de Dekker. La demostración se deja como
ejercicio:

Algoritmo de Peterson
El algoritmo de Dekker resuelve el problema de la exclusión mutua pero con un programa
complejo, difícil de seguir y cuya corrección es difícil de demostrar. Peterson [PETE81] ha
desarrollado una solución simple y elegante. Como antes, la variable global señal indica la

54
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

posición de cada proceso con respecto a la exclusión mutua y la variable global turno resuelve
los conflictos de simultaneidad. El algoritmo se expone en la figura 4.7.

Se puede demostrar fácilmente que se cumple la exclusión mutua. Considérese el proceso P0.
Una vez que ha puesto señal[0] a cierto, P1 no puede entrar en su sección crítica. Si P1 está
aún en su sección crítica, señal[l] = cierto y P0 está bloqueado para entrar en su sección crítica.
Por otro lado, se impide el bloqueo mutuo. Supóngase que P0 está bloqueado en su
bucle while. Esto significa que señal[1] es cierto y turno = 1. P0 puede entrar en su sección
crítica cuando señal[l] se ponga a falso o cuando turno se ponga a 0. Considérense ahora los
siguientes casos exhaustivos:

• P1 no está interesado en entrar en su sección crítica. Este caso es imposible porque


implica que señal[l]= falso.
• P1 está esperando entrar en su sección crítica. Este caso es también imposible porque
si turno = 1, P1 podrá entrar en su sección crítica.
• P1 entra en su sección crítica varias veces y monopoliza el acceso a ella. Esto no puede
pasar porque P1 está obligado a dar a PO una oportunidad poniendo turno a O antes
de cada intento de entrar en su sección crítica.

Así pues, se tiene una solución simple al problema de la exclusión mutua para dos procesos. Es
más, el algoritmo de Peterson se puede generalizar fácilmente al caso de n procesos [HOFR90].

Ejercicio Dekker / Peterson

Crea una clase para gestionar el saldo de una Cuenta. Debe tener métodos para obtener el
saldo actual, hacer un ingreso (se incrementa al saldo), hacer un reintegro (se le resta al saldo),
controlar si hay algún error, por ejemplo, si se hace un reintegro y no hay saldo; o si se hace un

55
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

ingreso y el saldo supera el máximo; mostrar mensajes con los movimientos que se realicen. Si
ocurre alguno de los errores anteriores finaliza el proceso. La cuenta recibe en su constructor
el saldo actual y el valor máximo que puede tener. Los métodos de ingreso y reintegro deben
definirse como synchronized.
Crea después la clase Persona que extienda Thread y que realice en su método run() ingresos
y/o reintegros de forma aleatoria y con algún sleep(int tiempo) en medio; hasta que ocurra
alguno de los errores nombrados anteriormente.

• Para determinar la cantidad a ingresar o retirar en cada movimiento genera números


aleatorios entre una cantidad mínima (CANTIDADMIN) y una cantidad máxima
(CANTIDADMAX) con la función random():

int cantidad = ((int) (Math.random()*(CANTIDADMAX -


CANTIDADMIN) +CANTIDADMIN);

• Para simular clientes muy activos, que van muchas veces al banco, y clientes
tranquilos, genera otro número aleatorio para el tiempo que transcurrirá entre dos
movimientos, TIEMPOMIN y TIEMPOMAX.

int tiempo = ((int) (Math.random()*(TIEMPOMAX - TIEMPOMIN) +


TIEMPOMIN) * 1000; //En ms

• Para simular personas ahorradoras y personas gastadoras utilizarás un parámetro que


indique el tanto por ciento de veces que hace ingresos (caracterAhorradorGastador)

int tipoMovimiento= ((int) (Math.random()*100 + 1);


if (tipoMovimiento < caracterAhorradorGastador)
movimiento = +1; //A ingresar dinero
else
movimiento = -1; //A retirar dinero

El constructor de la clase debe llevar el nombre de la persona, CANTIDADMIN, CANTIDADMAX,


TIEMPOMIN, TIEMPOMAX, caracterAhorradorGastador.
Crea en el método main() un objeto Cuenta compartido por varios objetos Persona e inicia el
proceso de realizar movimientos en la cuenta.

Implementar la solución usando los algoritmos de Dekker o Peterson (mínimo para dos
clientes).

ARCHIVO CUENTA.JAVA

package CompruebaTuAprendizaje4;
import java.util.Vector;

public class Cuenta {

// Propiedades
private boolean[] bandera = new boolean[2];
private int turno = 0;
private int saldoActual;
private int saldoMaximo;

// Constructor

56
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

public Cuenta(int saldoActual, int saldoMaximo){


inicializarArray(bandera);
this.saldoActual = saldoActual;
this.saldoMaximo = saldoMaximo;
}

// Metodos
public synchronized void ingresar(int cantidad, int turno, Persona
ultimoCliente){
bandera[turno] = true;

if (turno == 0)
this.turno = 1;
else
this.turno = 0;

if (turno == 0){
while(bandera[1] && turno == 1){
try{
Thread.sleep(300);
}
catch(InterruptedException e){}
} // Fin While

// SECCION CRITICA
seccionCriticaIngreso(cantidad, ultimoCliente);
} // Fin if Thread 0

if (turno == 1){
while(bandera[0] && turno == 0){
try{
Thread.sleep(300);
}
catch(InterruptedException e){}
} // Fin While

// SECCION CRITICA
seccionCriticaIngreso(cantidad, ultimoCliente);
} // Fin if Thread 1
bandera[turno] = false;
notifyAll();
} // Fin ingresar

public synchronized void retirar(int cantidad, int turno, Persona


ultimoCliente){
bandera[turno] = true;

if (turno == 0)
this.turno = 1;
else
this.turno = 0;

if (turno == 0){
while(bandera[1] && turno == 1){
try{
Thread.sleep(300);
}
catch(InterruptedException e){}

57
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

} // Fin While

// SECCION CRITICA
seccionCriticaRetirada(cantidad, ultimoCliente);
} // Fin if Thread 0

if (turno == 1){
while(bandera[0] && turno == 0){
try{
Thread.sleep(300);
}
catch(InterruptedException e){}
} // Fin While

// SECCION CRITICA
seccionCriticaRetirada(cantidad, ultimoCliente);
} // Fin if Thread 1
bandera[turno] = false;
notifyAll();
} // Fin retirar

private void inicializarArray(boolean[] array){


array[0] = false;
array[1] = false;
}

private void seccionCriticaIngreso(int cantidad, Persona


ultimoCliente){
if (saldoActual + cantidad <= saldoMaximo){
saldoActual = saldoActual + cantidad;
System.out.println(ultimoCliente.getNombre() + " ha
ingresado " + cantidad + " euros");
}
else{
System.out.println(ultimoCliente.getNombre() + " ha
superado el máximo permitido");
ultimoCliente.stop();
}// Fin if zona critica
}

private void seccionCriticaRetirada(int cantidad, Persona


ultimoCliente){
if (saldoActual - cantidad >= 0){
saldoActual = saldoActual + cantidad;
System.out.println(ultimoCliente.getNombre() + " ha
retirado " + cantidad + " euros");
}
else{
System.out.println(ultimoCliente.getNombre() + " ha
superado el máximo permitido");
ultimoCliente.stop();
} // Fin if zona critica
}

58
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

ARCHIVO PERSONA.JAVA

package CompruebaTuAprendizaje4;

public class Persona extends Thread{

// Propiedades
private String nombre;
private int numCliente;
private Cuenta cuenta;

// Constructor
public Persona(String nombre, int numCliente, Cuenta cuenta){
this.nombre = nombre;
this.numCliente = numCliente;
this.cuenta = cuenta;
}

// Métodos
public void run(){
while(true){
cuenta.ingresar(generarCifra(), numCliente, this);
try{
sleep(300);
}
catch(InterruptedException e){}

cuenta.retirar(generarCifra(), numCliente, this);


try{
sleep(300);
}
catch(InterruptedException e){}
} // Fin while
} // Fin run

public int generarCifra(){


return (int) (Math.random()*500+1);
}

public String getNombre(){


return nombre;
}
}

ARCHIVO MAIN.JAVA

package CompruebaTuAprendizaje4;
import java.util.Vector;

public class Main {

public static void main(String[] args) {


Cuenta cuenta = new Cuenta(2000, 10000);
Vector<Persona> gente = new Vector<Persona>();
gente.addElement(new Persona("Inazio", 0, cuenta));
gente.addElement(new Persona("Claver", 1, cuenta));
for (int i = 0; i < gente.size(); i++){
gente.elementAt(i).start();

59
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

}
}
}

60
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Programación en red

TCP/IP

Nivel de Transporte

TCP: Protocolo basado en la conexión, garantiza que los datos enviados desde un extremo de la
conexión llegan al otro extremo y en el mismo orden en que fueron enviados. De lo contrario, se
notifica un error.

UDP: No está basado en la conexión como TCP. Envía paquetes de datos independientes,
denominados datagramas, de una aplicación a otra; el orden de entrega no es importante y no se
garantiza la recepción de los paquetes enviados.

Paquete java.net
Proporciona las clases para la implementación de aplicaciones de red. Se pueden dividir en dos
secciones:

Una API de bajo nivel, que se ocupa de las abstracciones siguientes:


• Las direcciones: Son los identificadores de red, como por ejemplo las
direcciones IP.
• Sockets: Son los mecanismos básicos de comunicación bidireccional de datos.
• Interfaces: Describen las interfaces de red.
Una API de alto nivel, que se ocupa de las abstracciones siguientes:
• URI: Representan identificadores de recursos universales.
• URLs: Representan los localizadores de recursos universales.
• Conexiones: Representa las conexiones al recurso apuntado por URL.

Clase InetAddress
Es la abstracción que representa una dirección IP (Internet Protocol).
Tiene dos subclases: Inet4Address para direcciones IPV4 en Inet6Address para direcciones
IPv6; pero en la mayoría de los casos no es necesario recurrir a ellas.

61
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Ejemplo:

Archivo TestInetAddress.java

import java.net.*;

public class TestInetAddress {

public static void main(String[] args) {

InetAddress dir = null;

System.out.println("==========================================="
);
System.out.println("SALIDA PARA LOCALHOST: ");

try {
//LOCALHOST
dir = InetAddress.getByName("PC-ProfeB02");
pruebaMetodos(dir);

//URL www.google.es

System.out.println("==========================================")
;
System.out.println("SALIDA PARA UNA URL:");
dir = InetAddress.getByName("www.google.es");
pruebaMetodos(dir);

// Array de tipo InetAddress con todas las


direcciones IP asignadas a google.es
System.out.println("\tDIRECCIONES IP PARA: " +
dir.getHostName());
InetAddress[] direcciones =
InetAddress.getAllByName(dir.getHostName());
for (int i = 0; i < direcciones.length; i++)

System.out.println("\t\t"+direcciones[i].toString());

System.out.println("==========================================")
;
} catch (UnknownHostException e1) {
e1.printStackTrace();
}

62
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

}// main

private static void pruebaMetodos(InetAddress dir) {

System.out.println("\tMetodo getByName(): " + dir);


InetAddress dir2;
try {
dir2 = InetAddress.getLocalHost();
System.out.println("\tMetodo getLocalHost(): " +
dir2);
} catch (UnknownHostException e) {
e.printStackTrace();
}

//USAMOS METODOS DE LA CLASE


System.out.println("\tMetodo getHostName():
"+dir.getHostName());
System.out.println("\tMetodo getHostAddress(): "+
dir.getHostAddress());
System.out.println("\tMetodo toString(): " +
dir.toString());
System.out.println("\tMetodo getCanonicalHostName(): " +
dir.getCanonicalHostName());

}//fin de pruebaMetodos

}//fin de TestInetAddress

En el ejemplo anterior se define un objeto InetAddress de nombre dir. En primer lugar lo


utilizamos para obtener la dirección IP de la máquina local en la que se ejecuta el programa, en
el ejemplo su nombre es PC-PROFEB02. A continuación llamamos al método pruebaMetodos()
llevando el objeto creado. En dicho método se prueban los métodos de la clase InetAddress.
Después utilizamos el objeto para obtener la dirección IP de la URL www.google.es y volvemos
a invocar a pruebaMetodos() (para que funcione en este segundo caso necesitamos estar
conectados a Internet). Por último utilizamos el método getAllByName() para ver todas las
direcciones IP asignadas a la máquina representada por www.google.es. Se encierra todo en un
bloque try-catch.

Clase URL
Represneta un puntero a un recurso en la Web. Un recurso puede ser algo tan simple como un
fichero o un directorio, o puede ser una referencia a un objeto más complicado, como una
consulta a una base de datos o a un motor de búsqueda.

En general una URL se divide en varias partes. Por ejemplo en la siguiente URL:

63
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Constructores

Pueden lanzar la excepción MalformedURLException si la URL está mal construida.


No se hace ninguna verificación de que realmente exista la máquina o el recurso en la red.

Algunos métodos

Ejemplos:

En la siguiente clase vemos el uso de los constructores definidos anteriormente

Archivo Ejemplo1URL.java

import java.net.*;

public class Ejemplo1URL {

public static void main(String[] args) {

URL url;
try {
System.out.println("Constructor simple para una
URL:");
url = new URL("http://docs.oracle.com/");
Visualizar(url);

64
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

System.out.println("Otro constructor simple para una


URL:");
url = new URL("http://localhost/PFC/gest/cli
gestion.php?S=3" );
Visualizar(url);

System.out.println("Const. para protocolo +URL


+director1o");
url = new URL("http", "docs.oracle.com",
"/javase/7");
Visualizar(url);

System.out.println("Constructor para protocolo + URL


+ puerto directorio:");
url = new URL("http", "docs.oracle.com", 80,
"/javase/7");
Visualizar(url);

System.out.println("Constructor para un objeto URL y


un directorio:");
URL urlBase = new URL("http://docs.oracle.com/");
url = new URL(urlBase,
"/javase/7/docs/api/java/net/URL.html");
Visualizar(url);
} catch (MalformedURLException e) {
System.out.println(e);
}

}// fin de main

private static void Visualizar(URL url) {

System.out.println("\tURL completa: " + url.toString());


System.out.println("\tgetProtocol()" + url.getProtocol());
System.out.println("\tgetHost(): " + url.getHost());
System.out.println("\tgetPort(): " + url.getPort());
System.out.println("\tgetFile(): " + url.getFile());
System.out.println("\tgetUserInfo()" + url.getUserInfo());
System.out.println("\tgetPath(): " + url.getPath());
System.out.println("\tgetAuthority(): " +
url.getAuthority());
System.out.println("\tgetQuery(): " + url.getQuery());

System.out.println("============================================
==");

}// fin de Visualizar()

}// EjemplolURL

En el siguiente ejemplo creamos un objeto URL a la dirección http://www.elaltozano.es, abre


una conexión con él creando un objeto InputStream y lo utiliza como flujo de entrada para leer
los datos de la página inicial del sitio; al ejecutar el programa se muestra en pantalla el código
HTML de la página inicial del sitio.

Archivo Ejemplo2URL.java

import java.net.*;
import java.io.*;

65
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

public class Ejemplo2URL {

public static void main(String[] args) {

URL url=null;

try {
url = new URL("http://www.elaltozano.es");
} catch (MalformedURLException e) {
e.printStackTrace();
}

BufferedReader in;
try {
InputStream inputstream = url.openStream();
in = new BufferedReader(new
InputStreamReader(inputstream));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
} catch (IOException e) {
e.printStackTrace();
}

}// Fin de main

}//Fin de Ejemplo2URL

Clase URLConnection
Es una clase abstracta que contiene métodos que permiten la comunicación entre aplicaciones y
una URL.

Para conseguir un objeto de este tipo se invoca al método openConnection(), con ello
obtenemos una conexión al objeto URL referenciado.

Las instancias de esta clase se pueden utilizar tanto para leer como para escribir al recurso
referenciado por la URL.

Puede lanzar la excepción IOException.

Algunos métodos

66
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Ejemplo

Archivo Ejemplo1urlCon.java

import java.net.*;
import java.io.*;

public class Ejemplo1urlCon {

public static void main(String[] args) {

URL url=null;
URLConnection urlCon=null;

try {
url = new URL("http://www.elaltozano.es");
urlCon= url.openConnection();

BufferedReader in;
InputStream inputStream = urlCon.getInputStream();
in = new BufferedReader(new
InputStreamReader(inputStream));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
}
catch (MalformedURLException e) {e.printStackTrace();}
catch (IOException e) {e.printStackTrace();
}

}//fin de main

}//Fin de Ejemplo1urlCon

En el anterior ejemplo se crea un objeto URL a la dirección http://www.elaltozano.es, se invoca


al método openConnection() del objeto para crear una conexión y se obtiene un
URLConnection. Después se abre un stream de entrada sobre esa conexión mediante el método
getInputStream(). Al ejecutar el programa se muestra la misma salida que en el ejemplo
anterior; sin embargo, este programa crea una conexión con la URL y el anterior abre
directamente un stream desde la URL.

67
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Scripts del lado del servidor

Archivo index.html

<html>
<body>
<form action="vernombre.php" method="post" >
<p>Escribe tu nombre:
<input name="nombre" type="text" size="15">
</p>
<p>Escribe tus apellidos:
<input name="apellidos" type="text" size="15">
</p>
<input type="submit" name="ver" value="Ver">
</form>
</body>
</html>

Archivo vernombre.php

<?php
$nom=$_POST[nombre];
$ape=$_POST[apellidos];
echo "El nombre recibido es: $nom, y ";
echo "los apellidos son: $ape ";
?>

Desde Java, usando la clase URLConnection, podemos interactuar con scripts del lado del
servidor y podemos enviar valores a los campos del script sin necesidad de abrir un formulario
HTML.

Nuestro programa tendrá que hacer lo siguiente:

Crear el objeto URL al script con el que va a interactuar.


Abrir una conexión con la URL, es decir obtener el objeto URLConnection.
Configurar la conexión para que se puedan enviar datos.
Obtener un stream de salida sobre la conexión.
Escribir en el stream de salida
Cerrar el stream de salida

En el siguiente ejemplo se puede ver esta interacción con el script del lado servidor.

Archivo Ejemplo2urlCon.java

import java.io.*;
import java.net.*;

public class Ejemplo2urlCon {

public static void main(String[] args) {

try {

URL url = new


URL("http://localhost/DAM2PSP/vernombre.php");
URLConnection conexion = url.openConnection();
conexion.setDoOutput(true);

68
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

String cadena ="nombre=Maria Jesús&apellidos=Ramos


Martin";

//ESCRIBIR EN LA URL
PrintWriter output = new PrintWriter
(conexion.getOutputStream());
output.write(cadena);
output.close(); //cerrar flujo

//LEER DE LA URL
BufferedReader reader = new BufferedReader (new
InputStreamReader(conexion.getInputStream()));
String linea;
while ((linea = reader.readLine()) != null) {
System.out.println(linea);
}
reader.close();//cerrar flujo

} catch (MalformedURLException me) {


System.err.println("MalformedURLException: " + me);
} catch (IOException ioe) {
System.err.println("IOException: " + ioe);
}

}//fin de main

}//Ejemplo2urlCon

En el ordenador del profesor PC-ProfeB02 tenemos instalado un servidor web Apache y dentro
de htcdocs tenemos la carpeta DAM2PSP con el script PHP vernombre.php.
Normalmente cuando se pasa información a algún script PHP, este realiza alguna acción y
después envía la información de vuelta por la misma URL. Por tanto, si queremos ver lo que
devuelve será necesario leer desde la URL.

En el tercer ejemplo de esta clase se pureban algunos de los métodos de la clase


URLConnection.

Archivo Ejemplo3urlCon

import java.net.*;
import java.io.*;
import java.util.*;

public class Ejemplo3urlCon {

@SuppressWarnings("rawtypes")
public static void main(String[] args) throws Exception {

String cadena;
URL url = new URL("http://localhost/2014/vernombre.html");
URLConnection conexion = url.openConnection();

System.out.println("Direccion [getURL()]:" +
conexion.getURL());

Date fecha = new Date(conexion.getLastModified());


System.out.println("Fecha ultima modificacion -
[getLastModified()]: " + fecha);

69
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

System.out.println("Tipo de Contenido [getContentType()]: "


+ conexion.getContentType());

System.out.println("============================================
===============");
System.out.println("TODOS LOS CAMPOS DE CABECERA CON
getHeaderFields(): ");

//USAMOS UNA ESTRUCTURA Map PARA RECUPERAR CABECERAS


Map camposcabecera = conexion.getHeaderFields();
Iterator it = camposcabecera.entrySet().iterator();
while (it.hasNext()) {
Map.Entry map = (Map.Entry) it.next();
System.out.println(map.getKey() + " : " +
map.getValue());
}

System.out.println("============================================
");
System.out.println("CAMPOS 1 Y 4 DE CABECERA:");
System.out.println("getHeaderField(l)=> "+
conexion.getHeaderField(1));
System.out.println("getHeaderField(4)=> " +
conexion.getHeaderField(4));

System.out.println("============================================
");
System.out.println("CONTENIDO DE [url.getFile()]:"+
url.getFile());
BufferedReader pagina = new BufferedReader(new
InputStreamReader(url.openStream()));

while ((cadena =pagina.readLine()) != null) {


System.out.println(cadena);

}//Fin de main

}//Fin de Ejemplo3urlCon
}

Nota: Para recorrer una estructura Map podemos usar una estructura Iterator. Para obtener un
iterador sobre el map se invoca a los métodos entrySet() e iterator(). Para mover el iterador
utilizaremos el método next() y para comprobar si ha llegado al final usamos el método
hasNext(). De la estructura recuperaremos los valores mediante getKey(), para la clave y
getValue(), para el valor.

70
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Programación de sockets

¿Qué son los sockets?


Los protocolos TCP y UDP utilizan la abstracción de sockets para proporcionar los puntos
extremos de la comunicación entre aplicaciones o procesos.

Para los procesos receptores de mensajes, su conector debe tener asociado dos campos:
La dirección IP del host en el que la aplicación está corriendo.
El puerto local a través del cual la aplicación se comunica y que identifica el proceso.

Funcionamient en general de un socket


El programa cliente conoce el nombre de la máquina en la que se ejecuta el servidor y el número
de puerto por el que escucha las peticiones. Para realizar una solicitud de conexión, el cliente
realiza la petición a la máquina a través del puerto.

Si todo va bien, el servidor acepta la conexión. Una vez aceptada, el servidor obtiene un nuevo
socket sobre un puerto diferente. Esto se debe a que por un lado debe seguir atendiendo las
peticiones de conexión mediante el socket original y por otro debe antender las necesidades del
cliente que se conectó.

71
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

En el lado del cliente, si se acepta la conexión, se crea un socket y el cliente puede utilizarlo
para comunicarse con el servidor. Este socket utiliza un número de puerto diferente al usado
para conectarse al servidor. El cliente y el servidor pueden ahora comunicarse escribiendo y
leyendo por sus respectivos sockets.

Clase ServerSocket
Se utiliza para implementar el extremo de la conexión que corresponde al servidor, donde se
crea un conector en el puerto del servidor que escucha las peticiones de conexión de los clientes.

Constructores

Algunos métodos importantes

Ejemplo

Archivo SocketServidor.java

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServidor {

public static void main(String[] args) throws IOException {

int Puerto = 6000;// Puerto


ServerSocket Servidor=null;

Servidor = new ServerSocket(Puerto);

System.out.println("Escuchando en " +
Servidor.getLocalPort());

Socket clientel = Servidor.accept();//esperando a un


cliente

72
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

//realizar acciones con clientel

Socket cliente2 = Servidor.accept();//esperando a otro


cliente
//realizar acciones con cliente2

Servidor.close(); //cierro socket servidor

}//fin de main

}//fin de SocketServidor

Clase Socket
Implementa un extremo de la conexión TCP.

Constructores

Algunos métodos importantes

Ejemplo

Archivo SocketCliente.java

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;

public class SocketCliente {

public static void main(String[] args) throws IOException {

73
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

String Host = "localhost";


int Puerto = 6000;//puerto remoto

// ABRIR SOCKET
Socket Cliente = new Socket(Host, Puerto);//conecta

InetAddress i= Cliente.getInetAddress ();


System.out.println("Puerto local: "+
Cliente.getLocalPort());
System.out.println("Puerto Remoto: "+ Cliente.getPort());
System.out.println("Host Remoto: "+
i.getHostName().toString());
System.out.println("IP Host Remoto: "+
i.getHostAddress().toString());

Cliente.close();// Cierra el socket

}//fin de main

}//fin de SocketCliente

Gestión de Sockets TCP


El programa servidor crea un socket de servidor definiendo un puerto, mediante el método
ServerSocket(port), y espera mediante el método accept() a que el cliente solicite la conexión.

Cuando el cliente solicita una conexión, el servidor abrirá la conexión al socket con el método
accept().

El cliente establece una conexión con la máquina host a través del uerto especificado mediante
el método Socket(host, port).

El cliente y el servidor se comunican con manejadores InputStream y OutputStream. El


cliente escribe los mensajes en el OutputStream asociado al socket y el servidor leerá los
mensajes del cliente de InputStream. Igualmente el servidor escribirá los mensajes al
OutputStream y el cliente los leerá del InputStream.

74
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Hay puertos TCP de 0 a 65535.

Los puertos en el rango de 0 a 1023 están reservados para servicios privilegiados.

De 1024 a 49151 están reservados para aplicaciones concretas (por ejemplo el 3306 lo usa
MySQL, el 1521 Oracle).

De 49152 a 65535 no están reservados para ninguna aplicación concreta.

Ejemplo.

El programa servidor (Ejemplo1Servidor) recibe un mensaje de un cliente y lo muestra por


pantalla; después envía un mensaje al cliente. Se han eliminado los bloques try-catch para que el
código resulte más legible.

Archivo Ejemplo1Servidor.java

import java.io.*;
import java.net.*;

public class Ejemplo1Servidor {

public static void main(String[] arg) throws IOException {

int numeroPuerto = 6000;// Puerto


ServerSocket servidor = new ServerSocket(numeroPuerto);
Socket clienteConectado = null;
System.out.println("Esperando al cliente.....");
clienteConectado = servidor.accept();

// CREO FLUJO DE ENTRADA DEL CLIENTE


InputStream entrada = null;
entrada = clienteConectado.getInputStream();
DataInputStream flujoEntrada = new
DataInputStream(entrada);

// EL CLIENTE ME ENVIA UN MENSAJE


System.out.println("Recibiendo del CLIENTE: \n\t" +
flujoEntrada.readUTF());

// CREO FLUJO DE SALIDA AL CLIENTE


OutputStream salida = null;
salida = clienteConectado.getOutputStream();
DataOutputStream flujoSalida = new
DataOutputStream(salida);

// ENVIO UN SALUDO AL CLIENTE


flujoSalida.writeUTF("Saludos al cliente del servidor");

// CERRAR STREAMS Y SOCKETS


entrada.close();
flujoEntrada.close();
salida.close();
flujoSalida.close();
clienteConectado.close();
servidor.close();

}// main

75
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

}// fin de Ejemplo1Servidor

El programa cliente (Ejemplo1Cliente) envía un mensaje al servidor y después recibe un


mensaje del servidor visualizándolo en pantalla, se ha simplificado la obtención de los flujos de
entrada y salida.

Archivo Ejemplo1Cliente.java

import java.io.*;
import java.net.*;

public class Ejemplo1Cliente {

public static void main(String[] args) throws Exception {

String Host = "localhost";


int Puerto = 6000;//puerto remoto

System.out.println("PROGRAMA CLIENTE INICIADO....");


Socket Cliente = new Socket(Host, Puerto);

// CREO FLUJO DE SALIDA AL SERVIDOR


DataOutputStream flujoSalida = new
DataOutputStream(Cliente.getOutputStream());

// ENVIO UN SALUDO AL SERVIDOR


flujoSalida.writeUTF("SaludOS al SERVIDOR DESDE EL
CLIENTE");

// CREO FLUJO DE ENTRADA AL SERVIDOR


DataInputStream flujoEntrada = new
DataInputStream(Cliente.getInputStream());

// EL SERVIDOR ME ENVIA UN MENSAJE


System.out.println("Recibiendo del SERVIDOR: \n\t" +
flujoEntrada.readUTF());

// CERRAR STREAMS Y SOCKETS


flujoEntrada.close();
flujoSalida.close();
Cliente.close();

}// fin de main

}// Fin de Ejemplo1Cliente

Conexión de múltiples clientes. Hilos


Un único servidor con la clase ServerSocket e invocar al método accept() para esperar las
peticiones de conexión de los clientes.

Cuando un cliente se conecta, el método accept() devuelve un objeto Socket, éste se usará para
crear un hilo cuya misión es atender a este cliente.

Después se vuelve a invocar a accept() para esperar a un nuevo cliente; habitualmente la espera
de conexiones se hace dentro de un bucle infinito.

76
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Archivo Servidor.java

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Servidor {

public static void main(String args[]) throws IOException {

ServerSocket servidor;
servidor = new ServerSocket(6000);
System.out.println("Servidor iniciado...");
while (true) {
Socket cliente = new Socket();
cliente=servidor.accept();//esperando cliente
HiloServidor hilo = new HiloServidor(cliente);
hilo.start(); //Se atiende al cliente

}// Fin de while


}// Fin de main
}// Fin de Servidor

Clases para Sockets UDP


Los Sockets UDP son más simples y eficientes que los TCP pero no está garantizada la entrega
de paquetes.

No es necesario establecer una “conexión” entre cliente y servidor, como en el caso de TCP.

Los datagramas deben contener explícitamente la dirección IP y el puerto de destino.

El paquete del datagrama está formado por los siguientes campos:

77
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Clase DatagramPacket

Crea instancias de los paquetes Datagrama.

Constructores

Algunos métodos importantes

Clase DatagramSocket

Da soporte a sockets para el envío y recepción de datagramas UDP.

Constructores

78
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Algunos métodos importantes son

Gestión de sockets UDP


En los sockets UDP no se establece conexión.

Podemos considerar servidor al que espera un mensaje y responde; y cliente al que inicia la
comunicación.

Tanto uno como otro si desean ponerse en contacto necesitan saber en qué ordenador y en qué
puerto está escuchando el otro.

1. El servidor crea un socket asociado a un puerto local para escuchar peticiones de


clientes. Permanece a la espera de recibir peticiones.
2. El cliente creará un socket para comunicarse con el servidor. Para enviar datagramas
necesita conocer su IP y el puerto por el que escucha. Utilizará el método send() del
socket para enviar la petición en forma de datagrama.
3. El servidor recibe las peticiones mediante el método receive() del socket. En el
datagrama va incluido además del mensaje, el puerto y la IP del cliente emisor de la
petición; lo que le permite al servidor conocer la dirección del emisor del datagrama.
Utilizando el método send() del socket puede enviar la respuesta al cliente emisor.
4. El cliente recibe la respuesta del servidor mediante el método receive() del socket.
5. El servidor permanece a la espera de recibir más peticiones.

79
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Ejemplo

Archivo ServidorUDP.java

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class ServidorUDP {

public static void main(String[] argv) throws Exception {

byte[] bufer = new byte[1024];//bufer para recibir el


datagrama

//ASOCIO EL SOCKET AL PUERTO 12345


DatagramSocket socket = new DatagramSocket(12345);

//ESPERANDO DATAGRAMA
System.out.println("Esperando Datagrama ................");
DatagramPacket recibo = new DatagramPacket(bufer,
bufer.length);
socket.receive(recibo);//recibo datagrama
int bytesRec = recibo.getLength();//obtengo numero de bytes
String paquete= new String(recibo.getData());//obtengo
String

//VISUALIZO INFORMACIÓN
System.out.println("Número de Bytes recibidos: " +
bytesRec);
System.out.println("Contenido del Paquete : " +
paquete.trim());
System.out.println("Puerto origen del mensaje: " +
recibo.getPort());
System.out.println("IP de origen : " +
recibo.getAddress().getHostAddress());
System.out.println("Puerto destino del mensaje:" +
socket.getLocalPort());

80
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

socket.close(); //cierro el socket

}//Fin de main

}// Fin de SerivdorUDP

El programa servidor (ServidorUDP) recibe un datagrama enviado por un programa cliente. El


programa servidor permanece a la espera hasta que le llega un paquete del cliente; en este
momento visualiza: el número de bytes recibidos, el contenido del paquete, el puerto y la IP del
programa cliente y el puerto local por el que recibe las peticiones.

Archivo ClienteUDP.java

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class ClienteUDP {

public static void main(String[] argv) throws Exception {

InetAddress destino = InetAddress.getLocalHost();


int port = 12345; //puerto al que envío el datagrama
byte[] mensaje = new byte[1024];

String Saludo="Enviando Saludos !!";


mensaje = Saludo.getBytes(); //codifico String a bytes

//CONSTRUYO EL DATAGRAMA A ENVIAR


DatagramPacket envio = new DatagramPacket (mensaje,
mensaje.length, destino, port);
DatagramSocket socket = new DatagramSocket(34567);//Puerto
local
System.out.println("Enviando Datagrama de longitud: "+
mensaje.length);
System.out.println("Host destino : "+
destino.getHostName());
System.out.println("IP Destino : " +
destino.getHostAddress());
System.out.println("Puerto local del socket: " +
socket.getLocalPort());
System.out.println("Puerto al que envio: " +
envio.getPort());

//ENVIO DATAGRAMA
socket.send(envio);
socket.close(); //cierro el socket

}//Fin de main

}//Fin de ClienteUDP

El programa cliente envía un mensaje al servidor (máquina destino, en este caso es la máquina
local, localhost) al puero 12345 por el que espera peticiones. Visualiza el nombre del host de
destino y la dirección IP. También visualiza el puerto local del socket y el puerto al que envía el
mensaje.

81
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Ejemplo 2

Archivo ClienteUDP2.java

import java.io.*;
import java.net.*;

public class ClienteUDP2 {

public static void main(String args[]) throws Exception {

// FLUJO PARA ENTRADA ESTANDAR


BufferedReader in = new BufferedReader (new
InputStreamReader(System.in));

DatagramSocket clientSocket = new DatagramSocket();//socket


cliente
byte[] enviados = new byte[1024];
byte[] recibidos = new byte[1024];

// DATOS DEL SERVIDOR al que enviar mensaje


InetAddress IPServidor = InetAddress.getLocalHost();//
localhost
int puerto = 9876; // puerto por el que escucha

// INTRODUCIR DATOS POR TECLADO


System.out.print("Introduce mensaje: ");
String cadena = in.readLine();
enviados = cadena.getBytes();

// ENVIANDO DATAGRAMA AL SERVIDOR


System.out.println("Enviando " + enviados.length + " bytes
al servidor.");
DatagramPacket envio = new DatagramPacket (enviados,
enviados.length, IPServidor, puerto);
clientSocket.send(envio);

// RECIBIENDO DATAGRAMÄ DEL SERVIDOR


DatagramPacket recibo = new DatagramPacket (recibidos,
recibidos.length);
System.out.println("Esperando datagrama....");
clientSocket.receive(recibo);
String mayuscula = new String(recibo.getData());

// OBTENIDENDO INFORMACIÓN DEL DATAGRAMA


InetAddress IPOrigen = recibo.getAddress();
int puertoOrigen = recibo.getPort();
System.out.println("\tProcedente de: " + IPOrigen + ":" +
puertoOrigen);
System.out.println("\tDatos: " + mayuscula.trim());

//cerrar socket
clientSocket.close();

}//Fin de main

}//Fin de ClienteUDP2

El programa cliente envía un texto tecleado en su entrada estándar al servidor (en un pueto
pactado), el servidor lee el datagrama y devuelve al cliente el texto en mayúscula. El programa

82
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

cliente recibe un datagrama del servidor y muestra información del mismo en pantalla (IP,
puerto del servidor y el texto en mayúscula).

Archivo ServidorUDP2.java

import java.io.*;
import java.net.*;

public class ServidorUDP2 {

public static void main(String args[]) throws Exception {

//Puerto por el que escucha el servidor: 9876


DatagramSocket serverSocket = new DatagramSocket(9876);
byte[] recibidos = new byte[1024];
byte[] enviados = new byte[1024];
String cadena;

while(true) {
System.out.println ("Esperando datagrama.....");

//RECIBO DATAGRAMA
recibidos = new byte[1024];
DatagramPacket paqRecibido = new DatagramPacket
(recibidos, recibidos.length);
serverSocket.receive(paqRecibido);
cadena = new String(paqRecibido.getData());

//DIRECCION ORIGEN
InetAddress IPOrigen = paqRecibido.getAddress();
int puerto = paqRecibido.getPort();
System.out.println ("\tOrigen: " + IPOrigen + ":" +
puerto);
System.out.println ("\tMensaje recibido: " +
cadena.trim());

//CONVERTIR CADENA A MAYÚSCULA


String mayuscula = cadena.trim().toUpperCase();
enviados = mayuscula.getBytes();

//ENVIO DATAGRAMA AL CLIENTE


DatagramPacket paqEnviado = new DatagramPacket
(enviados, enviados.length, IPOrigen, puerto);
serverSocket.send(paqEnviado);

//Para terminar
if(cadena.trim().equals("*")) break;

}//Fin de while

serverSocket.close();
System.out.println ("Socket cerrado...");

}//Fin de main

}//Fin de ServidorUDP2

El programa servidor finaliza cuando recibe como cadena un asterisco.

83
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

MulticastSocket
La clase MulticastSocket es útil para enviar paquetes a múltiples destinos simultáneamente.

Para poder recibir estos paquetes es necesario establecer un grupo multicast, que es un grupo de
direcciones IP que comparten el mismo número de puerto.

Cuando se envía un mensaje a un grupo de multicast, todos los que pertenezcan a ese grupo
recibirán el mensaje.

La pertenencia al grupo es transparente al emisor, es decir, el emisor no conoce el número de


miembros del grupo ni sus direcciones IP.

Grupo multicast

Un grupo multicast sse especifica mediante una dirección IP de clase D y un número de puerto
UDP estándar.

Las direcciones desde la 224.0.0.0 a la 239.255.255.255 están destinadas para ser direcciones de
multicast.

La dirección 224.0.0.0 está reservada y no debe ser utilizada.

Constructores

Algunos métodos importantes son

Esquema general para un servidor multicast:

Se crea el socket multicast. No hace falta especificar puerto


MuslticastSocket ms = new MulticastSocket();
Se define el pueto multicast
int Puerto = 12345;
Se crea el grupo multicast
InetAddress grupo = InetAddress.getByName(“225.0.0.1”);
Se crea el datagrama
DatagramPacket paquete = new DatagramPacket(msg.getBytes(),
msg.length(), grupo, Puerto);
Se envía el paquete al grupo

84
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

ms.send(paquete);
Se cierra el socket
ms.close();

Esquema general para un cliente multicast:

Se crea un socket multicast en el puerto establecido


MulticastSocket ms = new MulticastSocket(12345);
Se configura la IP del grupo al que nos conectaremos
InetAddress grupo = InetAddress.getByName(“225.0.0.1”);
Se une al grupo
ms.joinGroup(grupo);
Recibe el paquete del servidor multicast
byte[] buf = new byte[1000];
DatagramPacket recibido = new DatagramPacket(buf, buf.length);
ms.receive(recibido);
Salimos del grupo multicast:
ms.leaveGroup(grupo);
Se cierra el socket
ms.close();

Ejemplo

En este ejemplo tenemos un servidor multicast que lee datos por teclado y los envía a todos
los clientes que pertenezcan al grupo multicast, el proceso terminará cuando se introduzca un
asterisco.

El programa cliente visualiza el paquete que recibe el servidor, su proceso finaliza cuando
recibe un asterisco.

Archivo ServidorMC.java

import java.io.*;
import java.net.*;

public class ServidorMC {

public static void main(String args[]) throws Exception {

// FLUJO PARA ENTRADA ESTANDAR


BufferedReader in = new BufferedReader(new
InputStreamReader(System.in));

//Se crea el socket multicast.


MulticastSocket ms = new MulticastSocket();
int Puerto = 12345;//Puerto multicast
InetAddress grupo =
InetAddress.getByName("225.0.0.1");//Grupo
String cadena="";

while(!cadena.trim().equals("*")) {

System.out.print("Datos a enviar al grupo: ");


cadena = in.readLine();

85
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

// ENVIANDO AL GRUPO
DatagramPacket paquete = new DatagramPacket
(cadena.getBytes(), cadena.length(), grupo, Puerto);
ms.send (paquete);

}//Fin de while

//cierro socket
ms.close ();
System.out.println ("Socket cerrado...");

}//Fin de main

}//Fin de ServidorMC

Archivo ClienteMC.java

import java.io.*;
import java.net.*;

public class ClienteMC {

public static void main(String args[]) throws Exception {

//Se crea el socket multicast


int Puerto = 12345;//Puerto multicast

MulticastSocket ms = new MulticastSocket(Puerto);


InetAddress grupo =
InetAddress.getByName("225.0.0.1");//Grupo

//Nos unimos al grupo


ms.joinGroup (grupo);
String msg="";

while(!msg.trim().equals("*")) {

//Recibe el paquete del servidor multicast


byte[] buf = new byte[1000]; // Genero dentro el
buffer para que se sobreescriba al enviar un nuevo mensaje
DatagramPacket paquete = new DatagramPacket(buf,
buf.length);
ms.receive(paquete);
msg = new String(paquete.getData());
System.out.println ("Recibo: " + msg.trim());

}//Fin de while

ms.leaveGroup(grupo); //abandonamos grupo

//cierra socket
ms.close();
System.out.println("Socket cerrado...");

}//Fin de main

}//Fin de ClienteMC

86
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Envío de objetos a través de Socket TCP

ObjectInputStream & ObjectOutputStream

Las clases ObjectInputStream y ObjectOutputStream nos permiten enviar objetos a través de


sockets TCP.

ObjectOutputStream outObjeto = new


ObjectOutputStream(socket.getOutoutStream());
ObjectInputStream inObjeto = new
ObjectInputStream(socket.getInputStream());

Utilizaremos los métodos readObject() para leer el objeto del stream y writeObject() para
escribir el objeto al stream.

Serialización

Para que un programa java pueda convertir un objeto en un montón de bytes y pueda luego
recuperarlo, el objeto necesita ser Serializable. Al poder convertir el objeto a bytes, ese objeto
se puede enviar a través de red, guardarlo en un fichero, y después reconstruirlo al otro lado
de la red, leerlo del fichero…

Para que un objeto sea serializable basta con que implemente la interfaz Serialzable. Como la
interfaz Serializable no tiene métodos, es muy sencillo implementarla, basta con un
implements Serializable y nada más.

Si dentro de la clase hay atributos que son otras clases, éstos a su vez también deben ser
Serializables. Con los tipos de java (String, Integer, etc.) no hay problema porque lo son. Si
ponemos como atributo nuestras propias clases, éstas a su vez deben implementar
Serializable.

Ejemplo

En el siguiente ejemplo vamos a ver como el programa servidor crea un objeto Persona,
dándole valores y se lo envía al programa cliente, el programa cliente realiza los cambios
oportunos en el objeto y se lo devuelve modificado al servidor.

ARCHIVO SERVIDOROBJETO.JAVA

import java.io.*;
import java.net.*;

public class ServidorObjeto {

public static void main(String[] arg) throws IOException,


ClassNotFoundException {

int numeroPuerto = 6000;// Puerto


ServerSocket servidor = new ServerSocket(numeroPuerto);
System.out.println("Esperando al cliente.....");
Socket cliente = servidor.accept();

87
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

// Se prepara un flujo de salida para objetos


ObjectOutputStream outObjeto = new ObjectOutputStream(
cliente.getOutputStream());

// Se prepara un objeto y se envía


Persona per = new Persona("Juan", 20);
outObjeto.writeObject(per);

//enviando objeto
System.out.println("Envio: " + per.getNombre() +"*"+
per.getEdad());

// Se obtiene un stream para leer objetos


ObjectInputStream inObjeto = new ObjectInputStream(
cliente.getInputStream());
Persona dato = (Persona) inObjeto.readObject();
System.out.println("Recibo: " + dato.getNombre() + "*" +
dato.getEdad());

// CERRAR STREAMS Y SOCKETS


outObjeto.close();
inObjeto.close();
cliente.close();
servidor.close();

}// Fin de main

}// Fin de ServidorObjeto

ARCHIVO CLIENTEOBJETO.JAVA

import java.io.*;
import java.net.*;

public class ClienteObjeto {

public static void main(String[] arg) throws IOException,


ClassNotFoundException {

String Host = "localhost";


int Puerto = 6000;//puerto remoto
System.out.println("PROGRAMA CLIENTE INICIADO....");
Socket cliente = new Socket(Host, Puerto);

//Flujo de entrada para objetos


ObjectInputStream perEnt = new
ObjectInputStream(cliente.getInputStream());

//Se recibe un objeto


Persona dato = (Persona) perEnt.readObject();

//recibo objeto
System.out.println("Recibo: " + dato.getNombre() + "*" +
dato.getEdad());

//Modifico el objeto
dato.setNombre("Juan Ramos");
dato.setEdad(22);

//FLUJO DE salida para objetos

88
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

ObjectOutputStream perSal = new ObjectOutputStream(


cliente.getOutputStream());

// Se envía el objeto
perSal.writeObject(dato);
System.out.println("Envio: " + dato.getNombre() + "*" +
dato.getEdad());

// CERRAR STREAMS Y SOCKETS


perEnt.close();
perSal.close();
cliente.close();

}// Fin de main

}// Fin de ClienteObjeto

ARCHIVO PERSONA.JAVA

import java.io.Serializable;

@SuppressWarnings("serial")
public class Persona implements Serializable {

String nombre;
int edad;

public Persona(String nombre, int edad) {


super();
this.nombre = nombre;
this.edad = edad;
}
public Persona() { super(); }

public String getNombre() { return nombre; }


public void setNombre(String nombre) { this.nombre = nombre; }
public int getEdad() { return edad; }
public void setEdad(int edad) {this.edad = edad; }

}// Fin de Persona

Envío de objetos a través de Sockets UDP

Utilizaremos las clases ByteArrayOutputStream y ByteARrayInputStream. Se necesita


convertir el objeto a un array de bytes.

Persona persona = new Persona("Maria", 22);

// Convertimos objeto a bytes


ByteArrayOutputStream bs = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bs);

// Escribir objeto a bytes


out.writeObject(persona);

// Cerrar stream
out.close();

89
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

// Objeto en bytes
byte[] bytes = bs.toByteArray();

Para convertir los bytes recibidos por el datagrama en un objeto Persona escribimos:

// Recibo datagrama
byte[] recibidos = new byte[1024];
DatagramPacket paqRecibido = new DatagramPacket(recibidos,
recibidos.length);
socket.receive(paqRecibido); // recibo el datagrama

// Convertirmos bytes a objetos


ByteArrayInputStream bais = new ByteArrayInputStream(recibidos);
ObjectInputStream in = new ObjectInputStream(bais);

// Obtengo objeto
Persona persona = (Persona)in.readObject();

in.close(); // Cerrar stream

RMI

Introducción a las aplicacione RMI

Las aplicaciones RMi normalmente comprenden dos programas separados: un servidor y un


cliente. Una aplicación servidor típica crea un montón de objetos remotos, hace accesibles
unas referencias a dichos objetos remotos, y espera a que los clientes llamen a estos métodos
u objetos remotos. Una aplicación cliente típica obtiene una referencia remota de uno o más
objetos remotos en el servidor y llama a sus métodos.

RMI proporciona el mecanismo por el que se comunican y se pasan información del cliente al
servidor y viceversa. Cuando es una aplicación algunas veces nos referimos a ella como
Aplicación de Objetos Distribuidos.

Objetos distribuidos

Elementos principales:
Interfaces remotas
Objetos remotos
Objetos serializables
Stubs
Servicio de nombres

Interfaces remotas. Es una interfaz acordada entre el servidor y el cliente. Un método que el
cliente puede invocar.
Las clases de los parámetros y del resultado han de ser serializables (en Java simplemente una
interfaz) o remotos.

Un objeto se convierte en remoto implementando un interface remoto, que tenga estas


características.
Un interface remoto desciende del interface java.rmi.Remote.

90
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Cada método del interface declara que lanza una java.rmi.RemoteException además de
cualquier excepción específica de la aplicación.

Objetos remotos. Son objetos cuyos mensajes pueden ser invocados remotamente (desde
objetos corriendo en otro proceso. En el caso de Java sería desde otra JVM).

Los objetos remotos deben implementar uno o varios interfaces remotos.

La clase del objeto remoto podría incluir implementaciones de otros interfaces (locales o
remotos) y otros métodos (que sólo estarán disponibles localmente). Si alguna clase local va a
ser utilizada como parámetro o cómo valor de retorno de alguno de esos métodos, también
debe ser implementanda.

Objetos serializables. El RMI utiliza el mecanismo de serialización de objetos para transportar


objetos entre máquinas virtuales. Implementar Serializable hace que la clase sea capaz de
convertirse en un stream de bytes auto-descriptor que puede ser utilizado para reconstruir
una copia exacta del objeto serializado cuando el objeto es leído desde el stream.

Stubs. Actúan como referencias a objetos remotos en el cliente. Es una clase usada por el
cliente en sustitución de la remota.

Su clase es generada automáticamente a partir de la interfaz, e implementa la interfaz remota.

La implementación de cada operación envía un mensaje a la máquina virtual que ejecuta el


objeto remoto y recibe el resultado, retransmitiendo llamadas desde el cliente hacia el
servidor y siendotransparente al código del cliente.

Cuando un cliente invoca una operación remota que devuelve una referencia a un objeto
remoto, obtiene una instancia del stub correspondiente.

Servicio de nombres. Permite asociar nombres lógicos a objetos.


El servidor asocia un nombre a un objeto, el cliente obtiene una referencia al objeto a partir
del nombre (stub), y así se conseguiría el objetivo, tener transparencia de localización.

Pasaje de objetos

El intercambio de objetos en la ejecución de un método en un objeto remoto puede ocurrir


cuando:
Un cliente pasa objetos como parámetro pues así lo requiere el signature del método.
El server recibe y puede manipular esos objetos.
El servidor reponde a la ejecución de un método a partir de un objeto. El cliente puede
utilizar ahora ese objeto que recibe como respuesta.

Cualquiera de estos objetos (parámetros o respuesta) deben ser remotos y si no lo son deben
ser serializables.

Si son remotos:
Estará tipado con una clase que extiende de UnicastRemoteObject.
Se pasan por referencia.
Los objetos remotos se convierten en stubs al pasar del servidor al cliente.

91
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Si no son objetos que se pueden acceder de manera remota (deben al menos ser serializables):
No están tipados con una clase que extienda UnicastRemoteObject (o alguna que
indique que es remoto).
Deben implementar java.io.Serializable (de lo contrario se produce una excepción).
Son pasados por valor.
RMI se ocupa de la serialización de forma transparente para el desarrollador.

Ejemplo RMI

Interfaces Java que deriban de la interfaz java.rmi.Remote.

Todos los métodos deben declarar java.rmi.RemoteException.

Argumetnos que pueden tomar los métodos:


Tipos primitivos Java
Stubs y objetos remotos
Objetos locales serializables (implementan la lase java.io.Serializable)

Implementación de Objetos Remotos

Subclase de java.rmi.server.UnicastRemoteObject que implementa la interfaz remota.


Implementar todos los métodos de la interfaz remota.

Programa Servidor

Crea instancias de las clases remotas y las registra en el servidor de nombres

Programa cliente

Declara objetos de la interfaz remota y obtiene stubs del servidor de nombres.


Invoca métodos sobre los objetos.

Java RMI define un servicio de nombres muy secillo.

El esquema de nombrado sigue la sintaxis de una URL (//maquina:puerto/nombreDeObjeto)


siendo:
nombreDeObjeto un nombre simple

92
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

máquina y puerto hacen referencia a la máquina en la que corre el servidor de


nombres.
Por defecto, máquina = localhost y puerto = 1099.

Servicio de nombres: rmiregistry

Aplicación que contiene un objeto que implementa el interfaz java.rmi.registry.Registry, no


siendo persistente.

Por motivos de seguridad, la implementación de rmiregistry prohíbe que se invoquen los


métodos bind, rebind y unbind de su objeto Registry desde otra máquina.

La clase java.rmi.Naming

Ejecución

1. Arrancar el servidor de nombres (rmiregistry)


2. Correr la clase servidor
3. Correr el (los) cliente(s)
4. En el momento de la ejecución, el cliente debe disponer en su máquina de:
a. .class de cada interfaz remota
b. .class de cada clase stub correspondiente

Ejemplo RMI. Código

Interfaz

package rmi_sample;

import java.rmi.Remote;
import java.rmi.RemoteException;

93
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

public interface StringerInterface extends Remote{

String getString() throws RemoteException;


}

Servidor

package rmi_sample;

import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class Stringer extends UnicastRemoteObject implements


StringerInterface{

private String str = "DEFAULT";

public Stringer(String s) throws RemoteException{


super();
str = s;
}

public String getString() throws RemoteException{


return str;
}

public static void main(String[] args) {


String name;
StringerInterface robject;

if(System.getSecurityManager() == null){
System.setSecurityManager(new RMISecurityManager());
}

name = "//localhost/StringerInterface";
try{
robject = new Stringer("Hi\n");
Naming.rebind(name, robject);
System.out.println("Stringer bounded");
}
catch(Exception e){
System.err.println("*******ComputeEngine exception:
*******");
e.getMessage();
}
}
}

Cliente

package rmi_sample;

import java.rmi.Naming;
import java.rmi.RMISecurityManager;

public class StrClient {

94
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

public static void main(String[] args) {


if (args.length < 1){
System.out.println("Necesita el hostname");
System.exit(0);
}

if (System.getSecurityManager() == null){
System.setSecurityManager(new RMISecurityManager());
}

try{
String name = "//" + args[0] + "/StringerInterface";
StringerInterface robject =
(StringerInterface)Naming.lookup(name);
String result = robject.getString();
System.out.println(result);
}
catch(Exception e){
System.err.println("******** Problemas en la
invocación ********" + e.getMessage());
}
}
}

Conclusión

RMI es un sistema que nos permite el intercambio de objetos, el cual se realiza de manera
transparente de un espacio de direcciones a otro, puesto que utiliza una técnica de
serialización. Además nos pemite el llamado de los métodos remotamente, sin tener la
necesidad de tener los métodos localmente.

Debido a que los sistemas necesitan manejo de datos en sistemas distribuidos cuando estos
residen en direcciones de distintos hosts, los métodos de invocación remota son una buena
alternativa para solucionar estas necesidades. RMI es un sistema de programación para la
distribución e intercambio de datos entre distintas aplicaciones existentes en un entorno
distribuido.

Se debe tener en cuenta que es más lento porque los objetos tienen que serializarse y luego
deserializarse, se debe chequear la seguridad, los paquetes tienen que ser ruteados a través de
switches.

Esto trae como conclusión que, lamentablemente, el diseño de un sistema distribuido, no es


solamente tomar un conjunto de objetos y ponerlos en otro proceso para balancear la carga.

Resumen rápido

En RMI intervienen dos progamas java que están ejecutándose en el mismo o distintos
ordenadores.

Uno de ellos, llamado servidor, “publica” algunos de los objetos/clases que tiene instanciadas,
es decir, los pone visibles desde la red, de forma que otros programas java puedan llamar a sus
métodos.

95
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

El otro programa, llamado cliente, localiza en el servidor el objeto publicado y llama a sus
métodos. Estos métodos se ejecutan en el programa servidor y devuelven un resultado (por el
return habitual) que recoge el cliente en su ordenador.

El cliente se queda “colgado” en la llamada hasta que el servidor termina de ejecutar el código
y devuelve un resultado.

Los objetos que comparten el servidor se conocen como objetos remotos.

a) Se crea una interface (remota) con los métodos que queremos que se publiquen, es
decir, los métodos que queremso que pueda llamar el cliente
• Esta interface debe heredar de la interface Remote.
• Todos los métodos de ésta interface deben lanzar una RemoteException.
b) Se implementa la interface remota creando una clase que herede de
UnicastREmoteObjet con un constructor que lance una RemoteException.

public class ObjetoRemoto extends java.rmi.server.UnicastRemoteObject


implements InterfaceRemota{
public ObjetoRemoto() throws java.rmi.RemoteException{super();}
public int suma(int a, int b){
return a + b;
}
}

c) Se compila de forma habitual


d) Al fichero .class se le pasa el comando rmic (está en $JAVA_HOME/bin) para generar el
objeto stub.

rmic paquete.ObjetoRemoto

e) Se inicia el servicio de registro RMI. Antes de lanzar nuestro servidor, debemos lanzar
un programa llamado rmiregistry que también viene con java (está en
$JAVA_HOME/bin). Este programa es el que realmente sabe todos los objetos que se
publican en este ordenador. La llamada Naming.rebind() en realidad coneca con
rmiregistry en este ordenador y le avisa que ObjetoRemoto debe publicarse al
exterior.

96
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Rmiregistry necesita encontrar la clase ObjetoRemoto_Stub.class, así que en el


servidor debemos fijar la propiedad java.rmi.server.codebase con el path, en formato
url, desde el que rmiregistry puede acceder a esta clase.

System.setProperty("java.rmi.server.codebase", "file:/un_path/");

f) Se lanza el servidor
g) En el cliente. Se escribe la clase cliente

InterfaceRemota objetoRemoto =
(InterfaceRemota)Naming.lookup("//IP_del_Servidor/ObjetoRemoto");
System.out.println("2 + 3 = ");
System.out.println(objetoRemoto.suma(2, 3));

h) Se compila de forma habitual. Para compilar necesitaremos el fichero


InterfaceRemota.class
i) Se lanza el cliente. Si no hemos puesto RMISecurityManager, necesitaremos los
ficheros InterfaceRemota.class y ObjetoRemoto_Stub.class para ejecutar.

Arrancar un RMISecurityManager y escribir un fichero java.policy de permisos es peligroso por


lo siguiente:

Si no instalamos el RMISecurityManager, tenemos una seguridad por defecto bastante


restrictiva. Si no sabemos, es mejor dejar la de defecto.
Si instalamos el RMISecurityManager, estamos habilitando una cosa llamada carga
dinámica de clases. La carga dinámica de clases consiste en que un cliente pueda
enviarnos una clase hecha por él y que no esté en el servidor. Esa clase puede tener
código malicioso.
Si instalamos el RMiSecurityManager, las clases tanto del servidor como las cargadas
dinámicamente de los clientes, tienen permiso para hacer aquellas cosas que se les
permite en el fichero java.policy. Es decir, el fichero java.policy dice si una clase
concreta tiene o no permisos para escribir o leer en disco duro, establecer sockets, etc.
En muchos tutoriales, para evitar problemas en el ejemplo y no extenderse en
explicaciones, ponen el fichero java.policy con todos los permisos para todo el mundo.
Cualquier clase que venga de un cliente podría borrarnos el disco duro.
Por ello se aconseja no poner RMISecurityManager, salvo que se necesite carga
dinámica de clases. En ese caso, no dejar el fichero java.policy con todos los permisos
para todo el mundo.

Ejercicio simple. La hipoteca

Este ejercicio fue el propuesto en clase de Procesos y Servicios para comprobar si se ha


comprendido la sección de RMI. Es decir, vamos a ver como se desarrolla un RMI básico
implementando la teoría vista hasta ahora.

97
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Ejercicio de la Hipoteca

Esta es la fórmula para el cálculo de la cuota mensual de una hipoteca.

capital * int eres


Cuota =
int eres − plazo
100 * (1 − (1 + )
100
Por ejemplo, para calcular la cuota de un préstamo hipotecarío de 100.000 unidades de
capital, de 20 años de plazo y un tipo de interés fijo del 4% anual en que los pagos se realizan
mensualmente, empleamos los siguientes cálculos.

1. Como los pagos son mensuales, comenzamos calculando el plazo expresados en meses y el
tipo de interés mensual.

Plazo (enMeses ) = 20 * 12 = 240 meses


4%
Interes(mensual) = = 0.33333%
12

2. La cuota que debemos ingresar mensualmente será:

100000 * 0.33333
Cuota = = 605.96
0.33333 −240
100 * (1 − (1 + )
100

Vamos a suponer que realizar estos cálculos es de una gran complejidad computacional por lo
que vamos a desarrollar un programa distribuido mediante RMI. Para ello crearemos dos
programas, un servidor y un cliente. El servidor tendrá un método remoto que sepa calcular
dicha cuota. Un cliente lo invocará pasándole los tres datos iniciales, capital, interés y plazo.

Lo primero será programar la interfaz de nuestro objeto remoto

INTERFAZREMOTA.JAVA

package claver.creditos;

import java.rmi.*;
import java.io.Serializable;

/**
* Interface Remota con métodos para llamada en remoto
*/
public interface InterfaceRemota extends Remote{

public double cuotaMensual(double capital, double interes,


double plazo) throws RemoteException;

} // Fin InterfaceRemota

Y el siguiente paso es desarrollar el objeto remoto propiamente dicho.

98
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

OBJETOREMOTO.JAVA

package claver.creditos;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class ObjetoRemoto extends UnicastRemoteObject implements


InterfaceRemota{

// Construye una instancia de ObjetoRemoto


public ObjetoRemoto() throws RemoteException{
super();
} // Fin Constructor

// Métodos

// Calcula la cuota mensual de un prestamo dado capital, interes


anual y plazo anual
public double cuotaMensual(double capital, double interes,
double plazo){
System.out.println("Calculando cuota...");

double plazoMes = plazo / 12.00;


double interesMes = interes / 12.00;
return (capital * interes) / (100.00 * (1 -
(Math.pow(interesMes / 100.00, plazoMes))));
} // Fin cuotaMensual
} // Fin ObjetoRemoto

Ahora solo queda desarrollar el Servidor, que será el que esperará que le envíen los datos para
calcular.

SERVIDOR.JAVA

package claver.creditos;

import java.rmi.*;

/**
* Servidor para el ejemplo RMI
* Exporta un método para sumar dos enteros y devuelve resultado
*/
public class Servidor{

// Crea nueva instancia de Servidor RMI


public Servidor(){
try{
/* Indico a rmiregistry donde están las clases.
Cambio el path al sitio en el que esté.
Hay que mantener la raya al final */
System.setProperty(
"java.rmi.server.codebase",
"file:/C:/ejercicios/primero/src_servidor");

// Se publica el objeto remoto


InterfaceRemota objetoRemoto = new ObjetoRemoto();

99
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Naming.rebind("//localhost/ObjetoRemoto",
objetoRemoto);
}
catch(Exception e){
e.printStackTrace();
}
} // Fin Constructor

// Main
public static void main(String[] args){
new Servidor();
} // Fin Main
} // Fin Servidor

Y el cliente, que mandará los datos para su cálculo y los recibirá una vez finalizados.

CLIENTE.JAVA

package claver.creditos;

import java.rmi.*;
import java.text.*;

/**
* Ejemplo de cliente rmi nocivo, para aprovecharse de un servidor
* sin SecurityManager
*/
public class Cliente{

// Crea nueva instancia de Cliente


public Cliente(){
try{
/* Lugar en el que está el objeto remoto
* Debe reemplazarse "localhost" por el nombre o
ip donde
* esté corriendo "rmiregistry".
* Naming.lookup() objtiene el objeto remoto
*/
InterfaceRemota objetoRemoto =
(InterfaceRemota)Naming.lookup("//localhost/ObjetoRemoto");

// Hago calculo remoto


System.out.println("Capital: 20.000 euros");
System.out.println("Interes: 6%");
System.out.println("Plazo: 5 años");
DecimalFormat df = new DecimalFormat("#.##");
System.out.println("Cuota mensual: " +
df.format(objetoRemoto.cuotaMensual(20000.00, 6.00, 5.00)) + "
euros");
}
catch(Exception e){
e.printStackTrace();
}
} // Fin constructor

// Main
public static void main(String[] args){
new Cliente();
} // Fin Main

100
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

} // Fin Cliente
Después de haber programado todas nuestras clases, deberemos compilarlo y comenzaremos
con el montaje del RMI.

Aquí debo hacer varios apuntes.


El primero es que he programado mi proyecto en el IDE Eclipse, teniendo el Workspace en
C:\Users\Inazio\Workspace. Esta ruta es importante porque el .stub y la compilación de las
clases la vamos a generar por línea de comandos en vez de aprovechar el autocompilado de
Eclipse.

La segunda cosa es que si examináis el código, cuando hago el setProperty del System, estoy
colocando los paquetes en dos carpetas.

La parte servidora en C:\ejercicios\primero\src_servidor, donde incluiré


Servidor.class, InterfazRemota.class, ObjetoRemoto.class y el .stub generado.
Y la parte cliente estará en C:\ejercicios\primero\src_cliente, y pondremos
Cliente.class, InterfazRemota.class y el .stub, que debe estar compartido en las dos carpetas.

Vale. Pues por pasos.

Lo primero será compilar todos los .java. Abrimos una terminal (Ejecutar cmd) y lo primero
es acceder a la ruta donde están nuestros archivos (sin entrar al paquete, nos quedamos en
src).

cd C:\Users\Inazio\workspace\EjercicioRMI\src

Ahora compilaremos todos los .java con el comando javac, colocando la ruta de los paquetes y
las clases a compilar.

javac claver\creditos\*.java

Ya estarán compilados, así que procederemos a generar el stub con la herramienta rmic.
Lo primero que haremos será situar el CLASSPATH de nuestra consola en la ruta src de nuestro
proyecto (es decir, en la ruta de nuestro proyecto sin contar el paquete) y posteriormente
lanzaremos la herramienta rmic con la ruta de nuestro objeto remoto. Esta ruta será como si
llamásemos a clases de java, es decir, deberemos separarla por puntos.

set CLASSPATH=C:\Users\Inazio\workspace\EjercicioRMI\src
rmic claver.creditos.ObjetoRemoto

Ya tenemos compilado y generado todo lo necesario para nuestra aplicación. Ahora


deberemos trasladar estos compilados a la ruta antes mencionada. Es decir:

En C:\ejercicios\primero\src_cliente
Cliente.class
InterfazRemota.class
ObjetoRemoto_Stub.class

En C:\ejercicios\primero\src_servidor
Servidor.class
ObjetoRemoto.class

101
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

InterfazRemota.class
ObjetoRemoto_Stub.class

De vuelta a la consola de comandos, lanzaremos el servicio rmiregistry para que nuestras


aplicaciones cliente y servidor puedan comunicarse entre sí.

Habrá que resetear el CLASSPATH modificado antes a uno vacio y ya lanzar el servicio.

set CLASSPATH=
rmiregistry

Se nos quedará la consola como esperando instrucciones. Bien, es lo que tiene que pasar.
Abrimos una segunda terminal y accedemos a la ruta src_servidor y ejecutamos nuestro
Servidor.

cd C:\ejercicios\primero\src_servidor
java claver.creditos.Servidor

Y lo mismo con nuestro Cliente

cd C:\ejercicios\primero\src_cliente
java claver.creditos.Cliente

Ahora, el cliente enviará los datos al servidor y este procederá a hacer el cálculo y reenviarlo a
Cliente, que en este caso en concreto lo mostrará por pantalla.

Ya tenemos hecho nuestro programa RMI. ¿Sencillo?


Un último apunte. Si decides copiar este ejercicio en tu ordenador, acuérdate de modificar las
rutas por las que se ajusten a tu ordenador. Por ejemplo es poco probable que tengas
C:\Users\Inazio. Cambialo por otra que se ajuste a tu caso.

102
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Programación segura

Buenas prácticas en programación

1. Informarse

Una forma de evitar fallos es estudiar y comprender los erroes que otros han cometido
a la hora de desarrollar software.

Internet es el hogar de una gran variedad de foros públicos donde se debaten con
frecuencia problemas de vulnerabilidad de software.

Leer libros y artículos sobre prácticas de codificación segura, así como análisis de los
defectos del software.

Explorar el software de código abierto, hay cantidad de ejemplos de cómo llevar a


cabo diversas acciones, sin embargo, también se pueden encontrar numerosos
ejemplos de cómo no se deben hacer las cosas.

2. Precaución en el manejo de los datos

Limpiar datos. Es el proceso de examen de los datos de entrada. Los atacantes a


menudo intentan introducir contenido que está más allá de lo que el programador
prevé para la entrada de datos del programa, por ejemplo alteración del juego de
caracteres, uso de caracteres no permitidos, desbordamiento del buffer de datos…

Realizar la comprobación de límites. Un problema muy típico es el desbordamiento de


búfer. Hemos de asegurarnos de verificar que los datos proporcionados al programa
pueden caber en el espacio que se asigna para ello. En los arrays hemos de revisar los
índices para garantizar que permanecen dentro de sus límites.

Revisar los ficheros de configuración. Es necesario validar los datos, como si se tratase
de una entrada de datos por teclado, ya que pueden ser manipulados por un atacante.

Comprobar los parámetros de línea de comandos.

No fiarse de las URLs Web. Muchos diseñadores de aplicaciones web utilizan URLs para
insertar variables y sus valores. El usuario puede alterar la URL directamente dentro de
su navegador por variables de ajuste y/o de sus valores a cualquier configuración que
elija. Si la aplicación Web no realiza una comprobación de los datos puede ser atacada
con éxito.

Cuidado con los contenidos Web. Muchas aplicaciones web insertar variables en
campos HTML ocultos. Tales campos también pueden ser modificados por el usuario
en una sesión de navegador.

Comprobar las cookies web. Los valores pueden ser modificados por el usuario final y
no se debe confiar en ellas.

103
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Comprobar las variables de entorno. Un uso común de las variables de entorno es


pasar configuración de preferencias a los programas. Los atacantes pueden
proporcionar variables de entorno no previstas por el programador

Establecer valores iniciales válidos para los datos. Es un buen hábito inicializar
correctamente las variables.

Comprender las referencias de nombre de fichero (rutas de acceso de ficheros y


directorios) y utilizarlas correctamente dentro de los programas.

Especial atención al almacenamiento de información sensible. Es de vital importancia


proteger la confidencialidad e integridad de información considerada como
confidencial, como contraseñas, números de cuenta de tarjetas de crédito, etc.

3. Reutilización de código siempre que sea posible. Se refiere a la reutilización del software
que ha sido completamente revisado y probado, y ha resistido las pruebas del tiempo y de los
usuarios.

4. Insistir en la revisión de los procesos. Siempre es aconsejable seguir una práctica de


revisión de los fallos de seguridad en el código fuente. Si un programa es confiado a varias
personas, todas deben participar en la revisión.

Es recomendable el desarrollo de una lista de cosas que buscar, esta tiene que ser
mantenida y actualizada con los nuevos fallos de programación encontrados.

Realizar una validación y verificación independiente. Algunos proyectos de


programación necesitan una revisión más formal que implica revisar el código fuente
de un programa, línea por línea, para garantizar que se ajusta a su diseño, así como a
otros criterios (por ejemplo, las condiciones de seguridad).

Identificar y utilizar las herramientas de seguridad disponibles. Hay herramientas de


software disponibles para ayudar en la revisión de fallos en el código fuente. Son útiles
para la captura de errores comunes pero no tan útiles para detectar otro error.

5. Utilizar listas de control de seguridad. Estas listas pueden ser muy útiles para asegurarse de
que se han cubierto todas las fases durante la ejecución. Por ejemplo:

Este sistema de aplicación requiere una contraseña para que los usuarios puedan
acceder.
Todos los inicios de sesión de usuario son únicos.
Esta aplicación utiliza el sistema de control de acceso basado en roles.
Las contraseñas no se transmiten a través de la red en texto plano.
El cifrado se utiliza para proteger los datos que se transfieren entre servidores y
clientes.

6. Ser amable con los mantenedores. El mantenimiento del código puede ser de vital
importancia para la seguridad del software en el transcurso de su vida útil. Seguiremos estas
prácticas de mantenimiento del código:

Utilizar normas. Con respecto la documentación en línea del código fuente, los
nombres de las variables, etc; para que hagan la vida más fácil para aquellos que

104
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

posteriormente mantendrán el código. El código modular, si está bien documentado,


es más fácil de mantener.
Retirar código obsoleto
Analizar todos los cambios en el código. Hemos de asegurarnos de probar a fondo los
cambios en el código antes de entrar en producción.

7. Las cosas que no se deben hacer:

Escribir código que utiliza nombres de ficheros relativos. La referencia a un nombre de


fichero debe ser completa.
Referirse dos veces en el mismo programa a un fichero por su nombre. Se recomienda
abrir el fichero una vez por su nombre y utiliza el identificador a partir de entonces.
Invocar programas no configuables dentro de los programas.
Asumir que los usuarios no son maliciosos.
Dar por sentado el éxito. Cada vez que se realiza una llamada al sistema (por ejemplo,
abrir un fichero, leer un fichero, recuperar una variable de entorno), comprobar el
valor de retorno por si la llamada falló.
Invocar un Shell o una línea de comandos.
Autenticarse en criterios que no sean de confianza.
Utilizar áreas de almacenamiento con permisos de escritura. Si es absolutamente
necesario, hay que suponer que la información pueda ser manipulada, alterada o
destruida por cualquier persona o proceso que así lo desee.
Guardar datos confidenciales en una base de datos sin protección de contraseña.
Hacer eco de las contraseñas o mostrarlas en la pantalla del usuario.
Emitir contraseñas vía e-mail
Distribuir mediante programación información confidencial a través de correo
electrónico.
Guardar las contraseñas sin cifrar (o cualquier otra información confidencial) en disco
en un formato fácil de leer (texto sin cifrar).
Transmitir entre los sistemas contraseñas sin encriptar (o cualquier otra información
confidencial). Se debe utilizar como antes certificados, encriptación fuete o
transmisión segura entre hosts de confianza.
Tomar decisiones de acceso basadas en variables de entorno o parámetros de línea de
comandos que se pasan en tiempo de ejecución.
Evitar, en la medida que se pueda, confiar en el software o los servicios de terceros
para operaciones críticas.

Criptografía

Proceso general de cifrado y descrifrado de mensajes.

Si a un texto legible se le aplica un algoritmo de cifrado, que en general depende de


una clave, esto arroja como resultado un texto cifrado que es el que se envía o guarda.
A este proceso se le llama cifrado o encriptación.
Si a ese texto cifrado se le aplica el mismo algoritmo, dependiente de la misma clave o
de otra clave (esto depende del algoritmo), se obtiene el texto legible original. A este
segundo proceso se le llama descrifrado o desencriptación.

105
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

3 clases de algoritmos criptográficos.

Funciones de una sola vía (o funciones Hash). Practicamente cualquier protocolo las
usa para procesar claves, encadenar una secuencia de eventos, o incluso autenticar
eventos y son esenciales en la autenticación por firmas digitales. Por ejemplo MD5 y
SHA-1

Algoritmos de clave secreta o de criptografía simétrica, como DES, Tripe DES, AES.

Algoritmos de clave pública o de criptografía asimétrica. RSA, siendo su uso


prácticamente universal como método de autenticación y firma digital y es
componente de protocolos y sistemas como IPSec (Internet Protocol Security), SSL,
PGP, etc.

Funcionamiento de la firma digital.

Una firma digital está compuesta por una serie de datos asociados a un mensaje, estos datos
nos permiten:

Asegurar la identidad del firmante (emisor del mensaje).


La integridad del mensaje

El método de firma digital más extendido es el RSA.

El procedimiento de firma de un mensaje por parte del emisor.

106
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

El emisor genera un hash (resumen) del mensaje mediante una función acordada, a
este hash le llamamos H1.
Este hash es el cifrado con su clave privada. El resultado es lo que se conoce como
firma digital (FD) que se envía adjunta al mensaje.
El emisor envía el mensaje y su FD al receptor, es decir, el mensaje firmado.

El procedimiento de firma de un mensaje por parte del receptor.

Separa el mensaje de la firma


Genera el resumén del mensaje recibido usando la misma función que el emisor, se
genera HZ.
Descifra la firma, FD, mediante la clave pública del emisor obteniendo el hash original,
H1.
Si los dos resúmenes coinciden se puede afirmar que el mensaje ha sido enviado por el
propietario de la clave pública utilizada y que no fue modificado.

107
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Funcionamiento conjunto de la firma digital:

CRIPTOGRAFÍA SIMÉTRICA O DE CLAVE SECRETA


Puntos fuertes Puntos débiles
Cifran más rápido que los algoritmos de clave Requieren un sistema de distribución de
pública. claves muy seguro (si se conoce la clave se
Sirven habitualmente como base para los pueden conocer todos los mensajes cifrados
sistemas criptográficos basados en hardware. con ella).
En el momento en que la clave cae en manos
no autorizadas, todo el sistema deja de
funcionar. Esto obliga a llevar una
administración compleja.
Si se asume que es necesaria una clave por
cada pareja de usuarios de una red, el
número total de claves crece rápidamente
con el número de usuarios.
CRIPTOGRAFÍA ASIMÉTRICA O DE CLAVE PÚBLICA
Puntos fuertes Puntos débiles
Permiten conseguir autenticación y no Son algoritmos más lentos que los de clave
repudio para muchos protocoles secreta, con lo que no suelen utilizarse para
criptográficos. cifrar gran cantidad de datos.
Suelen emplearse en colaboración con Sus implementaciones son comúnmente
cualquiera de los otros métodos hechas en sistemas software.
criptográficos. Para una gran red de usuario y/o máquinas se
Permiten tener una administración sencilla requiere un sistema de certificación de la
de claves al no necesitar que haya autenticidad de las claves públicas
intercambio de claves seguro.

Certificados digitales

108
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Un certificado digital es un documento que certifica que una entidad determinada, como
puede ser un usuario, una máquina, un dispositivo de red o un proceso, tiene una clave pública
determinada.

Para certificar estos documentos se acude a las Autoridades de Certificación (AC), que son
entidades que se encargan de emitir y gestionar tales certificados y que tienen una propiedad
muy importante: que se puede confiar en ellas. La forma en la que la AC hace válido el
certificado es firmándolo digitalmente.

Al aplicar el algoritmo de firma digital al documento se obtiene un texto, una secuencia de


datos que permiten asegurar que el titular de ese certificado ha “firmado electrónicamente” el
texto y que este no ha sido modificado.

Formato estándar X.509

Versión
Número de serie. Identificador numérico único
Algoritmo de firma y parámetros, que identifican el algoritmo asimétrico y la función e
una sola vía que se usa.
Emisor del certificado: El nombre X.500 de la AC.
Fechas de inicio y final de validez que determinan el periodo de validez del certificado.
Nombre del propietario de la clave pública.
Identificador del algoritmo que se está utilizando, la clave pública del usuario y otros
parámetros si son necesarios.
La firma digital de la AC, es decir, el resultado de cifrar mediante el algoritmo
asimétrico y la clave privada de la AC, el hash obtenido del docuemento X.509.

109
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Principales aplicaciones

Autentificar la identidad del usuario, de forma electrónica; ante terceros


Trámites electrónicos ante la Agencia Tributaria, la Seguridad Social, las Cámaras y
otros organismos públicos.
Trabajar con facturas electrónicas.
Firmar digitalmente e-mail y todo tipo de documentos.
Cifrar datos para que solo el destinatario del documento pueda acceder a su
contenido.

Como obtenerlos

Se pueden solicitar a través de la aplicación web de la Autoridad de Certificación (AC). Una


persona física (no persona jurídica) para solicitar un certificado a la Fábrica Nacional de
Moneda y Timbre (FNMT), accede a la web: www.ceres.fnmt.es; y sigu una serie de
pasos:

Solicitud del certificado (a través de la Web)


Acreditación de la identidad mediante personación física en una oficina de registro
Descarga del certificado desde Internet.

Algunas AC españolas que emiten certificados electrónicos de empresa son:

Fábrica Nacional de Moneda y Timbre (FNMT)


Agència Catalana de Certificació (CATCert)
Agencia Notarial de Certificación (ANCERT)
ANF Autoridad de Certificación (ANP AC)
Autoritat de Certificació de la Comunitat Valenciana (ACCV)
Etc…

Control de acceso

Componentes

Identificación. Proceso mediante el cual el sujeto suministra información diciendo


quien es.

110
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Autenticación. Es cualquier proceso por el cual se verifica que alguien es quien dice
ser. Esto implica generalmente un nombre de usuario y una contraseña, pero puede
incluir cualquier otro método para demostrar la identidad, como una tarjeta
inteligente, exploración de la retina, reconocimento de voz o las huellas dactilares.
Autorización. Es el proceso de determinar si el sujeto, una vez autenticado, tiene
acceso al recurso. La autorización es equivalente a la comprobación de la lista de
invitados a una fiesta.

Medidas de identificación y autenticación

Algo que se sabe, algo que se conoce, típicamente las contraseñas, es la más extendida
Algo que se tieen, los Access tokens (sistemas de tarjetas)
Algo que se es, medidas que utilizan la biometría (identificación por medio de la vez, la
retina, la huella dactilar, geometría de la mano, etc).

Seguridad en entorno Java

Antes de que la JVM comience el proceso de interpretación y ejecución de los bytecodes


(código objeto) debe realizar una serie de tareas para preparar el entorno en el que el
programa se ejecutará. Este es el punto en el que se implementa la seguridad interna de Java.

Hay tres componentes en el proceso:

El cargador de clases
El verificador de ficheros de clases
El gestor de seguridad

El cargador de clases

Es el responsable de encontrar y cargar los bytecodes que definen las clases. Cada programa
Java tiene como mínimo tres cargadores:

El cargador de clases bootstrap que carga las clases del sistema (normalmente desde el
fichero JAR rt.jar)
El cargador de clases de extensión que carga una extensión estándar desde el
directorio jre/lib/ext
El cargador de clases de la aplicación que localiza las clases y los ficheros JAR/ZIP de la
ruta de acceso a las clases (según está establecido por la variable de entorno
CLASSPATH o por la opción –classpath de la línea de comandos)

El verificador de ficheros de clases

Se encarga de validar los bytecodes. Algunas de las comprobaciones que lleva a cabo son:

Que las variables estén inicializadas antes de ser utilizadas


Que las llamadas a un método coinciden con los tipos de referencias a objetos
Que no se han infrigido las reglas para el acceso a los métodos y clases privados, etc.

El gestor de seguridad

111
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Es una clase que controla si está permitida una determinada operación.

Alguna de las operaciones que comprueban son las siguientes:

Si el hilo actual puede cargar un subproceso


Si puede acceder a un paquete específico
Si puede acceder o modificar las propiedades del sistema
Si puede leer desde o escribir en un fichero específico
SI puede eliminar un fichero específico
Si puede aceptar una conexión socket desde un host o número de puerto específico,
etc.

Por defecto no se instala de forma automática ningún gestor de seguridad cuando se ejecuta
una aplicación Java. En el siguiente ejemplo veremos la salida que produce el programa
ejecutándolo sin gestor de seguridad y con gestor de seguridad. El programa muestra los
valores de ciertas propiedades de sistema (usamos el método
System.getProperty(propiedad) para mostrar los valores), la siguiente tabla
describe alguna de las más importantes:

Archivo EJEMPLO1.JAVA

public class Ejemplo1 {

public static void main(String[] args) {

//propiedades de sistema en un array


String t[] = { "java.class.path", "java.home",
"java.vendor",
"java.version", "os.name",
"os.version","user.dir",
"user.home", "user.name"};

System.setProperty ("java.security.policy",
System.getProperty("user.dir") +
"\\src\\_01SinConGestor\\Politica1.policy");
System.setSecurityManager(new SecurityManager());
for (int i = 0; i < t.length; i++) {

System.out.print("Propiedad:" + t[i]);
try {
String s = System.getProperty(t[i]);
//valor de la propiedad
System.out.println("\t==> " + s);
} catch (Exception e) {
System.err.println("\n\tEXcepción " + e.toString()); }

}//Fin de for
}//Fin de main
}//Fin de Ejemplo1

Archivo POLITICA1.POLICY (para política de permisos)

grant {
permission java.util.PropertyPermission "java.class.path",
"read";

112
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

permission java.util.PropertyPermission "java.home", "read";


permission java.util.PropertyPermission "user.home", "read";
permission java.util.PropertyPermission "user.name", "read";
permission java.util.PropertyPermission "user.dir", "read";
permission java.util.PropertyPermission "on.version", "read";
};

APIs JAVA para seguridad

JCA (Java Cryptography Architecture, Arquitectura Criptográfica de Java). Es un marco de


trabaj para acceder y desarrollar funciones criptográficas en la plataforma Java. Proporciona la
infraestructura para la ejecución de los principales servicios de cifrado, incluyendo:

Firmas digitales
Resúmenes de mensajes (hash)
Certificados y validación de certificados
Encriptación (cifrado de bloques, cifrado simétrico y asimétrico)
Generación y gestión de claves y generación segura de números aleatorios

JSSE (Java Secure Extension, Extensión de Sockets Seguros Java). Es un conjunto de paquetes
Java provistos para la comunicación segura en Internet. Implementa una versión Java de los
protocolos SSL y TLS. Incluye funcionalidades como:

Cifrado de datos
Autenticación del servidor
Integridad de mensajes
Autenticación del cliente

JAAS (Java Authentication and Authorization Service, Servicio de Autenticación y


Autorización de Java). Es un interfaz que permite a las aplicaciones Java acceder a servicios de
control de autenticación y acceso. Puede usarse con dos fines:

La autenticación de usuarios para conocer quién está ejecutando código Java.

113
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

La autorización de usuarios para garantizar que quién lo ejecuta tiene los permisos
necesarios para hacerlo.

Ficheros de políticas en Java


Localización

El fichero de políticas predeterminado java.policy, está localizado en los siguientes directorios.

Sistemas operativos Windows: java.home\lib\security\java.policy


Sistemas UNIX: java.home/lib/security/java.policy

Donde java.home representa el valor de la propiedad java.home, que especifica el


directorio en el que se instaló el JRE.

El valor por defecto es tener un solo fichero de políticas en todo el sistema y un fchero de
políticas en el directorio home (dado por la variable user.home) del usuario (este tiene un
punto delante).

En el fichero java.security localizado en la carpeta java.home\lib\security\ se


encuentran las localizaciones de estos ficheros:

policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${user.home}/.java.policy

Entrada grant

Un fichero de políticas especifica qué permisos están disponibles para el código de varias
fuentes.

Se utiliza para conceder permisos del sistema.

Contiene una secuencia de entradas grant, cada una de ellas tiene una o más entradas, el
formato básico es el siguiente:

grant codeBase "URL"{


permission Nombre_clase "Nombre_destino", "Acción";
permission Nombre_clase "Nombre_destino", "Acción";
}

A la derecha de codeBase se indica la ubicación del código base sobre el que se van a definir
los permisos. Su valor es una URL y siempre se debe utiliza la barra diagonal (/) como
separador de directorio incluso para las URL de tipo file en Windows. Por ejemplo: grant
codeBase “file:/C:/somepath/api/”.
Si se omite codeBase entonces los permisos se aplican a todas las fuentes.

Nombre_clase contiene el nombre de la clase de permisos, por ejemplo:

java.io.FilePermission (representa acceso a fichero o directorio)


java.net.SocketPermission (acceso a la red vía socket)

114
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

java.util.PropertyPermission (permiso sobre propiedades del sistema),


etc.

El parámetro Nombre_destino especifica el destino del permiso y depende de la clase de


permiso.
Por ejemplo si la clase de permiso es java.io.FilePermission en Nombre_destino se puede
poner un fichero o un directorio; si la clase es java.net.SocketPermission se pondría un
servidor y un número de puerto; si es java.util.PropertyPermission se pondría una propiedad
del sistema.

En el parámetro Acción se indica una lista de acciones separadas por comas, por ejemplo read,
write, delete o execute para una clase de permiso java.io.FilePermission; accept,
listen, connect, resolve para una clase de permiso java.netSocketPermission; read,
write para una clase de permiso java.util.PropertyPermission.

Desde la URL:

http://docs.oracle.com/javase/8/docs/technotes/guides/security/p
ermissions.html

Se puede consultar los permisos, sus destinos y acciones.

Ejemplo de ficheros de políticas

Creamos un fichero en una carpeta determinada (en Windows C:\Ficheros) e insertamos una
línea, después lee el contenido del fichero y lo muestra en pantalla. El comportamiento del
programa será diferente si usamos o no el gestor de seguridad y ficheros de políticas.

Se añade el gestor de seguridad en el método main(), la línea


System.setSecurityManager(new SecurityManager()); antes de la cláusula
try, la salida muestra un error de acceso denegado al escribir datos en el fichero, tampoco se
podrá realizar la lectura del mismo.

Archivo EJEMPLO2.JAVA

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Ejemplo2 {

public static void main(String[] args) {

//El directorio C:/Ficheros debe estar creado previamente.


//System.setProperty ("java.security.policy",
System.getProperty("user.dir") +
"\\src\\_02FicherosPoliticas\\Politica2.policy");
//System.setSecurityManager(new SecurityManager());
try {
//escritura en fichero

115
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

BufferedWriter fichero = new BufferedWriter (new


FileWriter("C://Ficheros//Fichero.txt"));
fichero.write("Escritura de una linea en fichero.");
fichero.newLine(); // escribe un salto de línea
fichero.close();
System.out.println("Final proceso de escritura...");

//lectura del fichero


BufferedReader fichero2 = new BufferedReader (new
FileReader("C://Ficheros//Fichero.txt"));
String linea = fichero2.readLine();
System.out.println("Contenido del fichero: ");
System.out.println("\t" + linea);
fichero2.close();
System.out.println("Final proceso de lectura...");
} catch (FileNotFoundException fn) {
System.out.println("No se encuentra el fichero");
} catch (IOException io) {
System.out.println("Error de E/S ");
} catch (Exception e) {
System.out.println("ERROR => " + e.toString());
}//Fin de try

}// Fin de main


}// Fin de Ejemplo2

Ahora se crea el siguiente fichero de políticas de nombre Politica2.policy con el siguiente


contenido:

Archivo POLITICA2.POLICY

grant codeBase "file:${user.dir}/bin/"{


permission java.io.FilePermission "C:\\Ficheros\\*", "read,
write";
};

que permite a los programas localizados en la carpeta indicada (incluido permiso para crear)
ficheros en la ruta C\Ficheros.

Formato grant

El parámetro Nombre_destino para la clase de permiso java.io.FilePermission puede terminar


en una serie de caracteres:

“/” (donde “/” es el carácter separador de ficheros) indica un directorio y todos los
ficheros contenidos en ese directorio.
“/-“ indica un directorio y (recursivamente) todos los ficheros y subdirectorios
contenidos en ese directorio.
“<<ALL FILES>>” indica cualquier fichero
También es posible usar propiedades de sistema como user.dir o user.home para dar
privilegios sobre el directorio en el que se está ejecutando el programa o sobre el
directorio por defecto del usuario. Se debe utilizar la propiedad encerrada entre llaves
y con el caracer $ delante. ${propiedad}.

116
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

El parámetro Nombre_destino para la clase de permiso java.net.SocketPermission tiene el


siguiente formato:

Host = (nombre del host | Direccion IP) [:número de puerto] donde


número de puerto = N | -N | N-[M]

Las acciones son accept, listen, connect y resolve.


El host se especifica como un nombre de host o dirección IP.
Especificación de puerto:
“N”, puerto N
“N-“, todos los puertos desde N en adelante
“-N”, todos los puertos desde N hacia atrás
“M-N”, rango de puertos

Tercer ejemplo de ficheros de políticas de Sockets

Archivo EJEMPLO3SERVIDOR.JAVA

import java.net.ServerSocket;
import java.net.Socket;

public class Ejemplo3Servidor {

public static void main(String[] arg) {

int numeroPuerto = 6000;// Puerto


ServerSocket servidor = null;
System.setProperty ("java.security.policy",
System.getProperty("user.dir") +
"\\src\\_03FicherosPoliticasSockets\\Politica3.policy");
System.setSecurityManager(new SecurityManager());

try {
servidor = new ServerSocket(numeroPuerto);
System.out.println("Esperando al cliente.....");
Socket clienteConectado = servidor.accept();
System.out.println("Cliente conectado.");
clienteConectado.close();
System.out.println("Cliente desconectado.");
servidor.close();
} catch (Exception e) { System.err.println("ERROR=> " +
e.toString()); }

}// Fin de main


}//Fin de Ejemplo3Servidor

Archivo EJEMPLO3.JAVA

import java.net.Socket;

public class Ejemplo3 {

public static void main(String[] args) {

String Host = "localhost";


int Puerto = 6000;

117
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

System.setProperty ("java.security.policy",
System.getProperty("user.dir") +
"\\src\\_03FicherosPoliticasSockets\\Politica4.policy");
System.setSecurityManager(new SecurityManager());

try {
Socket Cliente = new Socket(Host, Puerto);
System.out.println("CLIENTE INICIADO.");
Cliente.close();
System.out.println("CLIENTE FINALIZADO.");
} catch (Exception e) { System.err.println("ERROR=> " +
e.toString()); }

}// Fin de main


}//Fin de Ejemplo3

Archivo POLITICA3.POLICY

/*Politica para el SERVIDOR*/


grant codeBase "file:${user.dir}/bin/" {
permission java.net.SocketPermission "localhost", "listen, accept";
};

Archivo POLITICA4.POLICY

/*Politica para el CLIENTE*/


grant codeBase "file:${user.dir}/bin/" {
permission java.net.SocketPermission "localhost:6000", "connect";
};

El programa servidor escucha y acepta peticiones de clientes en el puerto 6000, cuando el


cliente se conecta se visualiza un mensaje y al cerrarse también.

El programa cliente se conecta al servidor en el puerto 6000, visualiza unos mensajes y luego
se desconecta.

El fichero de políticas para el servidor, Politica3.policy, representa un permiso que permite a


los programas localizados en la carpeta ${user.dir}/bin/ escuchar y aceptar conexiones en
localhost.

El fichero de políticas para el cliente, Politica4.policy, representa un permiso que permite a los
programas localizados en la carpeta ${user.dir}/bin/ conectarse al puerto 6000 en localhost.

Herramienta PolicyTool

Se recomienda usar la herramienta para editar cualquier fichero de políticas y verificar la


sintaxis de su contenido.
Para lanzarla, desde la terminal escribiremos el comando policytool.

118
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Criptografía con Java


Proveedores de servicios criptográficos

El API JCA (Java Cryptography Architecture, incluye la extensión criptográfica de Java JCE –
Java Cryptography Extension) incluída dentro del paquete JDK incluye dos componentes de
software:

El marco que define y apoya los servicios criptográficos para que los proveedores
faciliten implementaciones. Este marco incluye paquetes como
java.security
javax.crypto
javax.crypto.spec
javax.crypto.interfaces
Los proveedores reales, tales como Sun, SunRsaSign, SunJCE, que contienen las
implementaciones criptográficas reales. El proveedore es el encargado de
proporcionar la implementación de uno o varios algoritmos al programador. Los
proveedores de seguridad se definen en el fichero java.security localizo en la carpeta
java.home\lib\security.

Forman una lista de entradas con un número que indican el orden de búsqueda
cuando en los programas no se especifica un proveedor.
security.provider.1=sun.security.provider.Sun
security.provider.2=sun.security.rsa.SunRsaSign
security.provider.3=com.sun.net.ssl.internal.ssl.Provider
security.provider.4=com.sun.crypyo.provider.SunJCE
security.provider.5=sun.security.jgss.SunProvider

JCA define el concepto de proveedor mediante la clase Provider del paquete java.security. Se
trata de una clase abstracta que debe ser redefinida por clases proveedor específicas.

Tiene métodos para acceder a informaciones sobre las implementaciones de los algoritmos
para la generación, conversión y gestión de claves y la generación de firmas y resúmenes,
como el nombre del proveedor, el número de versión, etc.

119
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Resúmenes de mensajes

Un message digest o resúmen de mensajes (también se le conoce como función hash) es una
marca digital de un bloque de datos.

La clase MessageDigest permite a las aplicaciones implementar algoritmos de resumen de


mensajes, como MD5, SHA-1 o SHA-256. Dispone de un constructor protegido, por lo que se
accede a él mediante el método getInstance(String algoritmo).

Algunos métodos de la clase MessageDigest son:

Ejemplo

Archivo EJEMPLO4.JAVA

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;

public class Ejemplo4 {

public static void main(String[] args) {

MessageDigest md;
try {
md = MessageDigest.getInstance("SHA");
String texto = "Esto es un texto plano.";

byte dataBytes[] = texto.getBytes();//TEXTO A BYTES


md.update(dataBytes) ;//SE INTRQDUCE TEXTO EN BYTES A
RESUMIR
byte resumen[] = md.digest();//SE CALCULA EL RESUMEN

120
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

//PARA CREAR UN RESUMEN CIFRADO CON CLAVE


//String clave="clave de cifrado";
//byte resumen[] = md.digest(clave.getBytes()); // SE
CALCULA EL RESUMEN CIFRADO

System.out.println("Mensaje original: " + texto);


System.out.println("Número de bytes: " +
md.getDigestLength());
System.out.println("Algoritmo: " +
md.getAlgorithm());
System.out.println("Mensaje resumen: " + new
String(resumen));
System.out.println("Mensaje en Hexadecimal: " +
Hexadecimal(resumen));
Provider proveedor = md.getProvider();
System.out.println("Proveedor: " +
proveedor.toString());
} catch (NoSuchAlgorithmException e) { e.printStackTrace();
}

}//Fin de main

// CONVIERTE UN ARRAY DE BYTES A HEXADECIMAL


static String Hexadecimal(byte[] resumen) {

String hex = "";


for (int i = 0; i < resumen.length; i++) {

String h = Integer.toHexString(resumen[i] & 0xFF);


if (h.length() == 1)
hex += "0";
hex += h;
}//Fin de for

return hex.toUpperCase();

}// Fin de Hexadecimal

}//Fin de Ejemplo4

Genera el resúmen de un texto plano. Con el método


MessageDigest.getInstance(“SHA”) se obtiene una instancia del algoritmo SHA. El
texto plano lo pasamos a un array de bytes y el array se pasa como argumento al método
upate(), finalmente con el método digest() se obtiene el resumen del mensaje. Después
se muestra en pantalla el número de bytes generados en el mensaje, el algoritmo utilizado, el
resumen generado y convertido a Hexadecimal y por último información del proveedor.

Se puede crear un resúmen cifrado con clave usando el segundo método


digest(bytes[]), donde se proporciona la clave en un array de bytes.

String clave="clave de cifrado";


byte dataBytes[] = texto.getBytes(); //TEXTO A BYTES
md.update(dataBytes) ; // SE INTRODUCE TEXTO EN BYTES A RESUMIR
byte resumen[] = md.digest(clave.getBytes()); // SE CALCULA EL
RESUMEN CIFRADO

121
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Segundo ejemplo

Archivo EJEMPLO5.JAVA

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Ejemplo5 {

public static void main(String args[]) {

try {
FileOutputStream fileout = new
FileOutputStream("DATOS.DAT");
ObjectOutputStream dataOS = new
ObjectOutputStream(fileout);
MessageDigest md = MessageDigest.getInstance("SHA");
String datos = "En un lugar de la Mancha, "
+ "de cuyo nombre no quiero acordarme, no
ha mucho tiempo "
+ "que vivía un hidalgo de los de lanza
en astillero, "
+ "adarga antigua, rocín flaco y galgo
corredor.";
byte dataBytes[] = datos.getBytes();
md.update(dataBytes) ;// TEXTQ A RESUMIR
byte resumen[] = md.digest(); // SE CALCULA EL
RESUMEN
dataOS.writeObject(datos); //se escriben los datos
dataOS.writeObject(resumen);//Se escribe el resumen
dataOS.close();
fileout.close();
} catch (IOException e) { e.printStackTrace();
} catch (NoSuchAlgorithmException e) { e.printStackTrace();
}

}//Fin de main
}//Fin de Ejemplo5

Guardaremos un mensaje en un fichero.


También guardaremos en el fichero el resumen del mensaje, para asegurarnos de que a la hora
de leer el mensaje el fichero no esté dañado o no haya sido manipuado y los datos sean los
correctos.

Tercer ejemplo

Archivo EJEMPLO6.JAVA

import java.io.FileInputStream;
import java.io.ObjectInputStream;

import java.security.MessageDigest;

public class Ejemplo6 {

122
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

public static void main(String args[]) {

try {
FileInputStream fileout = new
FileInputStream("DATOS.DAT");
ObjectInputStream dataOS = new
ObjectInputStream(fileout);
Object o = dataOS.readObject();

// Primera lectura, se obtiene el String


String datos = (String) o;
System.out.println("Datos: " + datos);

// Segunda lectura, se obtiene el resumen


o = dataOS.readObject();
byte resumenOriginal[] = (byte[]) o;

MessageDigest md = MessageDigest.getInstance("SHA");
//Se calcula el resumen del String leído del fichero
md.update(datos.getBytes());// TEXTO A RESUMIR
byte resumenActual[] = md.digest(); // SE CALCULA EL
RESUMEN

//Se comprueban lo dos resúmenes


if (MessageDigest.isEqual(resumenActual,
resumenOriginal))
System. out.println ("DATOS VÁLIDOS") ;
else
System.out.println("DATOS NO VÁLIDOS") ;
dataOS.close();
fileout.close();
}catch (Exception e) { e.printStackTrace(); }

}//Fin de main
}//Fin de Ejemplo6

Al recuperar los datos del fichero primero necesitamos leer el String y luego el resumen, a
continuación hemos de calcular de nuevo el resumen con el String leído y comparar este
resúmen con el leído del fichero.

Generando y verificando firmas digitales

El resumen de un mensaje no nos da un alto nivel de seguridad.

Se puede decir que el fichero no es correcto si el texto que se lee no produce la misma salida
que el resúmen guardado.

Pero alguien puede cambiar el texto y el resumen, y no podemos estar seguros de que el texto
sea el que debería ser.

123
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Clase KeyPairGenerator

En algunos casos, el par de claves (clave pública y clave privada) están disponibles en ficheros.
En ese caso, el programa puede importar y utilizar la clave privada para firmar.

En otros casos, el programa necesita generar el par de claves.

La clase KeyParGenerator nos permite generar el par de claves. Dispone de un constructor


protegido, por lo que se accede a él mediante el método getInstance(String
algoritmo).

Clase KeyPair

Es una clase soporte para generar las claves pública y privada.


Dispone de dos métodos:

PrivateKey y PublicKey son interfaces que agrupan todas las interfaces de clave privada y
pública respectivamente.

Clase Signature

Se usa para firmar los datos.

124
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Un objeto de esta clase se puede utilizar para generar y verificar firmas digitales.

Dispone de un constructor protegido y se acceder a él mediante el método


getInstance(String algoritmo).

Algunos de sus métodos:

Al especifiar el nombre del algoritmo de firma, también se debe incluir el nombre del
algoritmo de resumen de mensajes utilizado por el algoritmo de firma. SHA1withDSA es una
forma de especificar el algoritmo de firma DSA, usando el algoritmo de resumen SHA-1.
MD5withRSA significa algoritmo de resumen MD5 con algortmo de firma RSA.

Existen tres fases en el uso de un objeto Signature ya sea para firmar o verificar los datos:
inicialización (ya sea con clave pública initVerify() o clave privada initSign()), actualización
(update()) y firma (sign()) o verificación (verify()).

Ejemplo

Archivo EJEMPLO7.JAVA

import java.security.*;

public class Ejemplo7 {

public static void main(String[] args) {

try {
KeyPairGenerator keyGen =
KeyPairGenerator.getInstance("DSA");
//SE INICIALIZA EL GENERADOR DE CLAVES
SecureRandom numero =
SecureRandom.getInstance("SHA1PRNG");
keyGen.initialize (1024, numero);

//SE CREA EL PAR DE CLAVES PRIVADA Y PÚBLICA


KeyPair par = keyGen.generateKeyPair();
PrivateKey clavepriv = par.getPrivate();
PublicKey clavepub = par.getPublic();

125
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

//FIRMA CON CLAVE PRIVADA EL MENSAJE


//AL OBJETO Signature SE LE SUMINISTRAN LOS DATOS A
FIRMAR
Signature dsa = Signature.getInstance("SHAlwithDSA");
dsa.initSign (clavepriv);
String mensaje = "Este mensaje va a ser firmado";
dsa.update(mensaje.getBytes());

byte [] firma= dsa.sign(); //MENSAJE FIRMADO

//EL RECEPTOR DEL MENSAJE


//VERIFICA CON LA CLAVE PÚBLICA EL MENSAJE FIRMADO
//AL OBJETO signature SE LE SUMINIST. LOS DATOS A
VERIFICAR
Signature verificadsa =
Signature.getInstance("SHAlwithDSA");
verificadsa.initVerify(clavepub);
verificadsa.update(mensaje.getBytes());
boolean check = verificadsa.verify(firma);
if(check)
System.out.println("FIRMA VERIFICADA CON CLAVE
PÚBLICA") ;
else
System.out.println("FIRMA NO VERIFICADA");

} catch (NoSuchAlgorithmException e1) {


e1.printStackTrace();
} catch (InvalidKeyException e) { e.printStackTrace();
} catch (SignatureException e) { e.printStackTrace(); }

}//Fin de main
}//Fin de Ejemplo7

Almacenar las claves pública y privada en ficheros

Para almacenar la clave privada en disco es necesario codificarla en formato PKCS8 usando la
clase PKCS8EncodedKeySpec.

PKCS8EncodedKeySpec pk8Spec =
new PKCS8EncodedKeySpec(clavepriv.getEncoded());
//Escribir a fichero binario la clave privada
FileOutputStream outpriv =
new FileOutputStream("Clave.privada");
outpriv.write(pk8Spec.getEncoded());
outpriv.close();

Para almacenar la clave pública en disco es necesario codificarla en formato X.509 usando la
clase X509EncodedKeySpec.

126
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

X509EncodedKeySpec pkX509 =
new X509EncodedKeySpec(clavepub.getEncoded());
//Escribir a fichero binario la clave pública
FileOutputStream outpub =
new FileOutputStream("Clave.publica");
outpub.write(pkX509.getEncoded());
outpub.close();

Recuperar las claves pública y privada de ficheros

Clase KeyFactory. Para recuperar las claves de los ficheros que proporciona métodos para
convertir claves de formato criptográfico (PKCS8, X.509) a especificaciones de claves y
viceversa. Su constructor y alguno de sus métodos:

Firmar los datos de un fichero con la clave privada

Archivo EJEMPLO8.JAVA

import java.io.*;
import java.security.*;
import java.security.spec.*;

public class Ejemplo8 {

public static void main(String[] args) {

try {
// LECTURA DEL FICHERO DE CLAVE PRIVADA
FileInputStream inpriv = new
FileInputStream("Clave.privada");
byte[] bufferPriv = new byte[inpriv.available()];
inpriv.read(bufferPriv);// lectura de bytes
inpriv.close();

//RECUPERA CLAVE PRIVADA DESDE DATOS CODIFICADOS EN


FORMATO PKCS8

127
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

PKCS8EncodedKeySpec clavePrivadaSpec = new


PKCS8EncodedKeySpec(bufferPriv);
KeyFactory keyDSA = KeyFactory.getInstance("DSA");
PrivateKey clavePrivada =
keyDSA.generatePrivate(clavePrivadaSpec);

//INICIALIZA FIRMA CON CLAVE PRIVADA


Signature dsa = Signature.getInstance("SHA1withDSA");
dsa.initSign (clavePrivada);

//LECTURA DEL FICHERO A FIRMAR


//Se suministra al objeto Signature los datos a
firmar
FileInputStream fichero = new
FileInputStream("FICHERO.DAT");
BufferedInputStream bis = new
BufferedInputStream(fichero);
byte[] buffer = new byte[bis.available()];
int len;
while ((len = bis.read(buffer)) >= 0)
dsa.update(buffer, 0, len);
bis.close();

//GENERA LA FIRMA DE LOS DATOS DEL FICHERO


byte[] firma = dsa.sign();

// GUARDA LA FIRMA EN OTRO FICHERO


FileOutputStream fos = new
FileOutputStream("FICHERO.FIRMA");
fos.write(firma);
fos.close();

} catch (Exception e1) { e1.printStackTrace(); }

}//Fin de main
}//Fin de Ejemplo8

Genera la firma del fichero DATOS.DAT a partir de la clave privada almacenada en el fichero
Clave.privada. La firma se almacenará en el fichero DATOS.FIRMA.

Verificar la firma de un fichero con la clave pública

Archivo EJEMPLO9.JAVA

import java.io.*;
import java.security.*;
import java.security.spec.*;

public class Ejemplo9 {

public static void main(String[] args) {

try {
//LECTURA DE LA CLAVE PUBLICA DEL FICHERO
FileInputStream inpub = new
FileInputStream("Clave.publica");
byte[] bufferPub = new byte[inpub.available()];
inpub.read(bufferPub);// lectura de bytes
inpub.close();

128
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

//RECUPERA CLAVE PUBLICA DESDE DATOS CODIFICADOS EN


FORMATO X509
KeyFactory keyDSA = KeyFactory.getInstance("DSA");
X509EncodedKeySpec clavePublicaSpec = new
X509EncodedKeySpec(bufferPub);
PublicKey clavePublica =
keyDSA.generatePublic(clavePublicaSpec);

//LECTURA DEL FICHERO QUE CONTIENE LA FIRMA


FileInputStream firmafic = new
FileInputStream("FICHERO.FIRMA");
byte[] firma = new byte[firmafic.available()];
firmafic.read(firma); firmafic.close();

//INICIALIZA EL OBJETO Signature CON CLAVE PÚBLICA


PARA VERIFICAR
Signature dsa = Signature.getInstance("SHAlwithDSA");
dsa.initVerify (clavePublica);

//LECTURA DEL FICHERO QUE CONTIENE LOS DATOS A


VERIFICAR
//Se suministra al objeto Signature los datos a
verificar
FileInputStream fichero = new
FileInputStream("FICHERO.DAT");
BufferedInputStream bis = new
BufferedInputStream(fichero);
byte[] buffer = new byte[bis.available()];
int len;
while ((len = bis.read(buffer)) >= 0)
dsa.update(buffer, 0, len);
bis.close();

//VERIFICAR LA FIRMA DE LOS DATOS LEIDOS


boolean verifica = dsa.verify(firma);
//COMPROBAR LA VERIFICACIÓN
if (verifica)
System.out.println("LOS DATOS SE CORRESPONDEN
CON SU FIRMA.");
else
System.out.println("LOS DATOS NO SE
CORRESPONDEN CON SU FIRMA");
} catch (Exception el) { el.printStackTrace(); }

}//Fin de main
}//Fin de Ejemplo9

Necesitamos la clave púlica almacenada en el fichero Clave.publica, la firma del fichero


almacenada en DATOS.FIRMA y el fichero de datos DATOS.DAT. En primer lugar obtendremos
la clave pública del fichero Clave.publica, a continuación obtenemos la firma digital
almacenada en el fichero DATOS.FIRMA. A continuación se leen los datos del fichero de datos
DATOS.DAT y se suministran al objeto Signature. Por último se verifica la firma con la clave
pública.

Herramientas para firmar ficheros

129
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Java dispone de la herramienta de línea de comandos keytool para generar y manipular


certificados.

Para firmar un documento seguiremos los siguientes pasos:

1. Crear un fichero JAR que contiene el documento a firmar.

jar cvf Contrato.jar Contrato.pdf

2. Generar las claves púlica y privada (si no existen), con keytool-genkey.

keytool –genkey –alias FirmaContrato –keystore AlmacenClaves

Creamos un almacén de claves (keystore) con el nombre AlmacenClaves. FirmaContrato es el


nombre con el que haremos referencia al par de claves creado.

Nos pedirá contraseña para el almacén de claves y para la clave privada del par de claves
generado.

El certificado generado tiene una validez de 90 días a no ser que se especifique la opción -
validity en keytool.
Los certificados autofirmados son útiles para desarrollar y probar una aplicación.
La aplicación está firmada con un certificado que no es de confianza, por tanto, al ejecutarla nos
preguntará antes si queremos ejecutarla.
Se recomienda no importar en un almacén de claves un certificado en el que no se confíe
plenamente.

3. Firmar el fichero JAR, usando jarsigner y la clave privada.

jarsigner –keystore AlmacenClaves –signedjar


DocumentoFirmado.jar Contrato.jar FirmaContrato

4. Con keytool –export exportar el certificado de clave pública para que el receptor
autentique la firma del emisor.

keytool –export –keystore AlmacenClaves –alias FirmaContrato –


file MariaJesus.cer

5. Por último suministrar el fichero JAR firmado y el certificado al receptor.

El receptor necesita importar el certificado como un certificado de confianza

keytool –import –alias MJesus –file MariaJesus.cer –keystore


AlmacenReceptor

y verificar la firma del fichero JAR.

jarsigner –verify –verbose –certs –keystore AlmacenReceptor


DocumentoFirmado.jar

Clase Cipher

130
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Para crear un objeto Cipher se llama al método getInstance() pasando como argumento
el algoritmo y opcionalmente, el nombre de un proveedor.

Como algoritmo en el método getInstance() se pueden poner los siguientes


(algoritmo/modo/relleno), entre paréntesis se especifica el tamañi de la clave en bits:

AES/CBC/NoPadding (128)
AES/CBC/PKCS5Padding (128)
AES/ECB/NoPadding (128)
AES/ECB/PKCS5Padding (128)
DES/CBC/NoPadding (56)
DES/CBC/PKCS5Padding (56)
DES/ECB/NoPadding (56)
DES/ECB/PKCS5Padding (56)
DESede/CBC/NoPadding (168)
DESede/CBC/PKCS5Padding (168)
DESede/ECB/NoPadding (168)
DESede/ECB/PKCS5Padding (168)
RSA/ECB/PKCS1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)

Los modos son la forma de trabajar del algoritmo

ECB (Electronic Cookbook Mode). Los mensajes se dividen en bloques y cada uno de ellos es
cifrado por separado por separado utilizando la misma clave K. A bloques de texto plano o

131
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

claro idénticos les corresponden bloques idénticos de texto cifrado, de anera que se
pueden reconocer estos patrones. De ahí que no sea recomendable.
CBC (Cipher Block Chaining), a cada bloque de texto plano se le aplica la operación XOR con
el bloque cifrado anterior antes de ser cifrado. De esta forma, cada bloque de texto cifrado
depende de todo el texto en claro procesado hasta este punto. Para hacer cada men saje
único se utilza asimismo un vector de inicialización.

El relleno se utiliza cuando el mensaje a cifrar no es múltiplo de la longitud de cifrado del


algoritmo, entonces es necesario indicar la forma de rellenar los últimos bloques.

Clase KeyGenerator

Proporciona funcionalidades para generar claves secretas para usarse en algoritmos


simétricos. Algunos métodos son:

Pasos para encriptar y desencriptar con clave secreta

Creamos la clave secreta usando AES o DES.


Creamos un objeto Ciphercon el algoritmo/modo/relleno que creamos oportuno, lo
inicializamos en modo encriptación con la clave creada anteriormente.
Realizamos el cifrado de la información con el método doFinal().
Configuramos el objeto Cipher en modo desencriptación con la clave anterior para
desencriptar el texto, usamos el método doFinal().

Archivo EJEMPLO10.JAVA

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;

132
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

public class Ejemplo10 {

public static void main(String[] args) {

try {

//Creamos la clave secreta usando el algoritmo AES y


definimos un tamaño de clave de 128 bits
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init (128);
SecretKey clave = kg.generateKey();

//Creamos un objeto Cipher con el algoritmo


AES/ECB/PKCS5Padding, lo inicializamos en modo encriptación con la
clave creada anteriormente.
Cipher c =
Cipher.getInstance("AES/ECB/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, clave);

//Realizamos el cifrado de la información con el


método doFinal()
byte textoPlano[] = "Esto es un Texto
Plano".getBytes();
byte textoCifrado[] = c.doFinal(textoPlano);
System.out.println("Encriptado: "+ new
String(textoCifrado));

//Configuramos el objeto Cipher en modo


desencriptación con la clave anterior para desencriptar el texto,
usamos el método doFinal()
c.init(Cipher.DECRYPT_MODE, clave);
byte desencriptado[] = c.doFinal(textoCifrado);
System.out.println("Desencriptado: "+ new
String(desencriptado));

/*
//Muchos modos de algoritmo (por ejemplo CBC)
requieren un vector de inicialización que se especifica cuando se
inicializa
//el objeto Cipher en modo desencriptación. En estos
casos, se debe pasar al método init() el vector de inicialización.
//La clase IvParameterSpec se usa para hacer esto en
el cifrado DES.
KeyGenerator kg = KeyGenerator.getInstance("DES");
Cipher c =
Cipher.getInstance("DES/CBC/PKCS5Padding");
Key clave = kg.generateKey();

133
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

//Devuelve el vector IV inicializado en un nuevo


buffer
byte iv[]=c.getIV();
IvParameterSpec dps = new IvParameterSpec(iv);
c.init(Cipher.DECRYPT_MODE, clave, dps);
*/

} catch (NoSuchAlgorithmException e) {
} catch (NoSuchPaddingException e) {
} catch (InvalidKeyException e) {
} catch (IllegalBlockSizeException e) {
} catch (BadPaddingException e) {
// } catch (InvalidAlgorithmParameterException e) {
}

}// Fin de main


}// Fin de Ejemplo10

Almacenar la clave secreta en un fichero

import java.io.*;
import java.security.*;

import javax.crypto.*;

public class AlmacenaClaveSecreta {

public static void main(String[] args) {

try {
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(128);
//genera clave secreta
SecretKey clave = kg.generateKey();
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("Clave.secreta"));
out.writeObject(clave);
out.close();

/*
//Para recuperar la clave secreta del fichero
ObjectInputStream in = new ObjectInputStream(new
FileInputStream("Clave.secreta"));
Key secreta = (Key) in.readObject();
in.close();
*/

} catch (NoSuchAlgorithmException e) {e.printStackTrace();


} catch (FileNotFoundException e) {e.printStackTrace();
//} catch (ClassNotFoundException e) {
e.printStackTrace();//Para recuperar la clave secreta
} catch (IOException e) {e.printStackTrace();}

}//Fin de main
}//Fin de AlmacenaClaveSecreta

Genera una clave secreta AES y la almacena en el fichero Clave.secreta.

134
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Encriptar y desencriptar con clave pública

Conversación encriptada

Criptografía híbrida

Archivo EJEMPLO12.JAVA

import java.security.*;
import javax.crypto.*;

public class Ejemplo12 {

public static void main(String args[]) {

try {
//SE CREA EL PAR DE CLAVES PÚBLICA Y PRIVADA
KeyPairGenerator keyGen =
KeyPairGenerator.getInstance("RSA");
keyGen.initialize (1024);
KeyPair par = keyGen.generateKeyPair();
PrivateKey clavepriv = par.getPrivate();
PublicKey clavepub = par.getPublic();

//SE CREA LA CLAVE SECRETA AES

135
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init (128);
SecretKey clavesecreta = kg.generateKey();

//SE ENCRIPTA LA CLAVE SECRETA CON LA CLAVE RSA


PÚBLICA
Cipher c =
Cipher.getInstance("RSA/ECB/PKCSlPadding");
c.init(Cipher.WRAP_MODE, clavepub);
byte claveenvuelta[] = c.wrap(clavesecreta);

//CIFRAMOS TEXTO CON LA CLAVE SECRETA


c = Cipher.getInstance("AES/ECB/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, clavesecreta);
byte textoPlano[] = "Esto es un Texto
Plano".getBytes();
byte textoCifrado[] = c.doFinal(textoPlano);
System.out.println("Encriptado: " + new
String(textoCifrado));

/* Para desencriptar el texto primero necesitamos


desencriptar la clave Secreta con la clave privada y a continuación
desencriptar el texto con esa clave; usaremos el método unwrap():*/

//SE DESENCRIPTA LA CLAVE SECRETA CON LA CLAVE RSA


PRIVADA
Cipher c2 =
Cipher.getInstance("RSA/ECB/PKCS1Padding");
c2.init(Cipher.UNWRAP_MODE, clavepriv);
Key clavedesenvuelta= c2.unwrap (claveenvuelta,
"AES", Cipher.SECRET_KEY);

//DESCIFRAMOS EL TEXTO CON LA CLAVE DESENVUELTA


c2 = Cipher.getInstance("AES/ECB/PKCS5Padding");
c2.init(Cipher.DECRYPT_MODE, clavedesenvuelta);
byte desencriptado[] = c2.doFinal(textoCifrado);
System.out.println("DesenCriptado:" + new
String(desencriptado));

} catch (Exception e) { e.printStackTrace(); }

}//Fin de main
}//Fin de Ejemplo12

1. Se genera un par de claves pública y privada con el algoritmo RSA.


2. Se crea una clave secreta con el algoritmo AES.
3. Esta clave se creará para encriptar el texto.
4. La clave secreta es encriptada mediante la clave pública utilizando el método wrap()
5. Para desencriptar el texto primero necesitamos desencriptar la clave Secreta con la
clave priada y a continuación desencriptar el texto con esa clave; usaremos el método
unwrap()

Clave de sesión

Es un término medio entre el cifrado simétrico y asimétrico que permite combinar las dos
técnicas. Consiste en generar una clave de sesión K y cifrarla usando la clave pública del
receptor. El receptor descifra la clave de sesión usando su clave privada. El emisor y el receptor

136
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

comparten una clave que solo ellos conocen y pueden cifrar sus comunicaciones usando la
misma clave de sesión.

Encriptar y desencriptar flujos de datos

Clase CipherOutputStream. Encriptar datos hacia un fichero

CipherInputStream. Leer y desencriptar datos de un fichero

Ambas manipular de forma transparente las llamadas a update() y doFinal()

Archivo EJEMPLO13CIFRA.JAVA

import java.io.*;
import java.security.*;
import javax.crypto.*;

public class Ejemplo13Cifra {

public static void main(String[] args) {

try {
//RECUPERAMOS CLAVE SECRETA DEL FICHERO
ObjectInputStream oin = new ObjectInputStream( new
FileInputStream("Clave.secreta"));
Key clavesecreta = (Key) oin.readObject();
oin.close();

//SE DEFINE EL OBJETO Cipher para encriptar

137
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Cipher c =
Cipher.getInstance("AES/ECB/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, clavesecreta);

//FICHERO A CIFRAR
FileInputStream filein = new
FileInputStream("FICHERO.pdf");
//OBJETO CipherOutputStream donde se almacena el
fichero cifrado
CipherOutputStream out = new CipherOutputStream( new
FileOutputStream("FicheroPDF.Cifrado"), c);
int tambloque = c.getBlockSize();//tamaño de bloque
objeto Cipher
byte[] bytes = new byte[tambloque];//bloque de bytes

//LEEMOS BLOQUES DE BYTES DEL FICHERO PDF


//Y int LO VAMOS ESCRIBIENDO AL CipherOutputStream
int i = filein.read(bytes);
while (i != -1) {
out.write(bytes, 0, i);
i = filein.read(bytes);
}

out.flush();
out.close();
filein.close();
System.out.println("Fichero cifrado con clave
secreta.");

} catch (Exception e) {e.printStackTrace();}

}//Fin de main
}// Fin de Ejemplo13Cifra

Utiliza la clave secreta almacenada en un fichero llamado para cifrar un documento PDF de
nombre Fichero.pdf.

Archivo EJEMPLO13DESCIFRA.JAVA

import java.io.*;
import java.security.*;
import javax.crypto.*;

public class Ejemplo13Descifra {

public static void main(String[] args) {

try {
//RECUPERAMOS CLAVE SECRETA DEL FICHERO
ObjectInputStream oin = new ObjectInputStream(new
FileInputStream("Clave.secreta"));
Key clavesecreta = (Key) oin.readObject();
oin.close();

//SE DEFINE EL OBJETO Cipher para desencriptar


Cipher c =
Cipher.getInstance("AES/ECB/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, clavesecreta);

138
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

//OBJETO CipherInputStream CUYO CONTENIDO SE VA A


DESCIFRAR
CipherInputStream in = new CipherInputStream(new
FileInputStream("FicheroPDF.Cifrado"), c);
int tambloque = c.getBlockSize();//tamaño de bloque
byte[] bytes = new byte[tambloque];//bloque de bytes

//FICHERO CON EL CONTENIDO DESCIFRADO QUE SE CREARÁ


FileOutputStream fileout = new
FileOutputStream("FICHEROdescifrado.pdf");

//LEEMOS BLOQUES DE BYTES DEL FICHERO cifrado


//Y LO VAMOS ESCRIBIENDO desencriptados al
FileOutputStream
int i = in.read(bytes);
while (i != -1){
fileout.write(bytes, 0, i);
i = in.read(bytes);
}
fileout.close();
in.close();
System.out.println("Fichero descifrado con clave
secreta.");

} catch (Exception e) {e.printStackTrace();}

}//Fin de main
}//Fin de Ejemplo13Descifra

Utiliza la clase CipherInputStream para leer y desencriptar datos de un fichero cifrado.

Comunicaciones seguras con JAVA. JSSE

JSSE (Java Secure Socket Extension) es un conjunto de paquetes que permiten el desarrollo de
aplicaciones seguras en Internet. Proporciona un marco y una implementación para la versión
Java de los protocolos SSL y TSL e incluye funcionalidad de encriptación de datos,
autenticación de servidores, integridad de mensajes y autenticación de clientes.
Con JSSE, los desarrolladores pueden ofrecer intercambio seguro de datos entre un cliente y
un servidor que ejecuta un protocolo de aplicación, tales como HTTP, Telnet o FTP, a través de
TCP/IP.
Las clases de JSSE se encuentran en los paquetes javax.net y javax.net.ssl.

SSL

Las clas SSLSocket y SSLServerSocket representan sockets seguros y son derivadas de las ya
familiares Socket y ServerSocket respectivamente.

JSSE tiene dos clases SSLServerSocketFactory y SSLSocketFactory para la creación de sockets


seguros. No tienen constructor, se obtienen a través del método estatico getDefault().

Para obtener un socket servidor seguro o SSLServerSocket

SSLServerSocketFactory sfact = (SSLServerSocketFactory)


SSLServerSocketFactory.getDefault();
SSLServerSocket servidorSSL = (SSLServerSocket)
sfact.createServerSocket(puerto);

139
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

El método createServerSocket(int puerto) devuelve un socket de servidor


enlazado al puerto especificado. Para crear un SSLSocket:

SSLSocketFactory sfact = (SSLSocketFactory)


SSLSocketFactory.getDefault();
SSLSocket Cliente = (SSLSocket) sfact.createSocket(Host, puerto);

Archivo SERVIDORSSL.JAVA

import java.io.*;
import javax.net.ssl.*;

public class ServidorSSL {

public static void main(String[] arg) throws IOException {

//System.setProperty("javax.net.ssl.keyStore",
System.getProperty("user.dir") + "\\AlmacenSSL");
//System.setProperty("javax.net.ssl.keyStorePassword",
"1234567");

int puerto = 6000;


SSLServerSocketFactory sfact = (SSLServerSocketFactory)
SSLServerSocketFactory.getDefault();
SSLServerSocket servidorSSL = (SSLServerSocket)
sfact.createServerSocket(puerto);
SSLSocket clienteConectado = null;
DataInputStream flujoEntrada = null; //FLUJO DE ENTRADA DE
CLIENTE
DataOutputStream flujoSalida = null; //FLUJO DE SALIDA AL
CLIENTE

for (int i = 1; i < 5; i++) {

System.out.println("Esperando al cliente " + i);


clienteConectado = (SSLSocket) servidorSSL.accept();
flujoEntrada = new
DataInputStream(clienteConectado.getInputStream());
// EL CLIENTE ME ENVIA UN MENSAJE
System.out.println("Recibiendo del CLIENTE: " + i + "
\n\t" + flujoEntrada.readUTF());
flujoSalida = new
DataOutputStream(clienteConectado.getOutputStream());
// ENVIO UN SALUDO AL CLIENTE
flujoSalida.writeUTF("SaIudos al cliente del
servidor");

}// Fin de for

// CERRAR STREAMS Y SOCKETS


flujoEntrada.close();
flujoSalida.close();
clienteConectado.close();
servidorSSL.close();

140
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

}// Fin de main

}// Fin de ServidorSSL

Crea una conexión sobre un socket servidor seguro y que atenderá hasta cuatro conexiones de
clientes que se identificarán con un certificado válido. El servidor espera las conexiones, de
cada cliente que se conecta recibe un mensaje y a continuación le envía un saludo.

Archivo CLIENTESSL.JAVA

import java.io.*;
import javax.net.ssl.*;

public class ClienteSSL {

public static void main(String[] args) throws Exception {

//System.setProperty("javax.net.ssl.trustStore",
System.getProperty("user.dir") + "\\UsuarioAlmacenSSL");
//System.setProperty("javax.net.ssl.trustStorePassword",
"890123");

String Host = "localhost";


int puerto = 6000;

System.out.println("PROGRAMA CLIENTE INICIADO....");


SSLSocketFactory sfact = (SSLSocketFactory)
SSLSocketFactory.getDefault();
SSLSocket Cliente = (SSLSocket) sfact.createSocket(Host,
puerto);

// CREO FLUJO DE SALIDA AL SERVIDOR


DataOutputStream flujoSalida = new
DataOutputStream(Cliente.getOutputStream());

// ENVIO UN SALUDO AL SERVIDOR


flujoSalida.writeUTF("Saludos al SERVIDOR DESDE EL
CLIENTE");

// CREO FLUJO DE ENTRADA AL SERVIDOR


DataInputStream flujoEntrada = new
DataInputStream(Cliente.getInputStream());

// EL SERVIDOR ME ENVIA UN MENSAJE


System.out.println("Recibiendo del SERVIDOR: \n\t" +
flujoEntrada.readUTF());

/*---------------------------------------------------------
---------------------
//Información sobre la sesión SSL
SSLSession session = ((SSLSocket) Cliente).getSession();
System.out.println("Host: "+session.getPeerHost());
System.out.println("Cifrado: " + session.getCipherSuite());
System.out.println("Protocolo: " + session.getProtocol());
System.out.println("IDentificador:" + new
BigInteger(session.getId()));

141
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

System.out.println("Creación de la sesión: " +


session.getCreationTime());

X509Certificate certificate =
(X509Certificate)session.getPeerCertificates()[0];
System.out.println("Propietario: " +
certificate.getSubjectDN());
System.out.println("Algoritmo: " +
certificate.getSigAlgName());
System.out.println("Tipo: " + certificate.getType());
System.out.println("Emisor: " + certificate.getIssuerDN());
System.out.println("Número Serie: " +
certificate.getSerialNumber());
-----------------------------------------------------------
-------------------*/

// CERRAR STREAMS Y SOCKETS


flujoEntrada.close();
flujoSalida.close();
Cliente.close();

}// Fin de main

}// Fin de ClienteSSL

Envía un mensaje al servidor y visualiza el que el servidor le devuelve.

El servidor necesita disponer de un certificado que mostrar a los clientes que se conecten a él.
Usaremos la herramienta keytool para crearlo, en el ejemplo le damos el nombre de
AlmacenSSL y el valor de la clave es 1234567.

C:>keytool -genkey -alias claveSSL -keyalg RSA –keystore


AlmacenSSL -storepass 1234567

Para ejecutar el programa servidor es necesario indicar el certificado que se utilizará

C:>java -Djavax.net.ssl.keyStore=AlmacenSSL -
Djavax.net.ssl.keyStorePassword=1234567 ServidorSSL

Antes de ejecutar el programa cliente necesitamos colocar el certificado en el keystore del


usuario, para ello lo exportamos a un fichero, le llamamos por ejemplo CertificadoSSL.cer

C:>keytool -export -alias claveSSL -keystore AlmacenSSL -


storepass 1234567 -file Certificado.cer

Una vez que tenemos el fichero exportado es necesario incorporarle al nuevo almacenamiento
para permitir realizar la validación. A continuación se crea un keystore de nombre
UsuarioAlmacenSSL con la clave 890123 y se incorpora el fichero de certificado Certificado.cer.
Esto lo hacemos donde ejecutemos el cliente.

C:>keytool -import -alias claveSSL -file Certificado.cer –


keystore UsuarioAlmacenSSL -storepass 890123

Para ejecutar el programa cliente escribimos lo siguientes

142
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

C:>java -Djavax.net.ssl.trustStore=UsuarioAlmacenSSL -
Djavax.net.ssl.trustStorePassword=890123 ClienteSSL

En estos ejemplos para ejecutar el programa cliente y el servidor hemos establecido las
propiedades JSSE desde la línea de comandos usando la sintaxis –Dpropiedad=Valor. También
se pueden establecer desde el programa usando el método
System.setProperty(String propiedad, String valor).

En el programa servidor incluimos las siguientes líneas después de definir la variable puerto:

System.setProperty("javax.net.ssl.keyStore", "AlmacenSSL");
System.setProperty("javax.net.ssl.keyStorePassword", "1234567");

Y en el programa cliente:

System.setProperty("javax.net.ssl.trustStore", "UsuarioAlmacenSSL");
System.setProperty("javax.net.ssl.trustStorePassword", "890123");

Opcionalmente podemos registrar un proveedor SSL en los programas añadiendo esta línea:

java.security.Security.addProvider(new
com.sun.net.ssl.internal.ssl.Provider());

El método getSession() de la clase SSLSocket devuelve un objeto SSLSession con la sesión


SSL utilizada por la conexión, a partir de ella podemos obtener información como el
identificador de la sesión, el cifrado SSL, el protocolo, el certificado, etc.

SSLSession session = ((SSLSocket) Cliente).getSession();


System.out.println("Host: "+session.getPeerHost());
System.out.println("Cifrado: " + session.getCipherSuite());
System.out.println("Protocolo: " + session.getProtocol());
System.out.println("IDentificador:" + new
BigInteger(session.getId()));
System.out.println("Creación de la sesión: " +
session.getCreationTime());
X509Certificate certificate =
(X509Certificate)session.getPeerCertificates()[0];
System.out.println("Propietario: " + certificate.getSubjectDN());
System.out.println("Algoritmo: " + certificate.getSigAlgName());
System.out.println("Tipo: " + certificate.getType());
System.out.println("Emisor: " + certificate.getIssuerDN());
System.out.println("Número Serie: " + certificate.getSerialNumber());

143
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

En el programa servidor para obtener la información del certificado tendríamos que utilizar el
método getLocalCertificates() en lugar de getPeerCertificates().

Control de acceso con Java. JAAS

JAAS (Java Authentication and Authorization Service, Servicio de Autorización y Autenticación


de Java) es una interfaz que permite a las aplicaciones Java acceder a servicios de control de
autenticación y acceso.

Se puede utilizar para dos propósitos:

Para la autenticación de usuarios: para determinar de forma fiable y segura quién está
ejecutando nuestro código Java
Para la autorización de los usuarios: Para asegurarse de que quien lo ejecuta tiene los
permisos necesarios para realizar las acciones.

En estos procesos están involucradas las clases e interfaces:

LoginContext, contexto de inicio de sesión: clase para iniciar y gestionar el proceso de


autenticación mediante la creación de un Subject. La autenticación se hace llamando
al método login().
LoginModulo, módulo de conexión: interfaz para definir los mecanismos de
autenticación. Se deben implementar los siguientes métodos: iniatilize(),
login(), commit(), abort() y logout(). Se encarga de validar los datos en un
proceso de autenticación.
Subject, clase para representar a un ente autenticable (entidad, usuario, sistema)
Principal, clase que representa los atributos que posee cada Subject recuperado una
vez que se efectúa el ingreso a la aplicación. Un Subject puede contener varios
principales.
CallBackHandler, encargado de la interacción con el usuario para obtener los datos
necesarios para la autenticación. Se debe implementar el método handle().

Los paquetes en los que están disponibles las clases e interfaces principales de JAAS son:

javax.security.auth.* que contiene las clases de base e interfaces para los mecanismos
de autenticación y autorización.
javax.security.auth.callback.* que contiene las clases e interfaces para definir las
credenciales de autenticación de la aplicación.
javax.security.auth.login.* que contiene las clases para entrar y salir de un dominio de
aplicación.
javax.security.auth.spi.* que contiene las interfaces para un proveedor de JAAS para
implementar módulos JAAS.

Autenticación

El proceso básico de autenticación consta de los siguientes pasos

Creación de una instancia de LoginContext, uno o más LoginModulo son cargados


basándose en el archivo de configuración de JAAS.

144
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

La instanciación de cada LoginModule es opcionalmente provista con un


CallbackHandler que gestionará el proceso de comunicación con el usuario para
obtener los datos con los que este tratará de autenticarse.
Invocación del método login() del LoginContext el cual invocará el método login()
del LoginModule.
Los datos del usuario se obtienen por medio del CallbackHandler.
El LoginModule comprueba los datos introducidos por el usuario y los valida. Si la
validación tiene éxito el usuario queda autenticado.

Ejemplo

Esquema básico de las clases y ficheros que se van a utilizar en el ejemplo para probar la
autenticación básica con JAAS

Los ficheros son los siguientes:

Fichero EjemploJaasAutenticacion.java, es la aplicación que será autenticada y


autorizada mediante JAAS.
Fichero EjemploLoginModule.java, implementa el módulo LoginModule que
autentifica a los usuarios mediante su nombre y contraseña.
Fichero MyCallbackHandler.java que implementa CallbackHandler. Transfiere la
información requerida al módulo LoginModule.
Fichero de autenticación de JAAS, jaas.config, donde se configura el módulo de login.
Se vincula el nombre EjemploLogin al módulo LoginModule de nombre
EjemploLoginModule, la palabra required indica que el módulo de conexión
asociado debe ser capaz de autenticar al sujeto en todo el proceso de autenticación
para poder tener éxito.

EjemploLogin{
EjemploLoginModule required;
}

Fichero de autenticación JAAS, policy.config. Se conceden permisos de lectura sobre


las propiedades usuario y clave que se introducirán desde la línea de comandos y el
permiso para crear un contexto de inicio de sesión, createLoginContext, al

145
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

nombre EjemploLogin vinculado con la clase EjemploLoginModule. El contenido del


fichero es el siguiente:

grant {
permission java.util.PropertyPermission "usuario", "read";
permission java.util.PropertyPermission "clave", "read";
permission javax.security.auth.AuthPermission
"createLoginContext.EjemploLogin";
};

Archivo MYCALLBACKHANDLER.JAVA

import java.io.*;
import javax.security.auth.callback.*;

public class MyCallbackHandler implements CallbackHandler {

private String usuario;


private String clave;

//Constructor recibe parámetros usuario y clave


public MyCallbackHandler(String usu, String clave) {

this.usuario = usu;
this.clave = clave;
}

//método handle sera invocado por el LoginMbdule


public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {

for (int i = 0; i < callbacks.length; i++) {

Callback callback = callbacks[i];


if (callback instanceof NameCallback) {
NameCallback nameCB = (NameCallback) callback;
//se asigna al NameCallback el nombre de
usuario
nameCB.setName(usuario);
} else if (callback instanceof PasswordCallback) {
PasswordCallback passwordCB =
(PasswordCallback) callback;
//se asigna al PasswordCallback la clave
passwordCB.setPassword(clave.toCharArray());
}// fin del if
}// fin del for

}// Fin de handle


}// Fin de handle MyCallbackHandler

CallbackHandler tiene el método handle() que será invocado por el LoginModule al que le
pasará un array de objetos Callbacks, que contiene los campos de datos que se necesitan.
Cada Callback representa uno de los datos comunicados por el usuarios en el proceso de
autenticación (nombre, password, etc), es necesario recorrer este array para recuperar los
datos.

146
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Archivo EJEMPLOLOGINMODULE.JAVA

import java.util.Map;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

public class EjemploLoginModule implements LoginModule {

private Subject subject;


private CallbackHandler callbackHandler;

public boolean commit() throws LoginException {return true;}

public boolean logout() throws LoginException {return true;}

public boolean abort() throws LoginException {return true;}

public void initialize(Subject subject, CallbackHandler handler,


Map state, Map options) {
this.subject = subject;
this.callbackHandler = handler;
}

//método login - se realiza la autenticación


public boolean login() throws LoginException {

boolean autenticado = true;


if(callbackHandler == null){
throw new LoginException("Se necesita
CallbackHandler");
}

//Se crea el array de Callbacks


Callback[] callbacks = new Callback[2];
//Constructor de NameCallback y PasswordCallback con prompt
callbacks[0] = new NameCallback("Nombre de usuario: ");
callbacks[1] = new PasswordCallback("Clave: ", false);

try {
//se invoca al método handle del CallbackHandler
//para solicitar el usuario y la contraseña
callbackHandler.handle(callbacks);
String usuario =
((NameCallback)callbacks[0]).getName();
char [] passw =
((PasswordCallback)callbacks[1]).getPassword();
String clave = new String(passw);

147
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

//La autenticación se realiza aquí


//el nombre de usuario: maria, su clave: 1234
autenticado = ("maria".equalsIgnoreCase(usuario) &
"1234".equals(clave)) ;
} catch (Exception e) {e.printStackTrace();}

return autenticado;//devuelve true o false

}//Fin de login
}//Fin de EjemploLoginModule

Implementa el LoginModule que autenticará a los usuarios en su método login(). Debe


implementar los siguientes métodos:

initialize(): El propósito de este método es inicializar el LoginModule con la


información con la información relevante. El Subjectpasasdo a este método se usa
para almacenar los principales (Principal) y credenciales si la conexión tiene éxito.
Recibe un CallbackHandler que puede ser usado para introducir información de la
autenticación.
login(): El propósito de este método es autenticar al Subject.
commit(): Se llama a este método si tiene éxito la autenticación total del
LoginContext.
abort(): Informa al LoginModule de que algún proveedor o módulo ha fallado al
autenticar al Subject. Se llama a este método si la autenticación global del
LoginContext ha fallado.
logout(): Desconecta al Subject borrando los principales y credenciales del Subject.
Finalizará la sesión del usuario.

C:>java -Dusuario=maria -Dclave=1234 EjemploJAASAutenticacion

Autorización

Para que la autorización JAAS tenga lugar, se requiere lo siguiente:

El usuario debe autenticarse. Ya visto


En el fichero de políticas se deben configurar entradas para los principales
Se debe asociar al Subject el contexto de control de acceso actual usando los métodos
doAs() o doAsPrivileged() de la clase Subject.

Para lo cual insertaríamos dos nuevas clases al ejemplo anterior.

Archivo EJEMPLOACCION.JAVA

import java.io.*;
import java.security.PrivilegedAction;

public class EjemploAccion implements PrivilegedAction {

public Object run() {

File f = new File("fichero.txt");


if (f.exists()) {

148
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

System.out.println("EL FICHERO EXISTE ... ");


//Si existe se muestra su contenido
FileReader fic;
try {
fic = new FileReader(f);
int i;
System.out.println("Su contenido es: ");
while ((i = fic.read()) != -1)
System.out.print((char) i);
fic.close();
} catch (Exception e) { e.printStackTrace();}

}else {

//Si no existe se crea y se insertan datos


System.out.println("EL FICHERO NO EXISTE, LO CREO ...
");
try {
FileWriter fic = new FileWriter(f);
String cadena = "Esto es una linea de texto";
fic.append(cadena); fic.close();// cerrar
fichero
System.out.println("Fichero creado con
datos...");
} catch (IOException e) {
System.out.println("ERROR ==> " +
e.getMessage());
}
}// fin de if

return null;

}// Fin de run


}// Fin de EjemploAccion

Esta clase implementa PrivilegedAction, contiene el método run() que es el código que se
ejecutará una vez que el usuario ha sido autenticado, teniendo en cuenta las autorizaciones de
los principales definidas en el fichero de políticas.

Archivo EJEMPLOPRINCIPAL.JAVA

import java.security.Principal;

public class EjemploPrincipal implements Principal,


java.io.Serializable {

private String name;//nombre del principal

//Crea un EjemploPrincipal con el nombre suministrado.


public EjemploPrincipal(String name) {

if (name == null)
throw new NullPointerException("Entrada nula");
this.name = name;
}

//Devuelve el nombre del Principal


public String getName() {return name;}

149
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

//Compara el objeto especificado con el Principal


//para ver si son iguales.
public boolean equals(Object o) {

if (o == null) return false;


if (this == o) return true;
if (!(o instanceof EjemploPrincipal)) return false;
EjemploPrincipal that = (EjemploPrincipal) o;
if (this.getName().equals(that.getName())) return true;

return false;
}//Fin de equals

public int hashCode() {return name.hashCode();}

public String toString() {return (name);}

}//Fin de EjemploPrincipal

Esta clase implementa la interface Principal, se usa en la clase EjemploLoginModule una vez
que el usuario ha sido autenticado en el método commit().

Privilegios de acceso en el fichero de políticas (policy.config)

grant {
permission javax.security.auth.AuthPermission
"createLoginContext.EjemploLogin";
permission javax.security.auth.AuthPermission
"modifyPrincipals";
permission javax.security.auth.AuthPermission
"doAsPrivileged";
};

grant Principal EjemploPrincipal "maria" {


permission java.io.FilePermission "fichero.txt", "read";
} ;

grant Principal EjemploPrincipal "juan" {


permission java.io.FilePermission "fichero.txt", "write";
};

150
Desarrollo de Aplicaciones Multiplataforma. Inazio Claver
programandoapasitos.blogspot.com

Epílogo
Hasta aquí ha llegado lo visto en el módulo de Programación de servicios y procesos del Grado
Superior de Desarrollo de Aplicaciones Multiplataformas.

Tengo la sensación de que éste libro podría haber sido más eminentemente práctico, o haber
podido profundizar más a fondo en algunos temas, pero considero que pese a todo he creado
una buena guía para todo aquel que quiera iniciarse en la programación de servicios y
procesos desde cero.

Aparte del texto aquí presente, podéis visitar mi página web


http://programandoapasitos.blogspot.com, donde iré colgando muchos más tutoriales y
actualizando / corrigiendo los ya publicados.

Además, siempre podéis recurrir a Google para buscar información, o leer los siguientes libros
que recomiendo para comprender como programar procesos y servicios:

Programación de servicios y procesos, de Alberto Sánchez Campos y Jesús Montes


Sánchez
Make: Getting Started with Processing, de Casey Reas y Ben Fry
The Nature of Code: Simulating Natural Systems with Processing, de Daniel Shiffman

Espero que disfrutéis programando tanto como yo lo he hecho aprendiendo.

Inazio Claver
Huesca, a 25 de Marzo de 2016

151

También podría gustarte