¡Recomienda este blog!

viernes, 2 de diciembre de 2011

Aplicaciones distribuidas mediante RPC

Para la implementación  de aplicaciones  distribuidas  basadas en RPC podemos emplear la librería RPC, que  se facilita en las distribuciones Linux. Esta librería nos ofrece una serie de servicios para la invocación de procedimientos remotos. Estos servicios son relativamente complejos  de manejar,  por lo que resulta  conveniente  utilizar  un compilador de protocolos RPC, que nos resolverá la mayor parte de los problemas técnicos, y solo nos quedara la tarea de escribir el código de los procedimientos del servidor, y el código del cliente. Podemos emplear el comando rpcgen , que toma como entrada un fichero con extensión ’x’, en el cual se especifica el interfaz de las funciones que proporciona el servidor, y su número de versión. Una vez procesado dicho fichero con rpcgen  se obtienen varios ficheros en C, que serán utilizados para compilar el código del cliente y el código del servidor. La sintaxis requerida por los ficheros que rpcgen procesa es una extensión muy simple del lenguaje XDR, que es muy parecida a la sintaxis de C.

Primer Ejemplo 

Para comprender el  funcionamiento  de rpcgen  implementaremos  un sencillo  servidor, que facilita dos versiones de varios servicios simples, como sumar, restar y multiplicar números enteros. Lo primero que hemos de hacer es preparar el fichero fuente de rpcgen , que en este caso puede ser el siguiente:

program calcular{

    version UNO{

        int sumar(int a, int b) = 1;
        int restar(int a, int b) = 2;
    } = 1;

    version DOS{
        int sumar(int a, int b) = 1;
        int restar(int a, int b) = 2;
        int multiplicar(int a, int b) = 3;
    } = 2;
    
} = 9999999; /* Num Aleatorio*/

Como vemos, el programa (servicio) se identifica con un número (9999999), y este puede ofrecer varias versiones del citado servicio, para cada una de ellas se pueden especificar una serie de funciones, que han de identificarse también numéricamente. Naturalmente, los números de los programas deben ser coordinados por parte de un servidor central. Estas especificaciones admiten también declaraciones de estructuras, uniones y enumeraciones, e incluso declaraciones de arrays de longitud variable, aunque en este caso no se necesita ninguna de estas construcciones.

Una vez confeccionado el programa se ejecuta rpcgen , con la opción -N, obteniéndose los ficheros siguientes:

calcular.h: Cabecera para cliente y servidor calcular svc.c: Archivo suplementario de servidor calcular clnt.c: Archivo suplementario de cliente calcular xdr.c: Archivo común (conversión de tipos)
  
rpcgen  admite una serie de opciones, de las cuales mostramos algunas:

-a Se genera código de muestra para el cliente y el servidor, así como un makefile para facilitar la compilación.
-Sc  Se genera  código  de muestra,  para saber  la forma de emplear  los  procedimientos remotos por parte del cliente.

-Ss  Se genera código de muestra, para saber la forma de implementar los procedimientos del servidor.

-C Genera código en ANSI C (opción por defecto).
-k Genera código en K&R C.
-N Permite definir funciones con varios argumentos, pues por defecto solo se permite un argumento por función (un puntero normalmente). Suele emplearse esta opción, que no se toma por defecto para mantener la compatibilidad con las versiones anteriores.
  
El cliente debe utilizar la función clnt create  para conectarse al servidor. Esta función tiene el prototipo siguiente:

 CLIENT  *clnt create(char *host, long prog, long vers, char *proto)
  
donde host  es el nombre de la maquina donde  se está ejecutando el servidor, prog  y vers indican el número del programa y número concreto de versión que se pretende utilizar. Finalmente, proto indica el tipo de protocolo de transporte (tcp o udp) que se pretende utilizar. La función devuelve un asa, que debe ser empleada para invocar a las funciones remotas.
Cuando el cliente ya no necesite los servicios remotos debe liberar el asa  mediante la rutina clnt destroy :

 clnt destroy(CLIENT *clnt)
  
Para invocar estas rutinas hay que incluir el fichero de cabecera rpc.h , aunque el fichero cabecera generado por rpcgen  ya lo hace. Veamos el código del cliente y el código del servidor en nuestro ejemplo:

#include "calcular.h"

//
// Autor: Iván Durango. Cliente calcular.
//

int main(int argc, char *argv[]){
 char *host;
 CLIENT *sv;
 int *res;
 host = argv[1];
 // Creamos la instancia a la conexión 
 sv = clnt_create(host, calcular, UNO, "tcp");
 if (sv == NULL) exit(1);
 
 // LLamamos al método del servidor.
 res = sumar_1(5, 2, sv);
 
 // Imprimidos el resultado.
 printf("res = %d\n", *res);
 
 // Rompemos la conexión con el servidor.
 clnt_destroy(sv);
}

/* Codigo del servidor, fichero server.c: */
#include "calcular.h"

//
// Autor: Iván Durango. Servidor calcular.
//

// A continuacíon se muestran los métodos que serán llamados por los clientes.
int *sumar_1_svc(int a, int b, struct svc_req *rqstp){
    static int r;
    r=a+b;
    return &r;
}

int *restar_1_svc(int a, int b, struct svc_req *rqstp){
    static int r;
    r=a-b;
    return &r;
}
int *sumar_2_svc(int a, int b, struct svc_req *rqstp){
    static int r;
    r=a+b;
    return &r;
}

int *restar_2_svc(int a, int b, struct svc_req *rqstp){
    static int r;
    r=a-b;
    return &r;
}

int *multiplicar_2_svc(int a, int b, struct svc_req *rqstp){
    static int r;
    r=a*b;
    return &r;
}

