RPC (Remote Procedure Call) en C – Linux

| 2014-04-8 | No hay comentarios »

Diseño de Programas Distribuidos:

Diseño orientado a la comunicación (Prog. con sockets)
–Diseñar primero el protocolo.
–Construir programas acorde al protocolo.
Diseño orientado a la Aplicación (RPC)
–Construir aplicaciones.
–Dividir los programas y añadir protocolos de comunicación.
RPC (Remote Procedure Call) :

En unix es posible tener en ejecución un programa en C con varias funciones que pueden ser llamadas desde otro programas. Estos otros programas pueden estar corriendo en otros ordenadores conectados en red.

Supongamos, por ejemplo, que tenemos un ordenador muy potente en cálculo matemático y otro con un buen display para gráficos. Queremos hacer un programa con mucho cálculo y con unos gráficos “maravillosos”. Ninguno de los dos ordenadores cumple con ambos requisitos. Una solución, utilizando RPC (Llamada a procedimientos remotos), consiste en programar las funciones matemáticas en el ordenador de cálculo y publicar dichas funciones. Estas funciones podrán ser llamadas por el ordenador gráfico, pero se ejecutarán en el ordenador de cálculo. Por otro lado, hacemos nuestros gráficos en el ordenador gráfico y cuando necesitemos algún cálculo, llamamos a las funciones del ordenador de cálculo.

Al programa con las funciones se le llama “servidor”. Al programa que llama a esas funciones se le llama “cliente”. Normalmente el servidor está siempre corriendo y a la espera de que algún cliente llame a alguna de sus funciones.. Cuando el cliente llama a una función del servidor, la función se ejecuta en el servidor y el cliente detiene su ejecución hasta que el servidor termina.

En el código del programa servidor básicamente hay que seguir los siguientes pasos:

  • Codificar las funciones que van a ser llamadas siguiendo un determinado mecanismo.
  • Informar al sistema operativo (unix) de un nombre, una versión y funciones que publica.
  • Meterse en un bucle infinito esperando que alguien llame a alguna de sus funciones.

Mientras que el programa cliente debe:

  • Establecer una conexión con el servidor.
  • Llamar a las funciones.
  • Cerrar la conexión con el servidor.
RPCGEN
-Es una herramienta para automatizar la creación de clientes y servidores.
-El programa rpcgen hace la mayor parte del trabajo.
-La entrada para rpcgen es una  definición de protocolo  que consiste en una lista de procedimientos y sus parámetros.
Salida de rpcgen:
> rpcgen –C foo.x
foo_clnt.c    (client stubs)
foo_svc.c     (server main)
foo_xdr.c     (xdr  filters)
foo.h            (shared header file)

Creación del programa

# rpcgen -C foo.x
(Cliente main)
# rpcgen -C -Sc foo.x > foomain.c
(Server services)
# rpcgen -C -Ss foo.x > fooservices.c
# gcc -o fooserver fooservices.c foo_svc.c foo_xdr.c –lrpcsvc -lnsl
# gcc -o fooclient foomain.c foo_clnt.c foo_xdr.c -lnsl

Ejemplo de la definición del protocolo (foo.x):

struct twonums {
int a;
int b;
};
program UIDPROG {
version UIDVERS {
int  RGETUID(string<20>) = 1;
string RGETLOGIN( int ) = 2;
int  RADD(twonums)  = 3;
} = 1;
} = 0x20000001;

Con la definición foo.x se utiliza el rpcgen (Ver “Creación del programa” que está más arriba en éste artículo), lo que debemos de modificar son los archivos foomain.c (será el cliente) y fooservices.c (será el servidor). Los demás archivos generados:

  • foo_svc.c. Este es un ejemplo concreto de servidor. Normalmente nos vale tal cual. Básicamente registra al servidor en el sistema unix para indicarle que atienda a las llamadas y proporciona un “switch-case” para llamar a cada una de las funciones al recibir una petición de un cliente.
  • foo_clnt.c. Las llamadas a funciones de rpc desde un cliente son más o menos complejas. Se hacen a través de la función “clnt_call()” que lleva la friolera de 7 parámetros. En “foo_clnt.c” rpcgen mete unas funciones de “traducción” para hacernos más sencillas las llamadas desde nuestro cliente.
  • foo_xdr.c. Como RPC permite llamadas de clientes a servidores que estén en máquinas distintas y, por tanto, puedan tener una arquitectura distinta, es necesario traducir los parámetros y resultados a un “código” universal, independiente de las máquinas. Si los parámetros son tipos básicos (int, float, char, etc), el sistema unix ya tiene unas funciones de conversión (xdr_int(), xdr_float(), etc). Si los parámetros, como en este caso, son estructuras definidas por nosotros, las funciones de conversión hay que hacerlas. rpcgen genera automáticamente dichas funciones y en nuestro caso, las ha metido en el fichero foo_xdr.c
  • foo.h. Aquí están los prototipos de nuestras funciones. Cualquier cliente que quiera usarlas, deberá hacer un include de este fichero. El prototipo no es exactamente como esperaríamos. A cada función le añade en el nombre unas “coletillas” para indicar el número de versión. Define también otras constantes como nombre de programa, número de versión, etc, que son útiles a la hora de hacer la conexión con el servidor.

