Objetivo del tema Comunicación de procesos en sistemas distribuidos • Presentar los diferentes esquemas para comunicar procesos en sistemas distribuidos: – Servicios básicos de comunicación (colas de mensajes POSIX, mailslots de Win32, sockets) – Llamadas a procedimientos remotos – Entornos orientados a objetos (CORBA, DCOM, RMI). • Presentar el concepto de cliente-servidor • Comunicación de grupos. • Comunicación de procesos en aplicaciones paralelas (MPI) Sistemas Distribuidos Contenido • • • • • • • 2 Félix García Carballeira (1999) Comunicación en sistemas distribuidos • La comunicación de procesos es fundamental en cualquier sistema distribuido • Existen diferentes posibilidades todas ellas basadas en el paso de mensajes – Mecanismos de bajo nivel, el programador debe preocuparse de establecer los protocolos de comunicación, representación de datos, etc. • Colas de mensajes • Sockets – Mecanismo de alto nivel, ofrecen abstracciones donde el programador no debe preocuparse de establecer protocolos • Llamadas a procedimientos remotos • Invocación de métodos remotos (entornos orientados a objetos) Sistemas Distribuidos 4 Félix García Carballeira (1999) Mecanismos de comunicación entre procesos Mecanismos básicos de comunicación entre procesos Modelo cliente/servidor y comunicación en grupos Colas de mensajes POSIX y Mailslots de Win32 Sockets en Unix y Java Llamadas a procedimientos remotos (RPC) Entornos orientados a objetos. CORBA, RMI Comunicación en aplicaciones paralelas. MPI Sistemas Distribuidos 1 Félix García Carballeira (1999) • Ficheros • Tuberías – Sin nombre: pipes – Con nombre: FIFOs • Memoria Compartida – Espacio del sistema operativo – Espacio compartido entre los procesos (threads) • Paso de mensajes Sistemas Distribuidos 3 Félix García Carballeira (1999) Paso de mensajes: conceptos básicos • Primitivas básicas – send(destino, mensaje) – receive(origen, mensaje) • Tamaño del mensaje – Longitud fija o variable • Flujo de datos – Bidireccional, unidireccional • Nombrado – Comunicación directa • send(P, m): envía un mensaje m al proceso P • receive(Q, m): recibe un mensaje del proceso Q • receive (ANY, m): recibe un mensaje de cualquiera Sistemas Distribuidos 5 Félix García Carballeira (1999) Conceptos básicos Patrones típicos de comunicación • Nombrado – Comunicación indirecta: los datos se envían a estructuras intermedias • Puertos: se asocian a un proceso (un único receptor) – Ejemplo: sockets • Comunicación cliente-servidor – Transmisión de datos y código • Comunicación de grupos – Útil en servicios replicados • Colas de mensajes: múltiples emisores y receptores – Ejemplo: Colas de mensajes POSIX • Tipos de comunicación – Síncrona o asíncrona • Capacidad de almacenamiento (buffering) • Fiabilidad – Fiables o no fiables • Representación de datos, aplanamiento Sistemas Distribuidos 6 Félix García Carballeira (1999) Sistemas Distribuidos Comunicación cliente-sevidor petcición Máquina B cliente NÚCLEO servidor respuesta Félix García Carballeira (1999) Comunicación de grupos • Muy utilizada en entornos distribuidos (más del 90% de los sistemas distribuidos utilizan la arquitectura cliente-servidor) Máquina A 7 NÚCLEO • Utiliza mensajes multicast • Útil para: – Ofrecer tolerancia a fallos basado en servicios replicados – Localizar objetos en sistemas distribuidos – Mejor rendimiento mediante datos replicados – Actualizaciones múltiples – Operaciones colectivas en cálculo paralelo RED • Protocolo típico: petición-respuesta Sistemas Distribuidos 8 Félix García Carballeira (1999) Sistemas Distribuidos Tuberías en POSIX (pipes, FIFOS) Tuberías en POSIX • Si p ≥ n ⇒ devuelve n • Si p < n ⇒ devuelve p • int pipe(int fildes[2]); – Creación de FIFOS • int mkfifo(char *name, mode_t mode); • int open(char *name, int flag); – Lectura de una tubería • read(int fd, char *buf, int size); – Escritura de una tubería • write(int fd, char *buf, int size); 10 Félix García Carballeira (1999) • Semántica de las lecturas (read(fd[0], buf, n)) – Si la tubería está vacía ⇒ se bloquea al lector – Si la tubería tiene p bytes: • Mecanismo de comunicación y sincronización • Servicios POSIX – Creación de pipes Sistemas Distribuidos 9 Félix García Carballeira (1999) – Si la tubería está vacía y no hay escritores devuelve 0 • Semántica de las escrituras (write(fd[1], buf, n)) – Si la tubería está llena ⇒ se bloquea el escritor – Si no hay lectores se recibe la señal SIGPIPE • Lecturas y escrituras son atómicas (cuidado con tamaños grandes) Sistemas Distribuidos 11 Félix García Carballeira (1999) Colas de mensajes POSIX • Mecanismo de comunicación y sincronización • Nombrado indirecto • Asignan a las colas nombres de ficheros – Sólo pueden utilizar colas de mensajes procesos que comparten un mismo sistema de ficheros • Tamaño del mensaje variable • Flujo de datos: bidireccional • Sincronización: – Envío asíncrono – Recepción síncrona o asíncrona • Los mensajes se pueden etiquetar con prioridades Sistemas Distribuidos 12 Félix García Carballeira (1999) Colas de mensajes POSIX: servicios • mqd_t mq_open(char *name, int flag, mode_t mode, struct mq_attr *attr) – Crea una cola de mensajes con nombre y atributos • Número máximo de mensajes • Tamaño del mensaje • Recepción bloqueante o no bloqueante • int mq_close(mqd_t mqdes) – Cierra una cola • int mq_unlink(char *name) – Borra una cola de mensajes Sistemas Distribuidos Colas de mensajes POSIX: servicios • int mq_send(mqd_t mqdes, char *msg, size_t len, int prio) – Envía un mensaje a una cola con prioridad prio – Si la cola está llena el servicio bloquea al proceso • int mq_receive(mqd_t mqdes, char *msg, size_t len, int prio) – Recibe un mensaje de una cola – La recepción puede ser bloqueante o no dependiendo de los atributos asociados a las colas. Sistemas Distribuidos 14 Félix García Carballeira (1999) M áquina A cliente M áquina B sum ar(5,2) servidor 5+2 N Ú CLE O R estulado = 7 N Ú CLE O R ED Sistemas Distribuidos 15 Félix García Carballeira (1999) Proceso servidor 256 struct peticion { int a; int b; char q_name[MAXSIZE]; Félix García Carballeira (1999) Ejemplo Definición de tipos #define MAXSIZE 13 /* operando 1 */ /* operando 2 */ /* nombre de la cola cliente donde debe enviar la respuesta el servidor */ #include <mqueue.h> void main(void) { mqd_t q_servidor; mqd_t q_cliente; struct peticion pet; int res; struct mq_attr attr; /* cola de mensajes del servidor */ /* cola de mensajes del cliente */ attr.mq_maxmsg = 20; attr.mq_msgsize = sizeof(struct peticion); q_servidor = mq_open(“SERVIDOR_SUMA”, O_CREAT|O_READ, 0700, &attr); while(1) { mq_receive(q_servidor, &pet, sizeof(pet), 0); res = pet.a + pet.b; }; /* se responde al cliente abriendo previamente su cola */ q_cliente = mq_open(pet.q_name, O_WRONLY); mq_send(q_cliente, &res, sizeof(int), 0); mq_close(q_cliente); Sistemas Distribuidos 16 Félix García Carballeira (1999) } } Sistemas Distribuidos 17 Félix García Carballeira (1999) Proceso cliente #include <mqueue.h> void main(void) { mqd_t q_servidor; mqd_t q_cliente; struct peticion pet; int res; struct mq_attr attr; Mailslots de Win32 • Similares a las colas de mensajes de POSIX • Tamaño máximo de los mensajes es de 64 KB • Pueden utilizarse entre procesos que ejecuten en máquinas de un mismo dominio. • El nombre de un mailslots sigue el formato: \\.\mailslot\nombre • Para abrir un mailslot debe utilizarse: – \\.\mailslot\nombre para un mailslot local – \\.\maquina\mailslot\nombre para una máquina remota – \\.\dominio\mailslot\nombre para un mailslot creado en un dominio /* cola de mensajes del proceso servidor */ /* cola de mensajes para el proceso cliente */ attr.mq_maxmsg = 1; attr.mq_msgsize = sizeof(int); q_cliente = mq_open(“CLIENTE_UNO”, O_CREAT|O_RDONLY, 0700, &attr); q_servidor = mq_open(“SERVIDOR_SUMA”, O_WRONLY); /* se rellena la petición */ pet.a = 5; pet.b = 2; strcpy(pet.q_name, “CLIENTE_UNO”); mq_send(q_servidor, &pet, sizeof(struct petiticion), 0); mq_receive(q_cliente, &res, sizeof(int), 0); mq_close(q_servidor); mq_close(q_cliente); mq_unlink(“CLIENTE_UNO”); } Sistemas Distribuidos 18 Félix García Carballeira (1999) Sistemas Distribuidos Mailslots: servicios • HANDLE CreateFile(LPCSTR name, DWORD Access, DWORD ShareMode, LPVOID lpSecurityAttributes, DWORD CreationDisposition, DWORD FlagsAndAttributes, Handle TemplateFile); – Abre un mailslot existente. 20 Félix García Carballeira (1999) Mailslots: servicios • HANDLE CreateMailslot(LPCTSTR name, DWORD MaxMessSize, DWORD lReadTimeout, LPSECURITY_ATTRIBUTES lpsa): – Tamaño máximo del mensaje de mensajes – Tiempo de bloqueo de una operación de lectura (INFINITE) – Atributos de seguridad Sistemas Distribuidos 19 Félix García Carballeira (1999) • BOOL ReadFile(HANDLE hm, LPVOID lpBuffer DWORD nBytes, LPDWORD lpnBytes, LPOVERLAPPED lpOverlapped); – Lee datos de un mailslot • BOOL WriteFile(HANDLE hm, LPVOID lpBuffer DWORD nBytes, LPDWORD lpnBytes, LPOVERLAPPED lpOverlapped); – Escribe datos a un mailslot • BOOL CloseHandle(HANDLE hm); – Cierra un mailslot Sistemas Distribuidos Proceso servidor 21 Félix García Carballeira (1999) Proceso cliente #include <windows.h> void main(void) { HANDLE mhs, mhc; struct peticion pet; int res, nrw; #include <windows.h> void main(void) { HANDLE mhs; HANDLE mhc; struct peticion pet; int res, nrw; mhs = CreateMailslot(“\\.\mailslot\SERVIDOR_SUMA”, sizeof(struct peticion), INFINITE, NULL); while(1) { ReadFile(mhs, (char *)&pet, sizeof(struct peticion), &nrw, NULL); res = pet.a + pet.b; /* mailslot del proceso servidor */ /* mailslot para el proceso cliente */ mhc = CreateMailslot(“\\.\mailslot\CLIENTE_UNO”, sizeof(struct peticion), INFINITE, NULL); mhs = CreateFile (“\\.\mailslot\SERVIDOR_SUMA”, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, NULL); /* se rellena la petición */ pet.a = 5; pet.b = 2; strcpy(pet.q_name, “\\.\mailslot\CLIENTE_UNO”); /* se responde al cliente abriendo previamente su mailslot */ mhc = CreateFile(pet.q_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, NULL); WriteFile(mhc, &res, sizeof(int), &nrw, NULL); CloseHandle(mhc); WriteFile(mhs, (char *)&pet, sizeof(struct peticion),&nrw, NULL); ReadFile(mhc, &res, sizeof(int),&nrw, NULL); } CloseHandle(mhc); CloseHandle(mhs); } } Sistemas Distribuidos 22 Félix García Carballeira (1999) Sistemas Distribuidos 23 Félix García Carballeira (1999) Sockets: introducción Sockets: introducción • Aparecieron en 1981 en UNIX BSD 4.2 – Intento de incluir TCP/IP en UNIX – Diseño independiente del protocolo de comunicación • Un socket es punto final de comunicación (dirección IP y puerto) • Abstracción que: – Ofrece interfaz de acceso a los servicios de red en el nivel de transporte • Sujetos a proceso de estandarización dentro de POSIX (POSIX 1003.1g) • Actualmente – Disponibles en casi todos los sistemas UNIX – En prácticamente todos los sistemas operativos • WinSock: API de sockets de Windows – En Java como clase nativa • Protocolo TCP • Protocolo UDP – Representa un extremo de una comunicación bidireccional con una dirección asociada Sistemas Distribuidos 24 Félix García Carballeira (1999) Sistemas Distribuidos Sockets UNIX • • • • • • • • • 26 • • • • Un dominio representa una familia de protocolos Un socket está asociado a un dominio desde su creación Sólo se pueden comunicar sockets del mismo dominio Algunos ejemplos: – PF_UNIX (o PF_LOCAL): comunicación dentro de una máquina – PF_INET: comunicación usando protocolos TCP/IP • Los servicios de sockets son independientes del dominio Félix García Carballeira (1999) Sistemas Distribuidos Tipos de sockets 28 27 Félix García Carballeira (1999) Direcciones de sockets • Stream (SOCK_STREAM) – Orientado a conexión – Fiable, se asegura el orden de entrega de mensajes – No mantiene separación entre mensajes – Si PF_INET se corresponde con el protocolo TCP • Datagrama (SOCK_DGRAM) – Sin conexión – No fiable, no se asegura el orden en la entrega – Mantiene la separación entre mensajes – Si PF_INET se corresponde con el protocolo UDP • Raw (SOCK_RAW) – Permite el acceso a los protocolos internos como IP Sistemas Distribuidos Félix García Carballeira (1999) Dominios de comunicación Dominios de comunicación Tipos de sockets Direcciones de sockets Creación de un socket Asignación de direcciones Solicitud de conexión Preparar para aceptar conexiones Aceptar una conexión Transferencia de datos Sistemas Distribuidos 25 Félix García Carballeira (1999) • Cada socket debe tener asignada una dirección única • Las direcciones se usan para: – Asignar una dirección local a un socket (bind) – Especificar una dirección remota (connect o sendto) • Dependientes del dominio • Se utiliza la estructura struct sockaddr • Cada dominio usa una estructura específica – Uso de cast en las llamadas • Direcciones en PF_UNIX (struct sockaddr_un) – Nombre de fichero Sistemas Distribuidos 29 Félix García Carballeira (1999) Direcciones de sockets en PF_INET • Usuarios manejan direcciones en forma de texto: – decimal-punto: 138.100.8.100 – dominio-punto: laurel.datsi.fi.upm.es • Conversión a binario desde decimal-punto: – int inet_aton(char *str, struct in_addr *dir) • Host (32 bits) + puerto (16 bits) • Estructura struct sockaddr_in – Debe iniciarse a 0 – sin_family: dominio (AF_INET) – sin_port: puerto – sin_addr: dirección del host • Funciones para transformar formato: – htonl: de host a red – ntohl: de red a host Sistemas Distribuidos 30 Obtención de la dirección de host • str: contiene la cadena a convertir • dir: resultado de la conversión en formato de red • Conversión a binario desde dominio-punto: – struct hostent *gethostbyname(char *str) • str: cadena a convertir • Devuelve la estructura que describe el host Félix García Carballeira (1999) Sistemas Distribuidos Creación de un socket 31 Félix García Carballeira (1999) Asignación de direcciones • int socket(int dominio, int tipo, int protocolo) – Crea un socket devolviendo un descriptor de fichero – dominio: PF_XXX – tipo: SOCK_XXX – protocolo: dependiente del dominio y tipo • 0 elige el más adeucado • Especificados en /etc/protocols • El socket creado no tiene dirección asignada • int bind(int sd, struct sockaddr *dir, int long) – sd: descriptor devuelto por socket – dir: dirección a asignar – long: longitud de la dirección • Si no se asigna dirección (típico en clientes) – Se le asigna automáticamente (puerto efímero) en la su primera utilización (connect o sendto) • Direcciones en dominio PF_INET – Puertos en rango 0..65535. Reservados: 0..1023. Si 0, el sistema elige uno – Host: una dirección local IP • INNADDR_ANY: elige cualquiera de la máquina • El espacio de puertos para streams y datagramas es indendiente Sistemas Distribuidos 32 Félix García Carballeira (1999) Solicitud de conexión Sistemas Distribuidos 33 Félix García Carballeira (1999) Preparar para aceptar conexiones • Realizada en el cliente • int connect(int sd, struct sockaddr *dir, int long) – sd: descriptor devuelto por socket – dir: dirección del socket remoto – long: longitud de la dirección • Si el socket no tiene dirección asignada, se le asigna una automáticamente • Normalmente se usa con streams • Realizada en el servidor stream después de socket y bind • int listen(int sd, int baklog) – sd: descriptor devuelto por socket – backlog: Sistemas Distribuidos Sistemas Distribuidos 34 Félix García Carballeira (1999) • Número máximo de peticiones pendientes de aceptar que se encolarán (algunos manuales recomiendan 5) • Hace que el socket quede preparado para aceptar conexiones. 35 Félix García Carballeira (1999) Aceptar una conexión Aceptar una conexión • Realizada en el servidor stream después de socket, bind y listen • Cuando se produce la conexión, el servidor obtiene: – La dirección del socket del cliente – Un nuevo descriptor que queda conectado al socket del cliente • Después de la conexión quedan activos dos sockets en el servidor: – El original para aceptar nuevas conexiones – El nuevo para enviar/recibir datos por la conexión • int accept(int sd, struct sockaddr *dir, int *long) – sd: descriptor devuelto por socket – dir: dirección del socket del cliente devuelta – long: parámetor valor-resultado Sistemas Distribuidos Sistemas Distribuidos 36 Félix García Carballeira (1999) Obtener la dirección de un socket • Obtener la dirección a partir del descriptor – int getsockname(int sd, struct sockaddr *dir, int *long) • sd: descriptor devuelto por socket • dir: dirección del socket devuelta • long: parámetro valor-resultado (igual que en accept) • Obtener la dirección del socket en el toro extremo de la conexión: – int gerpeername(int sd, struct sockaddr *dir, int *long) • sd: descriptor devuelto por socket • dir: dirección del socket remoto • long: parámetro valor-resultado Sistemas Distribuidos 38 • Antes de la llamada: tamaño de dir • Después de la llamada: tamaño de la dirección del cliente que se devuelve. 37 Félix García Carballeira (1999) Transferencia de datos con streams • Una vez realizada la conexión, ambos extremos puede transferir datos. • Envío: – Puede usarse el servicio write sobre el descriptor de socket – int send(int sd, char *mem, int long, int flags) • Devuelve el nº de bytes enviados • Recepción: – Puede usarse el servicio read sobre el descriptor de socket – int recv(int sd, char *mem, int long, int flags) • Devuelve el nº de bytes recibidos • Los flags implican aspectos avanzado como enviar o recibir datos urgentes (out-of-band) Félix García Carballeira (1999) Sistemas Distribuidos 39 Félix García Carballeira (1999) Transferencia de datos con datagramas Cerrar un socket • No hay conexión real • Para usar un socket para transferir basta con: – Crearlo: socket – Asignarle una dirección: bind (si no, lo hará el sistema) • Envío: – int sendto(int sd, char *men, int long, int flags, struct sockaddr *dir, int long) • Se usa close para cerrar ambos tipos de sockets • Si el socket es de tipo stream, close cierra la conexión en ambos sentidos • Se puede cerrar un único extremo: – int shutdown(int st, int modo) • Devuelve el nº de bytes enviados • dir: dirección del socket remoto y long la longitud • sd: descriptor devuelto por socket • modo: SHUT_RD, SHUT_RW o SHUT_RDWR • Rccepción: – int recvfrom(int sd, char *men, int long, int flags, struct sockaddr *dir, int long) • Devuelve el nº de bytes enviados • dir: dirección del socket remoto y long la longitud Sistemas Distribuidos 40 Félix García Carballeira (1999) Sistemas Distribuidos 41 Félix García Carballeira (1999) Escenario típico con sockets streams Configuración de opciones Proceso servidor • Existen varios niveles dependiendo del protocolo afectado como parámetro – SOL_SOCKET: opciones independientes del protocolo – IPPROTO_TCP: nivel de protocolo TCP – IPPTOTO_IP: nivel de protocolo IP • Consultar opciones asociadas a un socket socket() Proceso cliente bind() listen() socket() Abrir conexión accept() connect() – int getsockopt(int sd, int nivel, int opc, char *val, int *long) • Modificar las opciones asociadas a un socket Petición write() – int setsockopt(int sd, int nivel, int opc, char *val, int long) • Ejemplos (nivel SOL_SOCKET): – SO_REUSEADDR: permite reutilizar direcciones Sistemas Distribuidos 42 read() cliente M áquina B servidor Restulado = 7 close() 43 Félix García Carballeira (1999) Servidor (TCP) 5+2 NÚCLEO write() Sistemas Distribuidos Ejemplo (TCP) sumar(5,2) read() Respuesta close() Félix García Carballeira (1999) M áquina A Crear thread accept() void main(int argc, char *argv[]) { struct sockaddr_in server_addr, int sd, sc; int size, val; int size; int num[2], res; client_addr; sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); val = 1; setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *) &val, sizeof(int)); NÚCLEO RED bzero((char *)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = 4200; bind(sd, &server_addr, sizeof(server_addr)); Sistemas Distribuidos 44 Félix García Carballeira (1999) Sistemas Distribuidos Servidor (TCP) Félix García Carballeira (1999) Cliente (TCP) listen(sd, 5); size = sizeof(client_addr); while (1) { printf("esperando conexion\n"); sc = accept(sd, (struct sockaddr *)&client_addr,&size); read(sc, (char *) num, 2 *sizeof(int)); 45 // recibe la petición res = num[0] + num[1]; void main(void) { int sd; struct sockaddr_in server_addr; struct hostent *hp; int num[2], res; sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); bzero((char *)&server_addr, sizeof(server_addr)); hp = gethostbyname ("arlo.datsi.fi.upm.es"); write(sc, &res, sizeof(int)); // se envía el resultado memcpy (&(server_addr.sin_addr), hp->h_addr, hp->h_length); server_addr.sin_family = AF_INET; server_addr.sin_port = 4200; close(sc); } close (sd); exit(0); } Sistemas Distribuidos 46 Félix García Carballeira (1999) Sistemas Distribuidos 47 Félix García Carballeira (1999) Cliente (TCP) Servidor (datagramas) // se establece la conexión connect(sd, (struct sockaddr *) &server_addr, sizeof(server_addr)); num[0]=5; num[1]=2; void main(void) { int num[2]; int s, res, clilen; struct sockaddr_in server_addr, client_addr; s = write(sd, (char *) num, 2 *sizeof(int)); read(sd, &res, sizeof(int)); // envía la petición // recibe la respuesta printf("Resultado es %d \n", res); close (sd); exit(0); socket(AF_INET, SOCK_DGRAM, 0); bzero((char *)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = 7200; bind(s, (struct sockaddr *)&server_addr, sizeof(server_addr)); } Sistemas Distribuidos 48 Félix García Carballeira (1999) Sistemas Distribuidos Servidor (datagramas) 49 Félix García Carballeira (1999) Cliente (datagramas) void main(int argc, char *argv[]){ struct sockaddr_in server_addr, client_addr; struct hostent *hp; int s, num[2], res; clilen = sizeof(client_addr); while (1) { recvfrom(s, (char *) num, 2* sizeof(int), 0, (struct sockaddr *)&client_addr, &clilen); if (argc != 2){ printf("Uso: client <direccion_servidor> \n"); exit(0); } res = num[0] + num[1]; sendto(s, (char *)&res, sizeof(int), 0, (struct sockaddr *)&client_addr, s = socket(AF_INET, SOCK_DGRAM, 0); hp = gethostbyname (argv[1]); clilen); bzero((char *)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; memcpy (&(server_addr.sin_addr), hp->h_addr, hp->h_length); server_addr.sin_port = 7200; } } Sistemas Distribuidos 50 Félix García Carballeira (1999) Sistemas Distribuidos Cliente (datagramas) 51 Félix García Carballeira (1999) Rendimiento I bzero((char *)&client_addr, sizeof(client_addr)); client_addr.sin_family = AF_INET; client_addr.sin_addr.s_addr = INADDR_ANY; client_addr.sin_port = htons(0); • Tiempo en realizar una suma utilizando datagramas y streams. Incluye todas las operaciones (creación de sockets, conexión, etc.) • Biprocesador Pentiun II 266 MHZ con Linux. bind (s, (struct sockaddr *)&client_addr, sizeof(client_addr)); num[0] = 2; UDP TCP Local 2,2 ms 2,74 ms Remoto 6,8 ms 7,35 ms num[1] = 5; sendto(s, (char *)num, 2 * sizeof(int), 0, (struct sockaddr *) &server_addr, sizeof(server_addr)); recvfrom(s, (char *)&res, sizeof(int), 0, NULL, NULL); printf("2 + 5 = %d\n", res); close(s); } Sistemas Distribuidos 52 Félix García Carballeira (1999) Sistemas Distribuidos 53 Félix García Carballeira (1999) Rendimiento II Sockets de Java • Tiempo en realizar únicamente el envío y la recepción de datos (sendto, recvfrom, read, write). Sin conexiones UDP TCP Local 0,21 ms 0,34 ms Remoto 0,29 ms 0,44 ms Sistemas Distribuidos 54 Félix García Carballeira (1999) • El paquete java.net de Java permite crear sockets TCP/IP. • Clases para sockets datagrama – DatagramSocket – DatagramPacket • Clases para sokcets stream – ServerSocket – Socket Sistemas Distribuidos 55 Félix García Carballeira (1999) Sockets datagrama Sockets stream • DatagramPacket: implementa un objeto que permite enviar o recibir paquetes: – Constructor: DatagramPackect – Métodos: getAddres, getPort, setData,... • DatagramSocket: implementa un socket que se puede utilizar para enviar o recibir datagramas. – Constructor: DatagramSocket – Métodos: send, receive, close, setSoTimetout, getSoTimetout, ... • La clase socket implementa un socket stream • La clase ServerSocket implementa un socket a utilizar en los servidores para esperar la conexiones de los clientes • Alunas clases abstractas para trabajar con streams: – OutputStream • close, flush, write – InputStream • close, read – DataOutputStream • writeByte, writeInt, writeDouble,... – DataInputStream • readBye, readInt, readDouble,... Sistemas Distribuidos 56 Félix García Carballeira (1999) Cliente-servidor con sockets streams de Java Sistemas Distribuidos 57 Félix García Carballeira (1999) Cliente-servidor con sockets streams de Java Servidor Proceso servidor Cliente S erverS ocket(puerto); ServerSocket() Proceso cliente socket(host, puerto) accept(); Socket() O uto putS tream Abrir conexión Accept() Obtener streams InputS tream Accept() Crear Thread Obtener streams Petición write() InputS tream O uto putS tream clo se() Sistemas Distribuidos clo se() 58 Félix García Carballeira (1999) read() Respuesta Sistemas Distribuidos read() write() close() close() 59 Félix García Carballeira (1999) Ejemplo M áquina A cliente Servidor (streams) import import import import M áquina B sumar(5,2) servidor 5+2 NÚCLEO Restulado = 7 NÚCLEO RED Sistemas Distribuidos 60 Félix García Carballeira (1999) java.lang.* ; java.io.* ; java.net.* ; java.util.* ; public class servidor { public static void main ( String [] args) { ServerSocket serverAddr = null; Socket sc = null; int num[] ; // petición int res; try { serverAddr = new ServerSocket(2500); } catch (Exception e){ System.err.println("Error creando socket"); } Sistemas Distribuidos 61 Servidor (streams) Félix García Carballeira (1999) Cliente (streams) while (true){ try { sc = serverAddr.accept(); // esperando conexión InputStream istream = sc.getInputStream(); ObjectInput in = new ObjectInputStream(istream); num = (int[]) in.readObject(); res = num[0] + num[1]; DataOutputStream ostream = new DataOutputStream(sc.getOutputStream()); ostream.writeInt(res); ostream.flush(); sc.close(); } catch(Exception e) { System.err.println("excepcion " + e.toString() ); e.printStackTrace() ; } } } import import import import java.lang.* ; java.io.* ; java.net.* ; java.util.* ; public class client { public static void main ( String [] args) { int res; int num[] = new int[2]; if (args.length != 1) { System.out.println("Uso: cliente <host>"); System.exit(0); } try { // se crea la conexión String host = args[0]; Socket sc = new Socket(host, 2500); // conexión } Sistemas Distribuidos 62 Félix García Carballeira (1999) Sistemas Distribuidos Cliente (streams) num[1] = 2; //prepara la petición sc.close(); System.out.println("La suma es " + res); } catch (Exception e){ System.err.println("excepcion " + e.toString() ); e.printStackTrace() ; } } } 64 import import import import java.lang.* ; java.io.* ; java.net.* ; java.util.* ; public class servidor { public static void main ( String [] args) { DatagramSocket s = null; DatagramPacket in, out; InetAddress client_addr = null; int client_port; byte brecv[] = new byte[100]; byte bsend[] = new byte[100]; int num[], res; try { s = new DatagramSocket(2500); in = new DatagramPacket(brecv, 100); // paquete para recibir la // solicitud s.writeObject(num); s.flush(); res = istream.readInt(); Sistemas Distribuidos Félix García Carballeira (1999) Servidor (datagramas) OutputStream ostream = sc.getOutputStream(); ObjectOutput s = new ObjectOutputStream(ostream); DataInputStream istream = new DataInputStream(sc.getInputStream()); num[0] = 5; 63 Félix García Carballeira (1999) Sistemas Distribuidos 65 Félix García Carballeira (1999) Servidor (datagramas) Servidor (datagramas) while (true) { s.receive(in); // esperamos a recibir los paquetes bsend = baos.toByteArray(); out = new DatagramPacket(bsend, bsend.length, client_addr, client_port); brecv = in.getData(); // obtener datos client_addr = in.getAddress(); client_port = in.getPort(); // desempaquetar los datos. ByteArrayInputStream bais = new ByteArrayInputStream(brecv) ; ObjectInputStream dis = new ObjectInputStream(bais); num = (int[])dis.readObject(); res = num[0] + num[1]; s.send(out); } } catch(Exception e) { System.err.println("excepcion " + e.toString() ); e.printStackTrace() ; } } } ByteArrayOutputStream baos = new ByteArrayOutputStream() ; DataOutputStream dos = new DataOutputStream(baos); dos.writeInt(res); Sistemas Distribuidos 66 Félix García Carballeira (1999) Sistemas Distribuidos Cliente (datagramas) import import import import 67 Félix García Carballeira (1999) Cliente (datagramas) java.lang.* ; java.io.* ; java.net.* ; java.util.* ; try { // se crea el socket del cliente s = new DatagramSocket(); // en el puerto que quiera public class client{ public static void main ( String [] args) { byte bsend[] = new byte[100]; byte brecv[] = new byte[100]; InetAddress server_addr = null; DatagramSocket s = null; DatagramPacket in = null; DatagramPacket out = null; int res; int num[] = new int[2]; // direción del servidor server_addr = InetAddress.getByName(args[0]); num[0] = 2; num[1] = 5; // empaquetar los datos. ByteArrayOutputStream baos = new ByteArrayOutputStream() ; ObjectOutputStream dos = new ObjectOutputStream(baos); dos.writeObject(num) ; if (args.length != 1) { System.out.println("Uso: cliente <host>"); System.exit(0); } Sistemas Distribuidos 68 bsend = baos.toByteArray() ; // se obtiene el buffer (datagrama) // un único envio out = new DatagramPacket(bsend, bsend.length, server_addr, 2500); s.send(out); Félix García Carballeira (1999) Sistemas Distribuidos Cliente (datagramas) 69 Félix García Carballeira (1999) Rendimiento I // se recibe el datagrama de respuesta in = new DatagramPacket(brecv, 100); s.receive(in); • Tiempo en realizar una suma utilizando datagramas y streams. Incluye todas las operaciones (creación de sockets, conexión, etc.) • Biprocesador Pentiun II 266 MHZ con Linux. // se obtiene el buffer brecv = in.getData(); // se desempaqueta ByteArrayInputStream bais = new ByteArrayInputStream(brecv) ; DataInputStream dis = new DataInputStream(bais); res = dis.readInt(); System.out.println("Datos recibidos " + res); } catch (Exception e) { System.err.println("<<<<<excepcion " + e.toString() ); e.printStackTrace() ; } UDP TCP Local 211 ms 235,6 ms Remoto 217 ms 286 ms } } Sistemas Distribuidos 70 Félix García Carballeira (1999) Sistemas Distribuidos 71 Félix García Carballeira (1999) Rendimiento II Rendimiento: sockets Unix contra Java • Tiempo en realizar únicamente el envío y la recepción de datos (sin conexiones) UDP TCP Local 6,25 ms 152 ms Remoto 4,95 ms 139 ms Sistemas Distribuidos 72 Félix García Carballeira (1999) • Streams con conexión (en ms) local 235,6 Java Sokcets Unix 2,74 • Datagramas con conexión (en ms) local 211 Java Sokcets Unix 2,2 Sistemas Distribuidos local 152 Java Sokcets Unix 0,34 Sistemas Distribuidos 74 remoto 139 0,44 remoto 4,95 0,29 Félix García Carballeira (1999) Funcionamiento de las RPC • El proceso que realiza la llamada empaqueta los argumentos en un mensaje, se los envía a otro proceso y espera el resultado • El proceso que ejecuta el procedimiento extrae los argumentos del mensaje, realiza la llamada de forma local, obtiene el resultado y se lo envía de vuelta al proceso que realizó la llamada • Objetivo: acercar la semántica de las llamadas a procedimiento convencional a un entorno distribuido (transparencia). Sistemas Distribuidos 76 Félix García Carballeira (1999) RPC • RPC (remote procedure call): llamadas a procedimiento remoto (Birrel y Nelson 1985) • Híbrido entre llamadas a procedimientos y paso de mensajes • Las RPC constituyen el núcleo de muchos sistemas distribuidos • Llegaron a su culminación con DCE (Distributed Computing Environment) • Han evolucionado hacia orientación a objetos – Invocación de métodos remotos (CORBA, RMI) • Datagramas sin conexión (en ms) local 6,25 Java Sokcets Unix 0,21 remoto 217 6,8 73 Rendimiento: sockets Unix contra Java • Streams sin conexión (en ms) remoto 286 7,35 Félix García Carballeira (1999) Sistemas Distribuidos 75 Félix García Carballeira (1999) Llamadas y mensajes en una RPC SISTEMA CLIENTE SISTEMA SERVIDO R PROCEDIM IENTOS CÓDIGO DE LA APLICACIÓN INICIO LLAMADA RESG UAR DO CLIEN TE Sistemas Distribuidos EJECUTA PROCEDIM IENTO REMOTO PREPARA 2 9 ENVÍA ENTRADA RECIBE SALIDA CONVIERTE ENTRADA RESG UAR DO SERVID OR 1 ENTRADA CONVIERTE SALIDA BIBLIOT. EJECUCIÓN RPC 5 FIN LLAMADA 6 PREPARA SALIDA 7 TRANSMITE SALIDA BIBLIOT. EJECUCIÓN RPC 8 77 RECIBE Y PASA 4 3 Félix García Carballeira (1999) Resguardos (stubs) RPC: protocolo básico • Se generan automáticamente por el software de RPC • En el cliente: – Localizan al servidor – Empaquetan los parámetros y construyen los mensajes – Envían el mensaje al servidor – Espera la recepción del mensaje y devuelven los resultados • En el servidor – Realizan tareas similares • Los resguardos son independientes de la implementación que se haga del cliente y del servidor. Sólo dependen de la interfaz. “enlaza con el servidor” servidor cliente Se registra con un servicio de nombres prepara parámetros, envía petición recibe petición Ejecuta el procedimiento envía petición Desempaqueta la respuesta Sistemas Distribuidos 78 Félix García Carballeira (1999) Problemas de diseño de las RPC • • • • Lenguaje de definición de interfaces. Generador de resguardos. Transferencia de parámetros Enlace dinámico (binding) Semántica de las RPC en presencia de fallos Sistemas Distribuidos 80 Félix García Carballeira (1999) Sistemas Distribuidos Sistemas Distribuidos 82 Félix García Carballeira (1999) Félix García Carballeira (1999) Lenguaje de definición de interfaces • Una interfaz especifica un nombre de servicio que utilizan los clientes y servidores • Nombres de procedimientos y parámetros (entrada y salida). • Los compiladores pueden diseñarse para que los clientes y servidores se escriban en lenguajes diferentes. • Tipos de RPC – Integrado con un lenguaje de programación (Cedar, Argus) – Lenguaje de definición de interfaces específico para describir las interfaces entre los clientes y los servidores (RPC de Sun y RPC de DCE) Sistemas Distribuidos Transferencia de parámetros • Una de las funciones de los resguardos es empaquetar los parámetros en un mensaje: aplanamiento (marshalling) • Problemas en la representación de los datos – Servidor y cliente pueden ejecutar en máquinas con arqutecturas distintas – XDR (external data representation) es un estándar que define la representación de tipos de datos • Problemas con los punteros – Una dirección sólo tiene sentido en un espacio de direcciones 79 81 Félix García Carballeira (1999) Enlace dinámico (Binding) • Enlace dinámico: permite localizar objetos con nombre en un sistema distribuido, en concreto, servidores que ejecutan las RPC. • Tipos de enlace: – Enlace no persistente: la conexión entre el cliente y el servidor se establece en cada RPC. – Enlace persistente: la conexión se mantiene después de la primera RPC. • Útil en aplicaciones con muchas RPC repetidas • Problemas si lo servidores cambian de lugar Sistemas Distribuidos 83 Félix García Carballeira (1999) Enlazador dinámico Semántica de las RPC en presencia de fallos • Enlazador dinámico (binder): Es el servicio que mantiene una tabla de traducciones entre nombres de servicio y direcciones. Incluye funciones para: – Registrar un nombre de servicio – Eliminar un nombre de servicio – Buscar la dirección correspondiente a un nombre de servicio • Como localizar al enlazador dinámico: – Ejecuta en una dirección fija de un computador fijo. – El sistema operativo se encarga de indicar su dirección – Difundiendo un mensaje (broadcast) cuando los procesos comienzan su ejecución. • Problemas que pueden plantear las RPC – El cliente no es capaz de localizar al servidor – Se pierde el mensaje de petición del cliente al servidor – Se pierde el mensaje de respuesta del servidor al cliente – El servidor falla después de recibir una petición – El cliente falla después de enviar una petición Sistemas Distribuidos Sistemas Distribuidos 84 Félix García Carballeira (1999) Cliente no puede localizar al servidor • • • • El servidor puede estar caído El cliente puede estar usando una versión antigua del servidor La versión ayuda a detectar accesos a copias obsoletas Cómo indicar el error al cliente – Devolviendo un código de error (-1) 85 Félix García Carballeira (1999) Pérdida de mensajes del cliente • Es la más fácil de tratar • Se activa una alarma (timeout) después de enviar el mensaje • Si no se recibe una respuesta se retransmite • No es transparente – Ejemplo: sumar(a,b) – Elevando una excepción • Necesita un lenguaje que tenga excepciones Sistemas Distribuidos 86 Félix García Carballeira (1999) Sistemas Distribuidos Pérdidas en los mensajes de respuesta 87 Félix García Carballeira (1999) Fallos en los servidores • Más difícil de tratar • Se pueden emplear alarmas y retransmisiones, pero: – ¿Se perdió la petición? – ¿Se perdió la respuesta? – ¿El servidor va lento? • Algunas operaciones pueden repetirse sin problemas (operaciones idempotentes) – Una transferencia bancaria no es idempotente • Solución con operaciones no idempotentes es descartar peticiones ya ejecutadas – Un nº de secuencia en el cliente – Un campo en el mensaje que indique si es una petición original o una retransmisión • El servidor no ha llegado a ejecutar la operación – Se podría retransmitir • El servidor ha llegado a ejecutar la operación • El cliente no puede distinguir los dos Sistemas Distribuidos Sistemas Distribuidos 88 Félix García Carballeira (1999) • ¿Qué hacer? – No garantizar nada – Semántica al menos una vez • Reintentar y garantizar que la RPC se realiza al menos una vez • No vale para operaciones no idempotentes – Semántica a lo más una vez • No reintentar, puede que no se realice ni una sola vez – Semántica de exactamente una • Sería lo deseable 89 Félix García Carballeira (1999) Fallos en los clientes Aspectos de implementación • La computación está activa pero ningún cliente espera los resultados (computación huérfana) – Gasto de ciclos de CPU – Si cliente rearranca y ejecuta de nuevo la RPC se pueden crear confusiones • Protocolos RPC – Orientados a conexión • Fiabilidad se resuelve a bajo nivel, peor rendimiento – No orientados a conexión – Uso de un protocolo estándar o un específico • Algunos utilizan TCP o UDP como protocolos básicos Sistemas Distribuidos 90 Félix García Carballeira (1999) Sistemas Distribuidos Programación con un paquete de RPC 91 Félix García Carballeira (1999) Programación con RPC DESARROLLO DE LA INTER FA Z • El programador debe proporcionar: – La definición de la interfaz (idl) FIC H ER O D E D EFINIC IÓN D E IN TE RFA Z COM PILADOR IDL • Nombres de las funciones • Parámetros que el cliente pasa al servidor • Resultados que devuelve el servidor al cliente C A BEC ER A R ESG UA R DO EN C LIE NTE – El código del cliente – El código del servidor • El compilador de idl proporciona: – El resguardo del cliente – El resguardo del servidor C OM PILAD O R C FIC H ER OS FUE NTE D EL C LIE NTE C A BE C E R A C A BE C E R A C OM PILAD O R C OB JETO R ESG UA R DO EN C LIE NTE FIC H ER OS OB JETO DE L C LIE NTE R ESG UA R DO EN SER VID OR FIC H ER OS FUE NTE D EL SER VID OR B IBLIOT. R PC B IBLIOT. R PC FIC H ER OS OB JETO DE L SER VID OR M ON TAD OR 92 Félix García Carballeira (1999) Sistemas Distribuidos OB JETO R ESG UA R DO EN SER VID OR M ON TAD OR EJEC U TA B LE D EL SER VID OR DESARROLLO EJEC U TA B LE D EL DEL C LIE NTE CLIEN TE Sistemas Distribuidos C OM PILAD O R C C OM PILAD O R C 93 DESARROLLO DEL SERVIDOR Félix García Carballeira (1999) Ejemplos de paquetes de RPC RPC de Sun • RPC de Sun (1990) utilizado en NFS • RPC del proyecto ANSA (1989) desarrollado por Architecture Project Management Ltd. (Cambridge, Inglaterra) • RPC de DCE (1990), estándar desarrollado por Open Software Foundation • Utiliza como lenguaje de definición de interfaz XDR: – Una interfaz contiene un nº de programa y un nº de versión. – Cada procedimiento específica un nombre y un nº de procedimiento – Los procedimientos sólo aceptan un parámetro. – Los parámetros de salida se devuelven mediante un único resultado – El lenguaje ofrece una notación para definir: • • • • Sistemas Distribuidos 94 Félix García Carballeira (1999) constantes definición de tipos estructuras, uniones programas Sistemas Distribuidos 95 Félix García Carballeira (1999) RPC de Sun Ejemplo • rpcgen es el compilador de interfaces que genera: – Resguardo del cliente – Resguardo del servidor y procedimiento principal del servidor. – Procedimientos para el aplanamiento (marshalling) – Fichero de cabecera (.h) con los tipos y declaración de prototipos. • Enlace dinámico – El cliente debe especificar el host donde ejecuta el servidor – El servidor se registra (nº de programa, nº de versión y nº de puerto) en el port mapper local – El cliente envía una petición al port mapper del host donde ejecuta el servidor Sistemas Distribuidos 96 Félix García Carballeira (1999) M áquina A cliente M áquina B sum ar(5,2) servidor 5+2 N Ú CLE O R estulado = 7 N Ú CLE O R ED Sistemas Distribuidos Esquema de la aplicación 97 Félix García Carballeira (1999) suma.x clien te.c A rch ivo s p ara el clien te su m a_c ln t.c struct peticion { int a; int b; }; su m a_x d r.c rep cg en A rch ivo s co m u n es su m a.x su m a.h su m a_s vc.c program SUMAR { version SUMAVER { int SUMA(peticion) = 1; } = 1; } = 99; A rch ivo s p ara el se rv id o r servid o r.c Sistemas Distribuidos 98 Félix García Carballeira (1999) Sistemas Distribuidos suma.h 99 Félix García Carballeira (1999) servidor.c #ifndef _SUMA_H_RPCGEN #define _SUMA_H_RPCGEN #include "suma.h" int *suma_1_svc(peticion *argp, struct svc_req *rqstp) { static int result; #include <rpc/rpc.h> struct peticion { int a; int b; }; result = argp->a + argp->b; return(&result); #define #define extern extern SUMAVER ((u_long)99) SUMA ((u_long)1) int * suma_1(peticion *, CLIENT *); int * suma_1_svc(peticion *, struct svc_req *); } #endif /* !_SUMA_H_RPCGEN */ Sistemas Distribuidos 100 Félix García Carballeira (1999) Sistemas Distribuidos 101 Félix García Carballeira (1999) cliente.c cliente.c #include "suma.h" /* localiza al servidor */ clnt = clnt_create(host, SUMAR, SUMAVER, "udp"); if (clnt == NULL) { clnt_pcreateerror(host); exit(1); } suma_1_arg.a = 5; suma_1_arg.b = 2; main( int argc, char* argv[] ) { CLIENT *clnt; int *res; peticion suma_1_arg; char *host; if(argc < 2) { printf("usage: %s server_host\n", argv[0]); exit(1); } host = argv[1]; res = suma_1(&suma_1_arg, clnt); if (res == NULL) { clnt_perror(clnt, "call failed:"); } printf("La suma es %d\n", *res); clnt_destroy( clnt ); } Sistemas Distribuidos 102 Félix García Carballeira (1999) Sistemas Distribuidos 103 Félix García Carballeira (1999) Rendimiento I Rendimiento II • Rendimiento de sockets y RPC. Incluye todo el tiempo. Enlace, conexión en sockets, etc. • Sólo incluye el tiempo en realiza la operación. Envío/recepción en sockets. Ejecución de suma_l en RPC Sockets UDP RPC TCP UDP Sockets TCP RPC UDP TCP UDP 0,34 ms 0,28 ms Local 2,2 ms 2,74 ms 4,61 ms 5,43 ms Local 0,21 ms Remoto 6,8 ms 7,35 ms 8,21 ms 8,77 ms Remoto 0,29 ms 0,44 ms Sistemas Distribuidos 104 Félix García Carballeira (1999) Sistemas Distribuidos Entornos orientados a objetos 0,36 ms 105 TCP 0,43 ms 0,55 ms Félix García Carballeira (1999) Modelo de objetos • Tendencia actual hacia sistemas compuestos por un conjunto de objetos que interactúan entre sí. – Un programa solicita servicios invocando los métodos que ofrece un objeto – La invocación de métodos se ve como un paso de mensajes • ANSA (1989-1991) fue el primer proyecto que intentó desarrollar una tecnología para modelizar sistemas distribuidos complejos – Utilizaba un diseño orientado a objetos • Estándares: – RMI: invocación de métodos remotos de Java – CORBA: expande DCE con servicios orientados a objetos – DCOM: versión CORBA de Microsoft Sistemas Distribuidos 106 Félix García Carballeira (1999) Sistemas Distribuidos 107 Félix García Carballeira (1999) Modelo de objetos en sistemas distribuidos M áquina A M áquina B Sistemas Distribuidos M áquina C 108 Félix García Carballeira (1999) Arquitectura de RMI Invocación de métodos remotos en Java • RMI (Remote method invocation) • El soporte para RMI en Java está basado en las interfaces y clases definidas en los paquetes java.rmi y java.rmi.server • RMI ofrece – Mecanismos para crear servidores y objetos cuyos métodos se puedan invocar remotamente – Mecanismos que permiten a los clientes localizar los objetos remotos Sistemas Distribuidos 109 Félix García Carballeira (1999) ¿Cómo escribir aplicaciones con RMI? 1 • Nivel de transporte: se encarga de las comunicaciones y de establecer las conexiones necesarias • Nivel de gestión de referencias remotas: trata los aspectos relacionados con el comportamiento esperado de las referencias remotas (mecanismos de recuperación, etc.) • Nivel de resguardo/esqueleto (proxy/skeleton) que se encarga del aplanamiento de los parámetros – proxy: resguardo local. Cuando un cliente realiza una invocación remota, en realidad hace una invocación de un método del resguardo local. – Esqueleto (skeleton): recibe las peticiones de los clientes, realiza la invocación del método y devuelve los resultados. D efinición de la interfaz rem o ta 2 Im plem entación d e la interfaz rem o ta (.java) 3 javac (.class) 4 8 C liente usa Esqueleto (.class) Esqueleto (.class) 5 (.java) 9 A rrancar R M IRegistry javac 6 C rear los ob jeto s (.class) 10 7 Ejectuar C liente R eg istrar los ob jeto s SERVID OR C LIENT E Sistemas Distribuidos 110 Félix García Carballeira (1999) Sistemas Distribuidos Invocación remota 0iTXLQD 111 Félix García Carballeira (1999) Ejemplo 0iTXLQD UPLUHJLVWU\ 50, &OLHQWH Servido r (.class) rm ic 50, 50, 6HUYLGRU Sistemas Distribuidos 112 Félix García Carballeira (1999) Sistemas Distribuidos 113 Félix García Carballeira (1999) Modelización de la interfaz remota (Sumador) public interface Sumador extends java.rmi.Remote { public int sumar(int a, int b) throws java.rmi.RemoteException; } Sistemas Distribuidos 114 Félix García Carballeira (1999) Clase que implementa la interfaz (SumadorImpl) import java.rmi.*; import java.rmi.server.UnicastRemoteObject; public class SumadorImpl extends UnicastRemoteObject implements Sumador { public SumadorImpl(String name) throws RemoteException { super(); try { System.out.println("Rebind Object " + name); Naming.rebind(name, this); } catch (Exception e){ System.out.println("Exception: " + e.getMessage()); e.printStackTrace(); } } public int sumar (int a, int b) throws RemoteException { return a + b; } } Sistemas Distribuidos Código del servidor (SumadorServer) import java.rmi.*; import java.rmi.server.*; public class SumadorServer { public static void main (String args[]) { int res = 0; System.out.println("Setting Security Manager"); if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } System.out.println("Security Manager Installed"); try { SumadorImpl misuma = new SumadorImpl("MiSumador"); } catch(Exception e) { System.err.println("System exception" + e); } } } Sistemas Distribuidos 116 Félix García Carballeira (1999) • Cualquier programa que quiera instanciar un objeto de esta clase debe realizar el registro con el servicio de nombrado de la siguiente forma: Sumador misuma = (Sumador)Naming.lookup("rmi://" + args[0] + "/" +"MiSumador"); • Antes de arrancar el cliente y el servidor, se debe arrancar el programa rmiregistry en el servidor para el servicio de nombres Sistemas Distribuidos 117 Félix García Carballeira (1999) ¿Cómo se ejecuta? • Compilación public class SumadorClient { public static void main(String args[]){ int res = 0; try { System.out.println("Buscando Objeto "); Sumador misuma = (Sumador)Naming.lookup("rmi://" + args[0] + "/" +"MiSumador"); res = misuma.sumar(5, 2); System.out.println("5 + 2 = " + res); } catch(Exception e){ System.err.println(" System exception"); } System.exit(0); } } Sistemas Distribuidos Félix García Carballeira (1999) Registro del servicio Código en el cliente (SumadorCliente) import java.rmi.registry.*; import java.rmi.server.*; 115 118 Félix García Carballeira (1999) javac Sumador.java javac SumadorImpl.java javac SumadorClient.java javac Sumador Server.java • Generación de los esqueletos rmic SumadorImpl • Ejecución del programa de registro de RMI rmiregistry • Ejecución del servidor java SumadorServer • Ejecución del cliente java SumadorCliente <host-del-servidor> Sistemas Distribuidos 119 Félix García Carballeira (1999) Rendimiento Comparando el rendimiento • Tiempo en realizar una suma con RMI • Biprocesador Pentiun II 266 MHZ con Linux. Con lookup Sin lookup Local 684 ms 17,5 ms Remoto 652 ms 15 ms Sistemas Distribuidos 120 Sockes Unix TCP Sockets Unix UDP Sockets Java TCP Sockets Java UDP RPC-TCP RPC-UDP RMI-Java Félix García Carballeira (1999) Sistemas Distribuidos CORBA 122 Félix García Carballeira (1999) C liente Cliente Invocación dinám ica d e R epositorio de interfaces Interfaz ORB Adaptador esqueletos Esqueletos Invocación estáticos dinám ica d e de objetos esqueletos R epositorio de im plm entación Object Request Broker Sistemas Distribuidos 124 Remoto Sin conexión 0,44 0,21 139 4,95 0,55 0,34 15 Félix García Carballeira (1999) C liente S ervidor O bjeto X O bjeto Y Llam ar a f() Invoca el método f del objetoX Código Datos f f O bject R equest Broker M ecanism o de R PC Sistemas Distribuidos Invoca el método f del objeto Y 123 Félix García Carballeira (1999) Componentes de CORBA Im plem entación de objetos Resguardo del cliente Local Sin conexión 0,34 0,29 152 6,25 0,43 0,44 17,5 121 S ervidor Estructura de CORBA Invocación dinám ica Remoto Con conexión 7,35 6,8 286 217 8,77 8,21 652 RPC contra ORB • CORBA (common object request broker architecure) • Estándar desarrollado por ONG (Object management group) a principios de los 90 • El modelo de referencia CORBA incluye el conjunto de componentes y servicios que debería ofrecer una plataforma distribuida • CORBA especifica: – Una arquitectura de componentes cliente-servidor cooperantes. – Un conjunto de funcionalidades – Un lenguaje de descripción de interfaces para definir objetos • La interfaz es independiente del lenguaje y se puede utilizar desde diferentes lenguajes de programación (C, C++, Java, ...) Sistemas Distribuidos Local Con conexión 2,74 2,2 235,6 211 5,43 4,61 684 Félix García Carballeira (1999) • Lenguaje de definición de interfaces (IDL). Especifica objetos, métodos, excepciones, herencia. • Compilador del IDL. Genera los resguardos y esqueletos. • ORB (Object Request Broker). Se ocupa de las comunicaciones, enlaces (bindings) y gestionar la invocación de métodos. Toda implementación CORBA debe ofrecer al menos un ORB. • Resguardos del cliente. Define la forma en la que los clientes invocan los servicios correspondientes del servidor. Generado por el compilador de IDL. • Interfaz de invocación dinámica. Permite a los clientes descubrir e invocar nuevas interfaces en tiempo de ejecución. No necesitan un resguardo precompilado. • Repositorio de interfaces. Base de datos distribuida que contiene las versiones de las interfaces definidas. Sistemas Distribuidos 125 Félix García Carballeira (1999) Componentes de CORBA Ejemplo • El interfaz ORB. API de los servicios que ofrece ORB (pe: convertir referencias a objetos en cadenas de caracteres y viceversa) • Adaptador de objetos básico. Componente de ORB que soporta la activación de objetos en los servidores. • Esqueletos estáticos. Proporcionan las interfaces estáticas para cada servicio exportado por un servidor. Generados por el compilador de IDL. • Interfaz para la invocación dinámica de esqueletos. Interfaz para manejar la invocación de métodos dinámicos (no están en los esqueletos). • Repositorio de implementación. Repositorio con las clases que soporta un servidor. Sistemas Distribuidos 126 Félix García Carballeira (1999) M áquina A NÚCLEO NÚCLEO RED Sistemas Distribuidos 127 Félix García Carballeira (1999) sumar.idl // sumar.idl Repositorio de interfaces module Sumador { interface sumar { long suma(in long a, in long b); }; }; Precompilar Esqueleto del servidor Resguardo del cliente Implementar el cliente Restulado = 7 2 Escribir la interfaz (IDL) 3 8 servidor 5+2 Programación con invocación estática 1 M áquina B sumar(5,2) cliente 4 Adaptador de objetos Implementar el servidor 6 9 Compilar 5 Compilar 7 Re pos itorio de im ple m e ntac ión Cliente Sistemas Distribuidos Servidor 128 Félix García Carballeira (1999) Sistemas Distribuidos Servidor 130 Félix García Carballeira (1999) Implementación del servidor // SumarServer.java: The Sumador Server main program class SumarServer { static public void main(String[] args) { try { // Initialize the ORB org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args, null); // Initialize the BOA org.omg.CORBA.BOA boa = orb.BOA_init(); // Create the Sumador object SumarImpl sumador = new SumarImpl("My Sumador"); // Export to the ORB the newly created object boa.obj_is_ready(sumador); // Ready to service requests boa.impl_is_ready(); } catch(org.omg.CORBA.SystemException e) { System.err.println(e);} } } Sistemas Distribuidos 129 Félix García Carballeira (1999) // SumarImpl.java: The Sumador Implementation class SumarImpl extends Sumador._sumarImplBase { private int sum; // Constructors SumarImpl(String name) { super(name); System.out.println("Creado objeto Sumador "); } public int suma(int a, int b) { int res = a+b; return res; } } Sistemas Distribuidos 131 Félix García Carballeira (1999) Cliente Comunicación en aplicaciones paralelas // SumarClient.java Static Client, VisiBroker for Java class SumarClient { public static void main(String args[]) { try { // Initialize the ORB org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args, null); // Bind to the Sumador Object Sumador.sumar sumador = Sumador.sumarHelper.bind(orb, "My Sumador"); long resultado = sumador.suma(5, 2); System.out.println("La Suma de 5 y 2 es = " + resultado); } catch(org.omg.CORBA.SystemException e) { System.err.println("System Exception"); System.err.println(e); } } • Aplicaciones paralelas en multicomputadores y entornos distribuidos: – La aplicación se divide en procesos que ejecutan en procesadores diferentes – La coordinación y la comunicación se realiza mediante paso de mensajes • En aplicaciones paralelas es necesario utilizar mecanismos de comunicación más fiables y más eficientes. • Ejemplos: – PVM (parallel virtual machine) – MPI (message passing interface) } Sistemas Distribuidos 132 Félix García Carballeira (1999) Sistemas Distribuidos MPI 133 Félix García Carballeira (1999) MPI-2 • MPI es una interfaz estándar para el desarrollo de aplicaciones paralelas que utilizan paso de mensajes. • Desarrollado en 1994 por el Forum de MPI – http://www.mpi-forum.org • Características: – Portabilidad de las aplicaciones • Interfaz para C y Fortran. • Aparece en 1997 • Incluye aclaraciones y correcciones al estándar básico • Características: – Creación y gestión de procesos – Operaciones colectivas extendidas – Interfaz de E/S paralela (MPI-IO) – Interfaces para lenguajes adicionales – Eficiencia – Funcionalidad Sistemas Distribuidos 134 Félix García Carballeira (1999) Sistemas Distribuidos Características • • • • Comunicación punto a punto (bloqueanntes, no bloqueantes). Operaciones asíncronas Operaciones colectivas Grupos de procesos – Los procesos se identifican mediante un nº entero (0,...) – Un grupo lista los identificadores de los procesos. – Un comunicador contiene al grupo e información sobre dicho grupo. Semántica multithread Tipos de datos (MPI_INT, MPI_FLOAT, ..) Capacidad para definir tipos de datos derivados. Soporte para redes heterogéneas Sistemas Distribuidos 136 Félix García Carballeira (1999) Algunas funciones • MPI_COMM_WORLD: comunicador por defecto que incluye a todos los procesos • • • • 135 Félix García Carballeira (1999) • int MPI_Init(int *argc, char ***argv); – Inicia la biblioteca de MPI. • int MPI_Finalize(void); – Finaliza un programa MPI, liberando los recursos. No interrumpe las comunicaciones pendientes. • int MPI_Abort(MPI_Comm comm, int errcode); – Aborta la ejecución • int MPI_Comm_rank(MPI_Comm comm, int *rank); – Devuelve el identificador asociado a un proceso dentro del grupo de procesos asociado al comunicador • int MPI_Commsize(MPI_Comm comm, int *size); – Devuelve el nº de procesos que hay en el grupo asociado al comunicador Sistemas Distribuidos 137 Félix García Carballeira (1999) Operaciones punto a punto • int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm); – Envía un mensaje (buf) de count elementos de tipo datatype. – El proceso receptor es dest dentro del grupo de procesos asociado al comunicador (comm) – El mensaje lleva asociado una etiqueta (tag). • int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status); – Recibe un mensaje (en buf) de count elementos de tipo datatype del proceso emisor (source) dentro del grupo de procesos asociado al comunicador (comm) – Se recibe el mensaje del proceso source con etiqueta (tag). Sistemas Distribuidos 138 Félix García Carballeira (1999) Ejemplo main(int argc, char **argv) { int me, size; int SOME_TAG = 0; char buf[1024]; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &me); MPI_Comm_size(MPI_COMM_WORLD, &size); if (me == 0) MPI_Send(buf, 1024, MPI_CHAR, 1, SOME_TAG, MPI_COMM_WORLD); else MPI_Recv(buf, 1024, MPI_CHAR, 0, SOME_TAG, MPI_COMM_WORLD); MPI_Finalize(); } Sistemas Distribuidos Ejemplo de operaciones colectivas 139 Félix García Carballeira (1999) Operaciones colectivas • int MPI_Barrier(MPI_Comm comm); – Bloquea la ejecución del proceso hasta que todos los miembros del grupo la ejecuten. • int MPI_Bcast(void *buf, int count, MPI_Datatype datatype, int root, MPI_Comm comm); – El proceso root envía un mensaje a todos los procesos del grupo. • int MPI_Scatter(void *sendbuf, int sendcont, MPI_Datatype sendtype, void *recbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm); – Operación scatter del proceso root al resto, incluido él. Sistemas Distribuidos 140 Félix García Carballeira (1999) Operaciones colectivas Sistemas Distribuidos • int MPI_Reduce(void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op po, int root, MPI_Comm comm); – El proceso root combina la información de todos los procesos del grupo usando la operación op y deja el resultado en el buffer de recepción. – Operaciones: MPI_MAX, MPI_SUM,... – Posibilidad de definir nuevas operaciones. 142 Félix García Carballeira (1999) Implementación de operaciones colectivas Félix García Carballeira (1999) ... . • int MPI_Gather(void *sendbuf, int sendcont, MPI_Datatype sendtype, void *recbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm); – Operación gather del proceso de todos los procesos del grupo al root (incluido él). Sistemas Distribuidos 141 Lista Cadena Árbo l b inario Sistemas Distribuidos Árbo l b inom ial 143 Félix García Carballeira (1999) Ejemplo Esqueleto del programa • Programa que calcula el nº π #include “mpi.h” #include <stdio.h> int main(int argc, char **argv) { while (1) { /* el proceso maestro pide al usuario el número de intervalos */ /* El proceso maestro envía a todos el número de intervalos */ MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); /* procesos esclavos calcular el área de los diferentes subintervalos */ /* El proceso raiz recoge los resultados y realiza la suma */ MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); } MPI_Finalize(); } Sistemas Distribuidos 144 Félix García Carballeira (1999) Sistemas Distribuidos 145 Ejemplo Ejemplo #include “mpi.h” #include <stdio.h> int main(int argc, char **argv) { int n, myid, numprocesos, i; double mypi, pi, h, sum, x; else { h = 1.0/(double)n; sum = 0.0; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &numprocesos); MPI_Comm_rank(MPI_COMM_WORLD, &myid); while(1) { if (myid == 0) { printf(“Introduzca el nº de intervalos: “); scanf(“%d”, &n); } MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); if (n==0) break; Sistemas Distribuidos 146 Félix García Carballeira (1999) for(i = myid + 1; i <= n; i += numprocesos) { x = h * ((double)i - 0.5); sum += (4.0 / (1.0) + x*x)); } mypi = h * sum; MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); if (myid == 0) { printf(“PI es aproximadamente %f\n”, pi); } } MPI_Finalize(); } Sistemas Distribuidos Algunas implementaciones • LAM, implementación de libre distribución, disponible para redes de estaciones de trabajo • MPICH, (libre distribución), disponible para estaciones de trabajo y SP2. Incluye MPI-IO (ROMIO). • MPI de IBM (comercial). IBM utiliza esta implementación como lenguaje nativo para la programación de SP2. Implementación con semántica thread-safe. • MiMPI, desarrollada en el DATSI (UPM) y está disponible para redes de estaciones de trabajo y SP2. Sistemas Distribuidos Félix García Carballeira (1999) 148 Félix García Carballeira (1999) 147 Félix García Carballeira (1999) MiMPI • Implementación multithread de MPI • Objetivos de MiMPI – Adecuada para el desarrollo de programas multithread (thread-safe) – Empleo de threads en su implementación • Facilita la implementación de operaciones asíoncronas • Mejora el rendimiento de operaciones colectivas – – – – Uso de compresión Portabilidad entre plataformas Ofrecer una interfaz gráfica para arrancar programas MPI Herramientas de depuración y visualización Sistemas Distribuidos 149 Félix García Carballeira (1999) Arquitectura de MiMPI Operaciones colectivas en MiMPI M P I_B cast() M P I_B cast() P VM MPI 2 th reads send N threads send receive receive XMP PO SIX M P I_B cast() M P I_B cast() M P I_B cast() 2 th reads 2 th reads M P I_B cast() TCP/IP M P I_B cast() 2 th reads Sistemas Distribuidos 150 Félix García Carballeira (1999) Sistemas Distribuidos 151 Implementación actual Félix García Carballeira (1999) Evaluación • MiMPI implementa sólo un subconjunto de todas las funciones de MPI • Primer prototipo de una interfaz gráfica para el arranque de programas que utilizan MiMPI • Plataformas: – Redes de estaciones de trabajo (Linux, Unix) – IBM SP2 3 3 4 5 3 3 4 scatter ping-pong 3 3 3 3 4 5 3 3 broadcast Sistemas Distribuidos 152 Félix García Carballeira (1999) Sistemas Distribuidos Rendimiento de MiMPI Ping-ping (SP2) 5 4 5 gather 153 Félix García Carballeira (1999) ping-pong multithread 60 MiMPI MPICH IBM's MPI Throughput (MB/s) 50 3URFHVV 3URFHVV 3LQJSRQJ 40 7KUHDGV 30 7KUHDGV 20 10 51 2 1K B 2K B 4K B 8K B 16 KB 32 KB 64 KB 12 8K B 25 6K B 51 2K B 1M B 25 6 64 12 8 8 32 4 16 2 1 0 Message length (bytes) Sistemas Distribuidos 154 Félix García Carballeira (1999) Sistemas Distribuidos 155 Félix García Carballeira (1999) Rendimiento del ping-pong multithread (SP2) Broadcast (SP2) 70 1 thread 60 Broadcast 2 threads 4 threads Throughput (MB/s) 50 80 MiMPI MPICH IBM's MPI 70 8 threads 60 Throughput (MB/s) 16 threads 40 30 20 50 40 30 20 10 10 8K B 16 KB 32 KB 64 KB 12 8K B 25 6K B 51 2K B 1M B 4K B 2K B 51 2 1K B 64 25 6 12 8 32 8 16 2 1 4 0 64 12 8 25 6 51 2 1K B 2K B 4K B 8K B 16 KB 32 KB 64 KB 12 8K B 25 6K B 51 2K B 1M B 8 32 4 16 2 1 0 Message length (bytes) Message length (bytes) Sistemas Distribuidos 156 Félix García Carballeira (1999) Sistemas Distribuidos 157 Gather Gather (SP2) Scatter 45 MiMPI MPICH IBM's MPI MiMPI MPICH IBM's MPI 40 35 Throughput (MB/s) Throughput (MB/s) Scatter Scatter (SP2) Gather 30 25 Félix García Carballeira (1999) 20 15 10 30 25 20 15 10 5 5 0 1 2 4 8 16 32 64 128 256 512 1KB 2KB 4KB 0 8KB 16KB 32KB 64KB 1 Message length (bytes) Sistemas Distribuidos 158 2 4 8 16 32 64 128 256 512 1KB 2KB 4KB 8KB 16KB 32KB 64KB Message length (bytes) Félix García Carballeira (1999) Sistemas Distribuidos Evaluación en estaciones con Linux (ping-pong) 159 Félix García Carballeira (1999) Trabajo futuro 16 12 Throughput (MB/s) • Finalizar la implementación de todas las primitivas de MPI • Optimizar MiMPI para reducir la latencia de las operaciones para mensajes de tamaño pequeño • Incorporar técnicas de compresión • Incorporar las características de MPI-2 (MPI-IO) • Realizar una evaluación más exhaustiva de MiMPI • Trabajar en herramientas de depuración, visualización y gestión del entorno de MiMPI MiMPI MiMPI lzrw3 MiMPI zlib/bs MiMPI zlib/bc MPICH 14 10 8 6 4 2 0 256 512 1K 2K 4K 8 K 16 K 32 K 64 K 128 K 256 K 512 K 1M 2M 4M 8M Message size Sistemas Distribuidos 160 Félix García Carballeira (1999) Sistemas Distribuidos 161 Félix García Carballeira (1999) Comunicación de grupos Grupos de procesos • Un grupo es un conjunto de procesos que actúan juntos • Mensaje multicast: mensaje enviado por un proceso a los miembros de un grupo de procesos. • Los mensajes multicast son útiles para construir sistemas distribuidos con las siguientes características: – Tolerancia a fallos basado en servidores replicados – Localizar objetos en servicios distribuidos – Mejor rendimiento mediante datos replicados – Actualización múltiple a un grupo de procesos (news) Sistemas Distribuidos 162 Félix García Carballeira (1999) CERR AD O AB IER TO DE CO M PAÑ ER O S JERÁRQ U ICO Sistemas Distribuidos Grupos de procesos 163 Félix García Carballeira (1999) Pertenencia a un grupo • Grupos cerrados se pueden usar para procesamiento paralelo • Grupos abiertos se pueden utilizar para servidores replicados • En los grupos de compañeros las decisiones se toman colectivamente • En los jerárquicos hay un coordinador y los demás son trabajadores (punto único de fallo) • Se necesita un método para crear, destruir, incorporarse o dejar un grupo de procesos • Una posible solución es emplear un servidor de grupos – Con una base de datos con el estado actual de los grupos – Un punto único de fallo Sistemas Distribuidos Sistemas Distribuidos 164 Félix García Carballeira (1999) Propiedades de la comunicación en grupos • Multicast atómico: – Un mensaje transmitido a un grupo es recibido por todos los procesos del grupo o por ninguno. • Multicast fiable: – Método que intenta entregar un mensaje a todos los miembros pero sin garantía de conseguirlo • Multicast totalmente ordenado: – Cuando varios mensajes se transmiten a un grupo todos los mensajes alcanzan a los miembros del grupo en el mismo orden. Sistemas Distribuidos 166 Félix García Carballeira (1999) 165 Félix García Carballeira (1999) Ejemplo de multicast sin ordenación P1 P2 P3 P4 A B A B Tiem po Sistemas Distribuidos 167 Félix García Carballeira (1999) Trabajos prácticos Trabajos prácticos • Desarrollo de un programa ping-pong entre dos procesos utilizando TCP y UPD con sockets UNIX y de Java. Medir la latencia y el ancho de banda para diferentes tamaños de mensajes. Comparar el rendimiento local con el remoto. Analizar las posibles pérdidas de paquetes. • Implementar un envío multicast con TCP y UDP y analizar la latencia del envío, el ancho de banda y la fiabilidad del envío. Obtener los resultados en función del tamaño del mensajes y el número de procesos. Comparar el rendimiento local con el remoto. Sistemas Distribuidos 168 Félix García Carballeira (1999) • Implementar un esquema cliente/servidor para transferir un fichero en bloques. Considerar diferentes implementaciones: – Colas de mensajes POSIX – Sockets TCP y UDP (UNIX, Java) – RPC – CORBA Analizar: 1) El tiempo de desarrollo y la facilidad del mismo 2) El rendimiento de la transferencia (local, remoto) Sistemas Distribuidos 169 Félix García Carballeira (1999)