Por tanto, en la máquina del cliente se compila:

$ cc -c client.c
$ cc -c calcular_xdr.c
$ cc -c calcular_clnt.c
$ cc client.o calcular_xdr.o calcular_clnt.o -o client.exe

Y en el servidor:

$ cc -c server.c
$ cc -c calcular_xdr.c
$ cc -c calcular_svc.c
$ cc server.o calcular_xdr.o calcular_svc.o -o server.exe

Si los ejecutamos en la misma maquina podemos poner:

$ server.exe & client.exe localhost

Si el programa del servidor se estuviera ejecutando en la maquina est193.info-ab.uclm.es
bastaría con ejecutar el cliente de la forma:

$ client.exe hostServer

Segundo Ejemplo

Se pretende implementar una aplicación distribuida basada en RPC para leer el contenido de un fichero remoto. El servidor facilita un servicio leer , que nos permite leer un cierto número  de bytes  de un cierto  fichero.  Dada la problemática  de los  punteros  en RPC, no podemos pasar  la dirección  del  buffer  del  cliente  como parámetro  y esperar  que el servidor rellene su contenido. Asimismo, para pasar el nombre del fichero tendremos que utilizar el tipo string  soportado por rpcgen . Para obtener el resultado hemos de preparar una estructura con dos campos,  uno para indicar si pudo leerse la información, y otro para obtener  los  datos  pedidos.  El campo error  será  0 si  no pudieron  leerse  datos,  y será positivo si pudieron leerse datos (en este caso el número de datos leídos). Así, la especificación que hemos de pasar a rcpgen  podría ser la siguiente:

/* Fichero lectura.x */
struct RESULTADO{
unsigned short error;
char buffer[255];
};
program lectura{
version UNO{
RESULTADO leer(string pathname, unsigned long offset, unsigned short numbytes) = 1;
} = 1;
} = 9999988; /* Num Aleatorio */

El servidor debe abrir el fichero en cada petición de lectura, y leer num bytes a partir del offset indicado (previa comprobación de que numbytes es menor o igual que 255), devolviendo esa información en el campo buffer . En caso de error o de no haber podido leer ningún dato se devolverá 0 en el campo error del resultado.

Cliente de lectura:

#include "lectura.h"
#include 
#include 
#include 
#include 

//
// Autor: Iván Durango. Cliente Lectura.
//

void lectura_1(char *host)
{
 /*INICIALIZAMOS VARIABLES*/
 int i;
 CLIENT *clnt;
 RESULTADO  *r;
 char pathname[25];
 // Espacion de antes de los caractares.
 unsigned long offset;
 // Cantidad e bytes que vamos a leer.
   unsigned long numbytes;
#ifndef DEBUG
 clnt = clnt_create (host, lectura, UNO, "udp");/*Creamos el cliente*/
 if (clnt == NULL) {
  clnt_pcreateerror (host);
  exit (1);
 }
#endif /* DEBUG */
 // Recupero la dirección del archivo a leer.
 strcpy(pathname,"prueba.txt");

 // Empezaremos a leer desde 0.
 offset = 0L;

 // Cantidad de bytes que leeremos
 numbytes = 20L;

 r = leer_1(pathname,offset,numbytes, clnt);/*Invocamos el metodo*/

 if (r == (RESULTADO *) NULL) {
  clnt_perror (clnt, "call failed");
 }

#ifndef DEBUG
 else{
  // No se ha leido nada.
  if(r->error == 0){
  printf("No se han leido datos\n");
  }else
   for(i=0;ibuffer[i]);
   }
  printf("\n");
 }
 clnt_destroy (clnt);/*Elimina al cliente*/
#endif  /* DEBUG */
}

// Función principal del programa.
int main (int argc, char *argv[]){

 // Variable que contendrá el host
 char *host;
 
 // Por si ocurre algún error
 if (argc < 2) {
  printf ("usage: %s server_host\n",argv[0]);
  exit (1);
 }
 host = argv[1];

 // LLamamos a la función encargada de llamar al servidor. 
        lectura_1(host); 

return 0;
}

Servidor de lectura:

#include "lectura.h"
#include 
#include 
#include 
#include 

//
// Autor: Iván Durango, Servidor de lectura.
//

RESULTADO *leer_1_svc(char *pathname, unsigned long offset, unsigned short numbytes,  struct svc_req *rqstp){
 static RESULTADO  result;
 int fd;
 off_t o;
 int s;
 
 // Si tengo más de 255, entonces retorno error.
 if(numbytes > 255){
  result.error=0; 
 }else{
  // Realizamos la apertura del archivo para solo lectura.
  fd = open(pathname,O_RDONLY);
  
  // Si no se abre correctamente retornamos error.
  if( fd < 0 ){

   result.error=0;

  }else{
   
   // Nos colocamos donde dice el offset, siempre será al principio.
   o = lseek(fd,offset,SEEK_SET);

   // Controlamos si falla lseek.
   if ( o == (off_t)-1 ) 

    result.error=0;

   else{
    // Leemos el número de bytes indicado.
    s = read(fd, result.buffer, numbytes);
    
    //Controlamos posibles errores.
    if(fd<0){

     result.error=0;
    
    }else { 
     // Si todo es ok, devuelvo lo leido.
     result.error=s;
    }
   }
  }
 }

 // Cerramos la tubería del archivo.
 close(fd);
 return &result;
}

Posteriormente tendremos que compilar todo y ejecutarlo de la siguiente forma:

$ lectura server & lectura client localhost

2 comentarios:

Juan Carlos dijo...

Muchas gracias por la aportación!!!

Un saludo.

Anónimo dijo...

Gracias abia estado buscando pero no encotraba nada.

Publicar un comentario