foomain.c


/*
 * This is sample code generated by rpcgen.
 * These are only templates and you can use them
 * as a guideline for developing your own functions.
 */

#include "foo.h"
void
uidprog_1(char *host,char *argv[],int argc)
{
 CLIENT *clnt;
 int *result_1;
 char * rgetuid_1_arg;
 char * *result_2;
 char *name;
 int rgetlogin_1_arg;
 int *result_3;
 twonums radd_1_arg;

#ifndef DEBUG
 clnt = clnt_create (host, UIDPROG, UIDVERS, "udp");
 if (clnt == NULL) {
 clnt_pcreateerror (host);
 exit (1);
 }
#endif /* DEBUG */

if(argc==3){
 //Pedir usuario pasando uid
 name= argv[2];
 if( (name[0]>='0') && (name[0]<='9') ){
 rgetlogin_1_arg=atoi(name);
 result_2 = rgetlogin_1(&rgetlogin_1_arg, clnt);
 if (result_2 == (char **) NULL) {
 clnt_perror (clnt, "call failed");
 }
 printf("UID: %d, Name is %s\n",rgetlogin_1_arg,*result_2);
 }
 else{
 //Pedir uid pasando usuario
 rgetuid_1_arg=name;
 result_1 = rgetuid_1(&rgetuid_1_arg, clnt);
 if (result_1 == (int *) NULL) {
 clnt_perror (clnt, "call failed");
 }
 printf("UID: %d, Name is %s\n",*result_1,name);
 }
}
else{
 radd_1_arg.a = atoi(argv[2]); /* Codigo añadido por programador */
 radd_1_arg.b = atoi(argv[3]); /* Codigo añadido por programador */

 //Llamada al procedimiento remoto
 result_3 = radd_1(&radd_1_arg, clnt);
 if (result_3 == (int *) NULL) {
 clnt_perror (clnt, "call failed");
 }

printf ("La suma de %d + %d es %d\n", radd_1_arg.a,radd_1_arg.b, *result_3);
 /* Codigo añadido por programador */

}
/*

result_1 = rgetuid_1(&rgetuid_1_arg, clnt);
 if (result_1 == (int *) NULL) {
 clnt_perror (clnt, "call failed");
 }
 result_2 = rgetlogin_1(&rgetlogin_1_arg, clnt);
 if (result_2 == (char **) NULL) {
 clnt_perror (clnt, "call failed");
 }
 result_3 = radd_1(&radd_1_arg, clnt);
 if (result_3 == (int *) NULL) {
 clnt_perror (clnt, "call failed");
 }
 */
#ifndef DEBUG
 clnt_destroy (clnt);
#endif /* DEBUG */
}

&nbsp;

main (int argc, char *argv[])
{
 if (argc < 3) {
 printf ("usage: %s server_host\n", argv[0]);
 exit (1);
 }
uidprog_1(argv[1],argv,argc);
}

fooservices.c 


/*
 * This is sample code generated by rpcgen.
 * These are only templates and you can use them
 * as a guideline for developing your own functions.
 */
#include "sys/types.h"
#include "pwd.h"
#include "foo.h"

char *error="Error";

int *
rgetuid_1_svc(char **argp, struct svc_req *rqstp)
{
 static int result;
 struct passwd *pw;
 //ARGP is a pointer to: string (a char *)
 pw= getpwnam(*argp);
 //ERROR ! NO USER FOUND
 if(pw==NULL){
 result=-1;
 }
 else{
 result=(int) pw->pw_uid;
 }

return &result;
}

char **
rgetlogin_1_svc(int *argp, struct svc_req *rqstp)
{
 static char *result;
 struct passwd *pw;

//ARGP is a pointer to: string (a char *)
 pw = getpwuid (*argp);
 //User not found
 //we cannot return a null
 //point to an error string
 if(pw==NULL){
 result=error;
 }
 else{
 result=pw->pw_name;
 }

return &result;
}

int *
radd_1_svc(twonums *argp, struct svc_req *rqstp)
{
 static int result;

result=argp->a+argp->b;

/*
 * insert server code here
 */

return &result;
}

Para ejecutar el ejemplo:

Compilar:

# gcc -o fooserver fooservices.c foo_svc.c foo_xdr.c –lrpcsvc -lnsl
# gcc -o fooclient foomain.c foo_clnt.c foo_xdr.c -lnsl

Y ejecutar los ejecutables ./fooclient (ip del servidor) (agregar argumento(s)) y ./fooserver en la consola.

Acerca del autor: Rodrigo Paszniuk

Ingeniero Informático, amante de la tecnología, la música, el ciclismo y aprender cosas nuevas.

Posts Relacionados

  • Developers SO Sistemas Operativos preferidos por los developers
  • Instalar Tomcat 7 en CentOS 6
  • Sockets en C (Parte II) – Linux
  • Sockets en C (Parte I) – Linux