Programando con RPC: Definiendo el servidor

Esta es la tercera parte de la serie de post sobre la programación con RPC, en este se implementará el funcionamiento del servidor, aunque parezca sencillo, y la verdad lo es, puede ser un poco complicado hacerlo debidamente ya que se debe tomar en cuenta aspectos como: uso adecuado de memoria, manejo de punteros y, en algunos casos, concurrencia. Como se comentaba en el post anterior el objetivo es hacer un servicio que retorne su información como proceso del sistema (nombre, usuario, id de proceso, etc).

Primero, no es obligatorio pero si recomendable, ejecutar el comando rpcgen con el parámetro -Ss:

$ rpcgen -Ss process.x

Esta orden hacé que el generador de código RPC cree una plantilla para empezar a implementar la función que hará el servidor, es decir, el procedimiento que sera llamado cuando sea requerido por el cliente. Algo como esto sera generado:

/*
 * 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 "process.h"

process_info *
get_process_info_1_svc(void *argp, struct svc_req *rqstp)
{
	static process_info  result;

	/*
	 * insert server code here
	 */

	return &result;
}

La función get_process_info_1_svc() define las acciones que serán tomadas del servidor, argp es el argumento de entrada, siempre en esta implementar de RPC la función toma solo un argumento de entrada por lo cual es necesario crear diferentes tipos de estructuras si se desea pasar mas de un argumento, también puede definirse mas de un solo parámetro o cambiar la forma de representar los datos pero estos son extensiones fuera del antiguo estándar pero mas intuitivos a la hora de implementar el cliente como el servidor. En este caso la función no toma argumentos pero aun así da el parámetro pero sin información útil. Para entregar la respuesta al cliente se usa el valor de retorno de la función. En este caso retornara una estructura process_info con los datos del proceso servidor.

Ahora se implementará la función de esta manera:

#include 
#include "process.h"

process_info *
get_process_info_1_svc(void *argp, struct svc_req *rqstp)
{
	static process_info  result;

	result.name = "process_server";
	result.pid = getpid();
	result.ppid = getppid();
	result.uid = getuid();
	result.gid = getgid();

	return &result;
}

Se muestra la función haciendo uso de las llamadas estándares de los sistemas UNIX para obtener la información del proceso. Ahora guardamos el código de implementación como process_server.c y ejecutamos el compilador para generar el ejecutable:

$ cc -o process_server process_server.c process_svc.c process_xdr.c

Una vez compilado podemos ejecutar el programa, pero no trabajara hasta que el cliente mande solicitudes, para ello en el siguiente post se implementara el programa cliente, el cual llamara al procedimiento del servidor y mostrara el resultado de esa información por la consola.

Programación con RPC: Definiendo el servicio

Antes de empezar a escribir alguna linea de código, no importa que tipo de programa sea, siempre tienes que definir para que servirá el programa, es decir, su funcionamiento, que datos procesa y cuales genera a partir de esta; con la programación con RPC no es la excepción. Ya que antes de definir alguna función que deseas ejecutar de forma remota (no siempre es así) primero hay que definir que parámetros recibirá y cuales regresara, así como los tipos de datos que va a necesitar.

Como se mencionó en la entrada anterior, el ejemplo sera un servicio que reciba peticiones llamando a una función y que retorne una estructura de datos al cliente con la información del proceso que realizo la tarea.

Empezamos con el código que rpcgen procesara para generar la interfaz:

/* Define process info struct */
struct process_info {
	string name<>;
	int pid;
	int ppid;
	int uid;
	int gid;
};

program PROCESS {
	version ONE {
		process_info GET_PROCESS_INFO(void) = 1;
	} = 1;
} = 20000000;

En este archivo se define una estructura al muy parecido al estilo C, con algunas excepciones: rpcgen siempre genera un typedef de la estructura, siendo innecesario poner la palabra clave struct antes del nombre, tambien al referirse a una cadena de caracteres se usa la palabra clave string en vez de la conocida char * , este es un punto muy importante ya que el protocolo RPC tiene una manera de trabajar con los tipos primitivos: (int, short, longfloat, double, array, pointer, string, etc); por ultimo, es importante tener en cuenta que no se puede cambiar fácilmente los tipos que se utilizaran en el servicio, ya que se tiene que actualizar todo el código, por eso se debe tener en claro que necesitaras. También existen otros tipos que son mas complejos en definición pero son muy útiles en ciertas circunstancias las cuales se presentan muy comúnmente en la programación normal. Algunos ejemplos son:

/* Ejemplo de diferentes estructuras de datos */
struct otra_estructura {
	int id;
	string nombre<>;
	string direccion<>;
};

struct otra_mas {
	int operacion;
	double dato1;
	double dato2;
	double dato3;
};

Después de definir los tipos utilizados se sigue con la definición del servicio en si. Primero se escribe la palabra clave program que describe el nombre del servicio, el cual es solo una referencia ya que al final se establece un valor numero fijo el cual servirá para reconocer el servicio al cual queremos llamar. Después de definir el nombre y antes de definir un valor ponemos unos corchetes y escribimos la palabra clave version la cual establece al nombre definido un valor que define la versión del servicio que solicitamos, generalmente se empieza por 1 hasta el numero de versiones que haya, algunos servicios como NFS tienen diferentes interfaces de comunicación agrupadas por versiones, esto es para evitar conflictos en caso de añadir una nueva funcionalidad y afectar a los clientes con versiones anteriores, al igual que el anterior entre el nombre y numero de versión, colocamos las funciones que llamaremos desde el cliente hacia el servidor. Estos se definen al estilo C pero con la excepción que también se le establece un valor numérico el cual cumple la función de identificar el procedimiento que estamos llamando. Un ejemplo diferente:

