Download Programación Distribuida y su Aplicación Bajo Internet

Document related concepts
no text concepts found
Transcript
Programación Distribuida y su Aplicación Bajo
Internet
Módulo 3.- Programación Concurrente y Prog. en red
Curso 2001–2002. Juan S. Sendra
Índice
1. Programación Concurrente
1.1. Introducción . . . . . . . . . .
1.2. Tareas en Java . . . . . . . .
1.3. Planificación . . . . . . . . .
1.4. Sincronización . . . . . . . . .
1.4.1. Exclusión mútua . . .
1.4.2. Espera y Notificación
1.5. Animación . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2. Programación en Red
2.1. Contenido del paquete java.net . . . . . . . .
2.2. Direcciones de Internet . . . . . . . . . . . . .
2.3. Ports . . . . . . . . . . . . . . . . . . . . . . .
2.4. Protocolos . . . . . . . . . . . . . . . . . . . .
2.5. URLs . . . . . . . . . . . . . . . . . . . . . .
2.6. Sockets . . . . . . . . . . . . . . . . . . . . .
2.6.1. Lectura/Escritura de datos desde/a un
1.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
2
3
5
6
7
8
9
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
socket
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
13
13
13
15
16
16
19
21
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Programación Concurrente
Programación Concurrente es la denominación de las técnicas de programación utilizadas para expresar paralelismo (varias actividades simultáneas)
y resolver los problemas de comunicación y sincronización que se presentan.
Java es uno de los pocos lenguajes de programación que incorpora construcciones para expresar concurrencia1 .
1
La programación concurrente resulta mucho más simple y natural en Java que en otros
lenguajes
1
1
PROGRAMACIÓN CONCURRENTE
1.1.
1.1
Introducción
Introducción
La ejecución secuencial (una actividad tras otra) presenta limitaciones a
distintos niveles:
flexibilidad .- no podemos alterar la secuencia de ejecución para dar paso
a tareas más urgentes, o responder de inmediato a las acciones del
usuario
interactividad .- la interacción con el usuario debe seguir estrictamente el
orden secuencial especificado por el programa
eficiencia .- Un sistema informático dispone de diferentes recursos que potencialmente pueden explotarse en paralelo (ej.- podemos servir una
petición a disco y simultáneamente utilizar el procesador, la tarjeta
de red, etc.). Si imponemos un orden secuencial podemos dilapidar ese
potencial (ej.- si la aplicación lanza una operación sobre disco, durante
el periodo de lectura/escritura el procesador no puede ejecutar nuevas
acciones
En la actualidad muchas aplicaciones necesitan concurrencia interna Ej.un navegador web puede visualizar un documento mientras descarga otros
ficheros. La concurrencia interna se implementa lanzando varas tareas que
cooperan entre sı́. Cada tarea mantiene un estado propio (código a ejecutar,
pila, registro, punto de ejecución) y además comparte con el resto los recursos
definidos globalmente por la aplicación.
Las distintas tareas se ejecutan teóricamente en paralelo. En la realidad,
el número de procesadores es inferior al de tareas, por lo que se utilizauna
estrategia de tiempo compartido, multiplexando el tiempo de procesador
entre las distintas tareas.
La programación concurrente plantea ventajas e inconvenientes
2
1
PROGRAMACIÓN CONCURRENTE
1.2
Tareas en Java
Ventajas
Inconvenientes
Facilita la programación reactiva .- algunos programas deben realizar actividades como respuesta (reacción) a
estı́mulos externos. Ej.- un sistema de
tiempo real que controla un proceso fı́sico debe responder a cambios en
temperatura, humedad, iluminación,
etc.
Seguridad .- Además de comprobar que cada tarea es correcta, debemos garantizar que las tareas no interfieren entre
sı́
Disponibilidad .- podemos replicar los servicios o explotar el paralelismo para
mejorar la disponibilidad (ej.- la impresora es varios órdenes de magnitud
más lenta que un disco; cada escritura en impresora se traduce en volcar
los datos a disco –tras lo cual la aplicación puede continuar– y lanzar una
tarea que vuelca datos de disco a impresora en cuanto ésta está libre
Simplifica el diseño .- Los objetos reales
suelen
mostrar
comportamiento
autónomo (cada uno evoluciona de
forma independiente). Para representarlos por programa es mucho más
fácil lanzar una tarea para cada uno
de ellos. Ej.- para simular vehı́culos,
que poseen dirección, velocidad, etc.
distintos entre sı́
Vivacidad .- Una o más tareas pueden bloquearse indefinidamente por distintas
causas (ej.- otras actividades monopolizan el uso de la CPU, o existe un
interbloqueo)
No determinismo .- La multiplexación del
tiempo de procesador no es fija, sino
que puede variar en cada ejecución. Un
programa que depende del intercalado
concreto de instrucciones resulta impredecible (no determinista), complicando su depuración
Coste .- Hay un coste extra por la multiplexación del tiempo de CPU (hay
que decidir qué tarea procede a continuación, y realizar el cambio de contexto entre tareas), y un coste de sincronización (para evitar interferencias
entre tareas)
Paralelismo .- en sistemas multiprocesador
podemos lanzar tareas independientes
en cada procesador, y en sistemas
monoprocesador multiplexar el tiempo de procesador para explotar el paralelismo con otros dispositivos
1.2.
Tareas en Java
Podemos implementar tareas de dos formas:
Extender la clase java.lang.Thread. Posee los métodos para controlar
una tarea
start .- inicia la ejecución de la tarea
run .- contiene el código a ejecutar
stop .- detiene la tarea
3
1
PROGRAMACIÓN CONCURRENTE
1.2
Tareas en Java
otros .- métodos para suspender/relanzar la tarea, retrasar su ejecución durante el periodo indicado, ceder el control a otras tareas,
etc.
class ImprimeNum extends Thread {
public void run() {
for (int i=-10; i<10; i++) System.out.print(""+i);
}
}
public class Test1 {
public static void main (String[] arg) {
ImprimeNum in = new ImprimeNum();
in.start();
}
}
Utilizar el interfaz java.lang.Runnable. Lanzamos la tarea pasando al
constructor un objeto que implementa Runnable. Resulta útil cuando
queremos lanzar la tarea para un objeto que ya extiende a otra clase.
class ImprimeNum implements Runnable {
public void run() {
for (int i=-10; i<10; i++) System.out.print(""+i);
}
}
public class Test1 {
public static void main (String[] arg) {
Thread in = new Thread(new ImprimeNum());
in.start();
}
}
Una vez iniciada la ejecución el procesador divide su tiempo entre las
sentencias tras start() y el código de la tarea (en run). Cuando se alcanza
el final del método run (o se invoca stop) la tarea ha finalizado.
El siguiente programa lanza tres tareas ImprimeNum. El orden en que
se muestra la salida del programa depende del intercalado de instrucciones
en la CPU, y por lo tanto es impredecible (puede variar al ejecutarse en
sistemas diferentes, o incluso entre ejecuciones en un mismo sistema).
class ImprimeNum extends Thread {
public void run() {
for (int i=-10; i<10; i++) System.out.print(""+i);
}
}
public class Test2 {
public static void main (String[] arg) {
ImprimeNum in1 = new ImprimeNum();
4
1
PROGRAMACIÓN CONCURRENTE
1.3
Planificación
ImprimeNum in2 = new ImprimeNum();
ImprimeNum in3 = new ImprimeNum();
in1.start(); in2.start(); in3.start();
}
}
Podemos dar nombre a las distintas tareas (ej.- para identificarlas durante la depuración). El constructor de Thread admite como parámetro una
tira de caracteres que corresponde al nombre de la tarea.
Ej.- Incorporando nombre a las tareas, podemos distinguir el autor de
cada lı́nea en nuestro programa ejemplo
class ImprimeNum extends Thread {
public ImprimeNum(String nom) {super(nom);}
public void run() {
for (int i=-10; i<10; i++) System.out.print(getName()+": "+i);
}
}
public class Test3 {
public static void main (String[] arg) {
ImprimeNum in1 = new ImprimeNum("Pepe");
ImprimeNum in2 = new ImprimeNum("Carlos");
ImprimeNum in3 = new ImprimeNum("Maria");
in1.start(); in2.start(); in3.start();
}
}
1.3.
Planificación
Una tarea es ejecutable (candidata a utilizar el procesador) si ha arrancado (con start), no ha terminado todavı́a (no ha finalizado el código de run
ni se ha ejecutado stop), y no está esperando un recurso.
Las tareas ejecutables se sitúan en unas colas de planificación organizadas
por prioridades y controladas por el soporte en ejecución de Java.
Por defecto, cada nueva tarea tiene la misma prioridad que su creador
Podemos alterar la prioridad invocando Thread.setPriority con un argumento entero en el rango [1..10] (mayor valor implica mayor prioridad). La clase Thread define las constantes MAX PRIORITY, NORM PRIORITY,
MAX PRIORITY
Cuando el procesador queda libre, se elige para ejecución el proceso ejecutable de mayor prioridad:
Si hay más de uno con dicha prioridad, se elige uno de forma no determinista
Si se necesita ejecutar una tarea de más prioridad que el que ocupa el
procesador, éste es expulsado (pre-empted)
5
1
PROGRAMACIÓN CONCURRENTE
1.4
Sincronización
El método yield devuelve voluntariamente el procesador
public class Test4 {
public static void main (String[] arg) {
ImprimeNum in1 = new ImprimeNum("Pepe");
ImprimeNum in2 = new ImprimeNum("Carlos");
ImprimeNum in3 = new ImprimeNum("Maria");
in1.setPriority(Thread.MIN_PRIORITY);
in2.setPriority(Thread.NORM_PRIORITY);
in3.setPriority(Thread.MAX_PRIORITY);
in1.start(); in2.start(); in3.start();
}
}
Los métodos de control (para afectar a la planifiación) son los siguientes:
start .- Provoca que la tarea invoque su método run como una actividad
independiente. A menos que se invoque sobre la tarea un método especial de control (ej. stop), la tarea termina cuando finaliza run
isAlive .- Es un método que devuelve cierto cuando la tarea ha arrancado
pero todavı́a no ha finalizado
stop .- Termina la tarea de forma irrevocable. Tras la terminación puede
invocarse de nuevo start para lanzar una nueva actividad usando la
misma tarea. La forma alternativa stop(excepcion) detiene la tarea y
lanza la excepción indicada.
suspend .- Suspende temporalmente la tarea, de forma que prosigue cuando
otra tarea ejecuta resume sobre la tarea detenida.
sleep .- Suspende la tarea durante el número de milisegundos especificado,
y luego la reactiva de forma automática
join .- Suspende al invocante hasta que se completa la tarea indicada (un
segundo argumento opcional indica un plazo máximo de espera en
milisegundos)
interrupt .- Provoca que se aborte la espera (iniciada con sleep, wait o
join) lanzando una interrupción tipo InterruptedException
1.4.
Sincronización
En general las tareas deben cooperar entre sı́, y para ello comparten
objetos (ej. una tarea modifica el estado de un objeto y otra tarea lee dicho
estado). Nuestra responsabilidad es garantizar que las distintas tareas no
interfieren entre sı́ (ej. una tarea no debe cambiar los datos mientras otra
los usa).
6
1
PROGRAMACIÓN CONCURRENTE
1.4
Sincronización
Ej.- el siguiente código no protege contra posibles interferencias, y por
tanto no es determinista (dos tareas intentan modificar los campos del mismo
objeto, y el orden en que se modifican los datos depende del intercalado
concreto).
public class Contador {
int i=0;
public void cuenta() {
int limite = i+100;
while (i++ != limite) System.out.println(i);
}
}
public class TareaContador extends Thread {
Contador c;
public static void main (String[] arg) {
Contador c = new Contador();
TareaContador t1 = new TareaContador(c);
TareaContador t2 = new TareaContador(c);
t1.start(); t2.start();
}
public TareaContador(Contador c0) {c = c0;}
public void run() {c.cuenta();}
}
1.4.1.
Exclusión mútua
Las interferencias entre tareas únicamente pueden aparecer en los fragmentos de código que acceden a objetos compartidos (denominados secciones
crı́ticas). Para garantizar el determinismo (evitar interferencias) es suficiente
garantizar la exclusión mútua entre secciones crı́ticas.
Con exclusión mútua garantizamos la ejecución secuencial (no concurrente) de las secciones crı́ticas → no pueden multiplexarse en el tiempo
instrucciones correspondientes a distintas secciones crı́ticas.
Java garantiza la exclusión mútua en el acceso a un método o bloque de
código mediante la palabra synchronized
public class ContadorSincronizado extends Contador {
public synchronized void void cuenta () {
int limite = i+100;
while (i++ != limite) System.out.println(i);
}
}
La palabra synchronized significa que dicho método es una sección crı́tica,
o sea que debe ejecutarse en exclusión mútua con las restantes secciones
crı́ticas del objeto (si las tareas t1 y t2 invocan métodos sincronizados sobre
el mismo objeto, una de las dos debe esperar).
Java garantiza que las operaciones primitivas (ej.- asignaciones sobre
todos los tipos primitivos excepto long y double) son atómicas, y siempre
7
1
PROGRAMACIÓN CONCURRENTE
1.4
Sincronización
pueden utilizarse con seguridad en contextos multitarea. Cualquier otro fragmento de código que debe comportarse como atómico requiere la etiqueta
synchronized.
La exclusión mútua se implementa mediante un contador interno de cada objeto Java. Dicho contador se incrementa cuando se invoca un método
sincronizado, y se decrementa cuando finaliza la ejecución del mismo. Un
método no sincronizado puede ejecutarse con independencia del valor del
contador, pero los métodos sincronizados sólo se pueden ejecutar si el contador vale 0 (en caso contrario dicha tarea queda temporalmente suspendida).
Los métodos definidos en interfaces no pueden etiquetarse como sincronizados.
1.4.2.
Espera y Notificación
Además de evitar interferencias, las tareas deben comunicarse información utilizando objetos compartidos.
Algunos de los métodos definidos en un objeto sólo deben invocarse en
determinados estados del objeto. ej.- en una lista podemos consultar la longitud independientemente del estado de la lista, pero sólo podemos extraer
si la lista no está vacı́a.
Cuando una tarea invoca un método sobre un objeto compartido, y el
estado del objeto no permite la ejecución de ese método, dicha tarea debe
esperar.
En general, la estructura de un método sincronizado es:
public synchronized tipoRetorno metodoEj (param) {
while (no se puede ejecutar)
wait();
.... // operaciones normales
if (el nuevo estado permite reactivar a otro)
notify():
}
Los métodos de sincronización disponibles son:
wait .- suspende la tarea actual (se deposita en una cola interna asociada al
objeto) y libera la exclusión mútua sobre ese objeto. Existe un método wait(ms), donde se especifica la espera máxima en milisegundos
(transcurrido ese plazo, se reactiva de forma automática)
notify .- reactiva a una (arbitrariamente) de las tareas de la cola asociada
al objeto
notifyAll .- igual que notify, pero liberando a todas las tareas suspendidas
sobre el objeto
8
1
PROGRAMACIÓN CONCURRENTE
1.5
Animación
Si durante la espera en la cola asociada al objeto ocurre una interrupción
(InterruptedException), se reactiva automáticamente la tarea (y se ejecuta
la correspondiente cláusula catch).
Ej.- Implementamos una lista en las que distintas tareas pueden insertar/extraer de forma simultánea. Los métodos Inserta y Extrae son sincronizados, pero Longitud no. La lista posee un tamaño máximo (indicado
en la creación), se implementa mediante un vector circular, y sólo permite
extraer si no está vacı́a e insertar si no está llena.
public class Lista {
int cabeza, cola, n, max;
public Lista (int tallaMax) {
max=tallaMax;
v = new Object[max]; // para guardar los datos
cabeza=cola=n=0; // inicialmente vacia
}
public int longitud() { return n; }
public synchronized void inserta(Object x) {
while (n==max) // lista llena
try {wait();} catch(InterruptedException e) {return;}
v[cola]=x;
cola = (cola+1) % max;
n++;
notifyAll(); // reactivamos por si alguien desea extraer
}
public synchronized Object extrae() {
while (n==0) // lista vacia
try {wait();} catch(InterruptedException e) {return;}
Object x=v[cabeza];
cabeza = (cabeza+1) % max;
n--;
notifyAll(); // reactivamos por si alguien desea insertar
return x;
}
}
1.5.
Animación
La animación consiste en una sucesión rápida (y perfectamente sincronizada) de imágenes. Si deseamos realizar animaciones en un applet debemos:
1.
Calcular la nueva imagen e invocar repaint para actualizar pantalla
2.
Repetir el paso 1) de forma periódica (ej.- cada 50ms para obtener 20
imágenes por segundo)
La siguiente discusión sobre animación nos permitirá ilustrar los mecanismos de programación concurrente.
9
1
PROGRAMACIÓN CONCURRENTE
1.5
Animación
Ej1.- Animación simple de una pelota (cı́rculo rojo) rebotando dentro de
una caja. La función paint se limita a dibujar la bola:
import java.awt.*;
public class BolaLoca extends java.applet.Applet implements Runnable {
int x=30, y=40, r=20; // posicion y radio de la bola
int incx=2, incy=2: // direccion y magnitud del movimiento
public void init() {(new Thread(this)).start();}
public void paint(Graphics g) {
g.setColor(Color.red);
g.fillOval(x, y, r, r);
}
}
public void run() {
while (true) { // bucle infinito
int nx =x+incx;
if (nx>=size().width || nx<0) incx*=-1; else x=nx;
int ny =y+incy;
if (ny>=size().heigth || ny<0) incy*=-1; else y=ny;
repaint();
}
}
}
Ej2.- Una animación correcta requiere una base de tiempos. El Ej1 muestra una bola con velocidad errática porque ésta depende de la multiplexación
del tiempo de procesador. Incorporamos la base de tiempos:
import java.awt.*;
public class BolaLoca extends java.applet.Applet implements Runnable {
int x=30, y=40, r=20; // posicion y radio de la bola
int incx=2, incy=2: // direccion y magnitud del movimiento
int retardo=50; // retardo de 50ms equivale a 20 fotogramas por segundo
public void init() {(new Thread(this)).start();}
public void paint(Graphics g) {
g.setColor(Color.red);
g.fillOval(x, y, r, r);
}
}
public void run() {
while (true) { // bucle infinito
long inicio = (new Date()).getTime();
int nx =x+incx;
if (nx>=size().width || nx<0) incx*=-1; else x=nx;
int ny =y+incy;
if (ny>=size().heigth || ny<0) incy*=-1; else y=ny;
repaint();
10
1
PROGRAMACIÓN CONCURRENTE
1.5
Animación
try {
Thread.currentThread.sleep(retardo-(new Date()).getTime()+inicio)
}
catch (InterruptedException e) {}
}
}
}
Ej3.- El ejemplo 2 muestra una velocidad constante, pero también aparece
un notable efecto parpadeo debido a la falta de sincronización entre el momento en que el sistema desea actualizar pantalla y el momento en que desea
hacerlo el programa.
Para resolver el parpadeo debemos reducir al mı́nimo el intervalo de actualización de pantalla. Existen dos vı́as de solución (utilizar un área de
clipping y utilizar doble buffering para pantalla), pero únicamente desarrollamos el esquema denominado doble buffering.
Se trata de componer el dibujo sobre una variable interna, y no sobre
pantalla (posteriormente se vuelca esa variable a pantalla, un proceso mucho
más rápido que dibujar cada elemento individual). A;adimos al applet el
siguiente código:
private Image im;
private Dimension tallaim;
private Graphics g2;
public final synchronized void update (Graphics g) {
Dimension d=size();
if (im==null || (d.width != tallaIm.width) || (d.heigth != tallaIm.heigth)) {
im = createImage(d.width, d.height);
tallaIm = d;
g2 = im.getGraphics();
}
g2.clearRect(0,0,d.width,d.height);
paint(g2);
g.drawImage(im,0,0,null);
}
Ej4.- Añadimos la posibilidad de detener/reanudar la animación mediante sendos clicks de ratón
import java.awt.*;
public class BolaLoca extends java.applet.Applet implements Runnable {
int x=30, y=40, r=20; // posicion y radio de la bola
int incx=2, incy=2: // direccion y magnitud del movimiento
Thread t; // tarea subyacente
boolean activo;
private Image im;
11
1
PROGRAMACIÓN CONCURRENTE
1.5
Animación
private Dimension tallaim;
private Graphics g2;
public void init() {
t=new Thread(this);
activo=false;
addMouseListener (new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (activo) t.suspend(); else t.resume();
activo = !activo;
}
});
}
public void start() {t.start(); activo=true;}
public final synchronized void update (Graphics g) {
Dimension d=size();
if (im==null || (d.width != tallaIm.width) || (d.heigth != tallaIm.heigth)) {
im = createImage(d.width, d.height);
tallaIm = d;
g2 = im.getGraphics();
}
g2.clearRect(0,0,d.width,d.height);
paint(g2);
g.drawImage(im,0,0,null);
}
public void paint(Graphics g) {
g.setColor(Color.red);
g.fillOval(x, y, r, r);
}
}
public void run() {
while (true) { // bucle infinito
int nx =x+incx;
if (nx>=size().width || nx<0) incx*=-1; else x=nx;
int ny =y+incy;
if (ny>=size().heigth || ny<0) incy*=-1; else y=ny;
repaint();
}
}
}
12
2
PROGRAMACIÓN EN RED
2.
Programación en Red
Las caracterı́sticas de portabilidad, junto a las facilidades para acceder
a servicios básicos de red (URLs, sockets, etc.), justifican el desarrollo de
programas Java diseñados para ejecución en red (ej. monitorización remota
de equipos, aplicaciones de trabajo en grupo, etc.).
Este capı́tulo introduce el soporte básico de red, y analiza la estructura
de programas en red simples.
2.1.
Contenido del paquete java.net
El paquete java.net contiene el soporte para programación en red, organizado como sigue:
general
Datagrama
Socket
URL
Clases
ContentHandler
InetAddress
HttpURLConnection
DatagramPacket
DatagramSocket
DatagramSocketImpl
MulticastSocket
ServerSocket
Socket
SocketImpl
URL
URLConnection
URLEncoder
URLStreamHandler
Interfaces
ContentHandlerFactory
FileNameMap
Excepciones
BindException
ConnectException
NoRouteToHostException
UnknownHostException
UnknownServiceException
SocketImplFactory
URLStreamHandlerFactory
URLStreamHandlerFactory
MalformedURLException
El resto del capı́tulo introduce los distintos conceptos (IP, socket, URL,
etc.) y detalla las definiciones asociadas con cada uno.
2.2.
Direcciones de Internet
Cada ordenador conectado a Internet se identifica mediante una dirección única de 4 bytes denominada dirección IP (normalmente se escriben
los cuatro valores separados por puntos: ej 199.3.45.234). Para simplificar
su uso, asociamos nombres a las direcciones IP (ej. ”www.hp.com”, ”mistral.dsic.upv.es”).
Un ordenador pueden disponer de varias direcciones IP (ej. porque dispone
de varias tarjetas de red), pero generalmente sólo posee una. En cualquier
caso, la máquina posee un nombre simbólico único.
Una dirección IP corresponde a la clase java.net.InetAddress
13
2
PROGRAMACIÓN EN RED
2.2
Método
InetAddress getByName(String host) throws
UnknownHostException
InetAddress[] getAllByName(String host)
throws UnknownHostException
InetAddress getLocalHost() throws
UnknownHostException
String getHostName()
byte[] getAddress()
int hashCode()
boolean equals(Object x)
String toString()
Direcciones de Internet
Explicación
dirección IP del host
todas las direcciones asociadas a ese
host
dirección IP de la máquina que ejecuta el código
nombre correspondiente a la
máquina local
dirección IP de esta máquina como
vector de bytes (mayor peso en pos
0)
devuelve un código hash para esa dirección
compara esta dir. con el objeto x
convierte la dir. en una tira imprimible
La clase InetAddress no posee constructores públicos. Para generar nuevos
objetos pasamos el nombre de la máquina (o una tira con su dirección fı́sica)
al método de clase getByName.
try {
InetAddress d1=InetAddress.getByName("mistral.dsic.upv.es");
InetAddress d2=InetAddress.getByName("128.34.5.12");
}
catch (UnknowHostException e) {
System.out.println(e.getMessage());
}
Si la máquina posee varias direcciones utilizamos getAllByName
import java.net.*;
class A {
public static void main (String[] arg) {
try {
InetAddress[] d=InetAddress.getAllByName("www.apple.com");
for (int i=0; i<d.length; i++)
System.out.println(d[i]);
}
catch (UnknowHostException e) {
System.out.println(e.getMessage());
}
}
}
// muestra
// www.applet.com/17.254.3.28
// www.applet.com/17.254.3.37
// www.applet.com/17.254.3.61
// www.applet.com/17.254.3.21
El método InetAddress.getLocalHost() devuelve un objeto InetAddress
que contiene la dirección del ordenador en el que se está ejecutando el programa.
14
2
PROGRAMACIÓN EN RED
2.3 Ports
try {
InetAddress yo=InetAddress.getLocalHost();
}
catch (UnknowHostException e) {
System.out.println(e.getMessage());
}
Dado un objeto InetAddress, podemos:
Obtener su nombre simbólico como String (getHostName)
Obtener su dirección IP como String (getHostAddress)
Obtener su dirección IP como vector de bytes (getAddress)
Averiguar si es o no una dirección multicast (isMulticastAddress)
El siguiente programa imprime información sobre la máquina local
import java.net.*;
class A {
public static void main (String[] arg) {
try {
InetAddress yo = InetAddress.getLocalHost();
System.out.println("mi nombre es "+yo.getHostName()+
" y mi direccion es "+yo.getAddress());
byte[] d = yo.getAddress();
for (int i=0; i<d.length; i++) {
int unsignedByte = d[i] < 0 ? d[i]+256 : d[i];
System.out.println(unsignedByte+" ");
}
}
catch (UnknowHostException e) {
System.out.println("No conozco ni mi dirección");
}
}
}
También podemos averiguar el nombre de la máquina dada su dirección
IP
2.3.
Ports
En muchos casos hay que mantener abiertas varias conexiones simultáneas
con máquinas distintas (ej. varias sesiones ftp, conexiones web, etc.). Con
este fin el interfaz de red se divide desde un punto de vista lógico en 65536
ports distintos.
El concepto de port es una abstracción (no representa ningún componente fı́sico). Cada paquete que circula por la red indica un número de port
15
2
PROGRAMACIÓN EN RED
2.4
Protocolos
además de la dirección IP destino; cuando la máquina destino recibe un paquete, utiliza el número de port para determinar qué programa utilizará el
contenido del mensaje (en un instante dado sólo puede haber un programa
dado escuchando en cada port).
Cada port puede recibir varias conexiones simultáneas (ej. un servicio
web suele utilizar el port 80, y soporta varias conexiones simultáneas en dicho
port). En resumen, a un port podemos asociar varias conexiones externas
simultáneas, pero un sólo proceso local.
Muchos servicios asumen números concretos de port (por convenio o por
indicación del protocolo). Ej.- los servidores http escuchan generalmente en
el port 80, los servidores SMTP en el 25, los de Echo en el 7, etc. Otros
servicios no manejan ports predefinidos, sino que los descubren de forma
dinámica (ej. NFS).
2.4.
Protocolos
Un protocolo define la forma en que se comunican dos máquinas. Ej
El protocolo Daytime (RFC 867) indica que el cliente se conecta al
puerto (port) 13 del servidor, tras lo cual el servidor contesta con una
tira de caracteres que representa la hora actual y cierra la conexión
El protocolo Time (RFC 868) indica que el cliente se conecta al port
9 del servidor, y entonces el servidor contesta con la hora actual en
formato binario y cierra la conexión
Existen tantos tipos distintos de protocolos como de servicios que los
utilizan. Los protocolos Lockstep requieren una respuesta en cada paso, protocolos como FTP utilizan varias conexiones y permiten varias peticiones y
respuestas por conexión, protocolos como HTTP sólo permiten una petición
y respuesta por conexión, etc.
2.5.
URLs
Una URL (Uniform Resource Locator) es un mecanismo para identificar
de forma no ambigua la ubicación de un recurso en Internet. Una URL puede
descomponerse hasta en 5 partes, que corresponden a:
protocolo .- JDK 1.1 entiende los protocolos mailto, ftp, file, http y appletresource. Además define y utiliza internamente los protocolos doc,
netdoc, systemresource, verbatim
máquina .- IP correspondiente a la máquina donde se localiza el servicio
port .- port a utilizar
fichero .16
2
PROGRAMACIÓN EN RED
2.5
URLs
sección .- referencia dentro del fichero
Ejemplos de URLs:
http://www.javasoft.com
file://Macintosh\%20HD/Java/docs/jdk%201.1/api/java.net.InetAddress.html
mailto:[email protected]
La clase java.net.URL representa una URL. Hay constructores para crear
URLs y métodos que descomponen una URL en sus elementos, pero la utilidad más interesante es la posibilidad de obtener un stream (flujo de datos)
de entrada a partir de una URL. de forma que podamos leer datos desde un
servidor.
El objetivo es separar la gestión de los datos descargados, y la gestión del
protocolo utilizado para descargar los datos. El gestor de protocolo comunica
con el servidor y lleva a cabo cualquier negociación previa con el mismo, y
devuelve como resultado el fichero o secuencia de bytes solicitados. Entonces
el gestor de contenido interpreta dicho fichero o secuencia de bytes y los
traduce en algún tipo de objeto Java (ej. InputStream o ImageProducer).
Cuando consumimos un objeto URL, Java busca un gestor adecuado
para el protocolo (si no hay ninguno, lanzala excepción MalformedURLException). Podemos construir una URL a partir de una tira única o de tiras
separadas para cada elemento de la URL (protocolo, máquina, port, fichero,
sección). Además, muchas páginas HTML contienen URLs relativas (ej. permiten mantener un mismo documento en distintos mirrors sin invalidar las
referencias internas), por lo que también existe un constructor para crear
URLs relativas a una dada.
// URL(String U)
try {
URL u = new URL("http:/www.poly.edu/schedule/fall97/bgrad.html#cs");
}
catch (MalformedURLException e) {}
// URL(String protocolo, String maquina, String fichero)
try {
URL u = new URL("http","www.poly.edu","schedule/fall97/bgrad.html#cs");
}
catch (MalformedURLException e) {}
// URL(String protocolo, String maquina, int port, String fichero)
try {
URL u = new URL("http","www.poly.edu",80,"schedule/fall97/bgrad.html#cs");
}
catch (MalformedURLException e) {}
// URL(String contexto, String u)
try {
17
2
PROGRAMACIÓN EN RED
2.5
URLs
URL u1 = new URL("http://sunsite.unc.edu/javafaq/course/week12/07.html);
URL u2 = new URL(u1, "08.html");
}
catch (MalformedURLException e) {}
La clase URL posee cinco métodos para dividir una URL en sus componentes: getProtocol, getHost, getPort, getFile, getRef.
try {
URL u = new URL("http:/www.poly.edu/schedule/fall97/bgrad.html#cs");
System.out.println(
"\nEl protocolo es :"+u.getProtocol()+
"\nLa maquina es :"+u.getHost()+
"\nEl port es :"+u.getPort()+
"\nEl fichero es :"+u.getFile()+
"\nLa seccion es :"+u.getRef());
}
catch (MalformedURLException e) {}
Si la URL no especifica un número de Port, getPort devuelve -1 (lo cual
indica que se utiliza el port por defecto para ese protocolo). Si la URL no
especifica un fichero, getFile devuelve /”.
Sobre una URL también podemos abrir una conexión que devuelve un
flujo de entrada (InputStream).
Un flujo de entrada constituye una secuencia de bytes a los que podemos
acceder mediante operaciones read y readBytes. Cuando se abre la conexión
(operación openStream) se descartan todos los campos de cabecera y control
previos a los datos.
El siguiente ejemplo intenta la conexión con el servidor especificado,
descarga los datos, y los escribe en pantalla.
import java.net.*;
import java.io.*;
public class R6 { // lectura de datos desde una URL
public static void main (String[] arg) {
try {
URL u = new URL(arg[0]);
InputStream is = u.openStream();
DataInputStream dis = new DataInputStream(is);
String linea;
while ((linea=dis.readLine()) != null)
System.out.println(linea);
}
catch (MalformedURLException e) {System.out.println(e.Message());}
catch (IOException e) {System.out.println(e.Message());}
}
}
18
2
PROGRAMACIÓN EN RED
2.6.
2.6 Sockets
Sockets
En Internet enviamos datos entre máquinas utilizando TCP/IP. Los
datos se dividen en paquetes de talla variable, pero finita (ej < 64k) denominados datagramas. Si perdemos un paquete se retransmite sólo el paquete
extraviado (en lugar de reenviar todos los paquetes), y si los paquetes llegan
desordenados pueden reordenarse en el receptor.
Java permite manejar datagramas y Sockets. Un socket representa una
comunicación fiable entre dos máquinas, ocultando al programador los detalles de codificación de paquetes, paquetes perdidos y retransmitidos, y
paquetes que llegan fuera de orden. El software de red de java gestiona de
forma transparente la división de los datos en paquetes (en el emisor) y su
recepción y reconstrucción (en el receptor).
Las operaciones básicas que soporta un socket son la conexión a una
máquina remota (no puede conectarse simultáneamente a más de una máquina),
envı́o de datos, recepción de datos, y cierre de conexión.
La clase java.net.socket permite realizar todas las operaciones fundamentales sobre sockets:
19
2
PROGRAMACIÓN EN RED
conexión
2.6 Sockets
mediante la construcción de nuevos sockets. Cada socket se asocia exactamente con una máquina remota (para conectar con otra
máquina hay que crear otro socket).
Como mı́nimo hay que especificar la máquina y port a los que
conectamos (la máquina puede especificarse mediante una tira o un
objeto InetAddress, y el port debe ser un entero entre 1 y 65536).
También podemos especificar la máquina y el port desde el que
conectamos (un valor 0 para el port indica la elección arbitraria de
uno de los ports disponibles). Todos los constructores no sólo crean
el socket, sino que además intentan conectar el socket al servidor
remoto (si no es posible, se activa una excepción IOException).
public Socket (String host, int port)
throws UnknownHostException, IOException
public Socket (InetAddress dir, int port)
throws UnknownHostException, IOException
public Socket (String host, int port, InetAddress dirLocal, int portLocal)
throws UnknownHostException, IOException
public Socket (InetAddress dir, int port, InetAddress dirLocal, int portLocal)
throws UnknownHostException, IOException
envı́o y recepción
Se realiza mediante streams (flujos) de entrada y salida. Existen
métodos para obtener flujos para entrada y para salida en un socket
public InputStream getInputStream () throws IOException
public OutputStream getOutputStream () throws IOException
cierre
public synchronized void close () throws IOException
fijar opciones
public void setTcpNoDelay (boolean on)
throws SocketException
public boolean getTcpNoDelay ()
throws SocketException
public void setSoLinger (boolean on, int val)
throws SocketException
public synchronized void setSoTimeout (int timeout)
throws SocketException
public synchronized int getSoTimeout ()
throws SocketException
public static synchronized void setSocketImplFactory (SocketImplFactory f)
throws SocketException
obtención información
public
public
public
public
public
InetAddress getInetAddress()
InetAddress getLocalAddress()
int getPort()
int getLocalPort()
String toString()
No podemos conectar con cualquier port en cualquier máquina: la máquina
20
2
PROGRAMACIÓN EN RED
2.6 Sockets
remota debe estar esperando conexiones en ese port. El siguiente código permite averiguar qué ports esperan conexiones en una máquina dada:
import java.net.*;
import java.io.*;
public class R7 { // explora ports
public static void main (String[] arg) {
try {
busca(InetAddress.getLocalHost());
}
catch (UnknownHostException e) {
System.out.println("No conozco la maquina ");
}
}
static void busca (InetAddress dir) {
String host = dir.getHostName();
for (int port=0; port<20; port++) {
try {
Socket s = new Socket(dir,port);
System.out.println("Hay un servidor escuchando en el port "+port);
s.close();
}
catch (IOException e) {
System.out.println("nadie escucha en el port "+port);
}
}
}
}
2.6.1.
Lectura/Escritura de datos desde/a un socket
Una vez que hemos conectado un socket podemos enviar datos al servidor
a través de un flujo de salida o leer datos mediante un flujo de entrada. El
significado exacto de los datos enviados/recibidos depende del protocolo
utilizado.
El método getInputStream() devuelve un flujo de entrada (InputStream)
que lee datos desde el socket.
import java.net.*;
import java.io.*;
import java.util.Date;
public class R8 { // lectura de datos desde un socket
public static void main (String[] arg) {
try {
Socket s = new Socket("sunsite.unc.edu",13);
InputStream is = s.getInputStream();
21
2
PROGRAMACIÓN EN RED
2.6 Sockets
DataInputStream dis = new DataInputStream(is);
String hora = dis.readLine();
System.out.println(hora);
s.close();
}
catch (UnknownHostException ex) {
System.out.println("No conozco la maquina ");
System.out.println("hora local = "+(new Date()).toString());
}
catch (IOException ex) {
System.out.println("Error de Entrada/Salida ");
System.out.println("hora local = "+(new Date()).toString());
}
}
}
El método getOutputStream() devuelve un flujo de salida (OutputStream)
que escribe datos en el socket.
import java.net.*;
import java.io.*;
public class whois {
public final static int port = 43;
public final static String hostname = "whois.internic.net";
public static void main(String[] args) {
Socket theSocket;
DataInputStream theWhoisStream;
PrintStream ps;
try {
theSocket = new Socket(hostname, port, true);
ps = new PrintStream(theSocket.getOutputStream());
for (int i = 0; i < args.length; i++) ps.print(args[i] + " ");
ps.print("\r\n");
theWhoisStream = new DataInputStream(theSocket.getInputStream());
String s;
while ((s = theWhoisStream.readLine()) != null) {
System.out.println(s);
}
}
catch (IOException e) {
System.err.println(e);
}
}
22
2
PROGRAMACIÓN EN RED
2.6 Sockets
}
En la mayor parte de los casos el cliente desea intercalar lecturas y escrituras. Algunos protocolos requieren una alternancia estricta entre lecturas
y escrituras; otros, como HTTP, permiten varias operaciones de cada tipo
(varias de lectura, varias de escritura), y otros protocolos permiten cualquier
secuencias de lecturas y escrituras. Java no impone ninguna restricción (ej.
una tarea puede leer de un socket mientras otra escribe en el mismo).
El siguiente ejemplo envı́a una petición a un servidor http utilizando un
flujo de salida sobre un socket; luego, lee la respuesta mediante un flujo de
entrada (el servidor cierra la conexión cuando ha enviado la respuesta)
import java.net.*;
import java.io.*;
public class R10 { // lectura-escritura a un socket para HTTP
public static void main (String[] arg) {
int port=80;
for (int i=0; i<arg.length; i++) {
try {
URL u=new URL(arg[i]);
if (u.getPort() != -1) port=u.getPort();
if (!(u.getProtocol().equalsIgnoreCase("http"))) {
System.out.println("Lo siento, pero no entiendo HTTP");
continue;
}
Socket s = new socket(u.getHost(), u.getPort());
OutputStream os = s.getOutputStream();
PrintWriter pw = new PrintWriter(os, false); // no auto-flushing
// a~
nadimos las marcas al final de linea
pw.print("GET" + u.getFile() + " HTTP/1.0\r\n");
pw.print("Aceptamos: text/plain, text/html, text/*\r\n");
pw.print("\r\n");
pw.flush();
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
String linea;
while ((linea = dis.readLine()) != null) {
System.out.println(linea);
}
}
catch (MalformedURLException ex) {
System.out.println("No es una URL valida ");
}
catch (IOException ex) {
System.out.println(e.getMessage());
}
23
2
PROGRAMACIÓN EN RED
2.6 Sockets
}
}
}
24