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:
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:
-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).
CLIENT *clnt create(char *host, long prog, long vers, char *proto)
clnt destroy(CLIENT *clnt)
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
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.
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.
-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:
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 :
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;i buffer[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
1 comentarios:
Gracias abia estado buscando pero no encotraba nada.
Publicar un comentario