/* Se definirá un servicio con dos versiones diferentes,
 * las cuales proporcionan la misma función pero con diferentes
 * parámetros. */
program NOMBRE_SERVICIO {
	version VERSION_UNO {
		int get_double (int) = 1;
	} = 1; /* verison 1 */
	version VERSION_DOS {
		long get_double (long) = 1;
	} = 2; /* version 2 */
} = 20000001; /* Numero de servicio: puede ser cualquiera,
               * con algunas excepciones */

Regresando al código original, este es solo una interfaz, en si no puede ser compilado como cualquier código fuente en C, sino debe ser procesado por rpcgen para generar el código apropiado, hay que tomar en cuenta que solo generara las bases del servidor y del cliente, pero se debe definir a mano las funciones del servidor, así como las llamadas que hará el cliente y como usara la información que obtendrá. Para generar los archivos se ejecuta el siguiente comando:

$ rpcgen process.x

Siendo process.x el nombre del código RPC que aparece al principio del tutorial. Los archivos resultantes de ello son:

  • process.h: Archivo de cabecera que posee las definiciones y variables utilizadas tanto por el cliente como por el servidor.
  • process_xdr.c: Archivo de código fuente en C que maneja el tipo process_info de forma dinámica para ser manejado por el protocolo RPC.
  • process_svc.c: Archivo de código fuente en C que define la parte principal de servidor RPC, pero no define las funciones que serán llamadas por el cliente.
  • process_clnt.c: Archivo de código fuente en C que se encarga de abstraer la llamada del procedimiento remoto.

En el siguiente post se definirá una versión mínima del servicio process complementando el código faltante.

Programación con RPC (Remote Procedure Calls)

RPC (Remote Procedure Calls), o también conocido en español como Llamadas a Procedimiento Remoto es una tecnología de intercomunicación de procesos, que se diferencia de algunos otros como memoria compartida, manejo de hilos (threads) o sistemas multi-proceso (fork), ya que ejecuta código de una maquina remota y obtiene el resultado de esta y esta capacitado para funcionar sobre red, es decir, sirve para intercambiar información entre procesos de una misma o diferentes maquinas con procedimientos definidos para llevar a cabo una tarea en particular, como intercambio de archivos (NFS) o monitoreo de servidores.

RPC Diagram

Actualmente existen varias implementaciones o modelos de RPC los cuales cumplen con ciertas tareas o funciones especificas, las mas conocidas son:

  • ONC/RPC: Uno de los primeros protocolos RPC, creado por Sun Microsystems para sistemas basados en UNIX.
  • DCE/RPC: Protocolo RPC de Open Software Foundation.
  • RMI: Sistema de invocación de procedimientos para Java.
  • XML-RPC: Llamadas a procedimiento remoto usando XML vía HTTP.
  • JSON/HTTP: Llamadas a procedimientos por lenguaje de marcas JSON vía HTTP.
  • DCOM: Modelo de Objetos de Componentes Distribuidos de Microsoft.

Aparte de otras variantes privativas o de uso menor.

En este caso utilizaremos la implementación de Sun: ONC/RPC. Es uno de los mas usados en entornos basados en UNIX, a causa de su diseño simple pero potente, ya que el crear un servidor RPC es sencillo sin tomar en cuenta a la complejidad del trabajo que realizara. Aunque a causa de su simpleza de trabajo, no puede ser utilizados fácilmente en redes WAN o sistemas heterogéneos. Pero ello no lo hace malo para aprender los fundamentos de la ejecución de código remoto.

Antes de empezar el tutorial se tienen que descargar los programas y librerías necesarias para la compilación de los ejemplos. Se recomienda que sea sobre un sistema GNU/Linux o Mac OS X, si necesita usar Windows instalar el entorno Cygwin. Los requerimientos son:

  • Compilador de C, preferentemente GCC.
  • Generador de código RPC: rpcgen, Se incluye por defecto en los entornos de desarrollo UNIX. En Cygwin se instala como onc-rpc-devel.
  • Las librerías de desarrollo libtirpc (solo es necesario si el sistema no lo incluye por defecto como Cygwin).
  • Preferentemente dos maquinas para ejecutar el programa cliente y el servidor aparte.

En la siguiente parte se mostrara el archivo de interfaz de RPC para empezar a implementar un servicio de prueba: Obtener información del proceso servidor.

Reapertura del Blog

Han pasado mas de dos años desde la última publicación en el Blog de DarkMega Zero, por varias razones no se había hecho alguna publicación pero próximamente habrá mas contenido referido a temas como programación y diseño de sistemas, así como temas específicos como videojuegos o aplicaciones personalizables. Espero poder cumplir con mi objetivo esta vez. Si tienen alguna duda o pregunta sobre el funcionamiento de Linux o un caso de programación con gusto los apoyaré.