Remote procedure call

Las llamadas a procedimiento remoto o Remote Procedure call (en inglés) son un concepto esencial detrás de SOA.

Sumario

  1. Introducción
  2. Cómo funciona
  3. Factores de implementación de RPC
  4. Pasaje de parámetros en RPC y representación externa de datos

Introducción

RPC es una poderosa técnica para construir aplicaciones cliente-servidor distribuidas. Es una extensión de la idea de llamada a procedimiento local o convencional, tal que el procedimiento invocado no necesita estar en el mismo espacio de direcciones que el procedimiento invocante. Los dos procesos podrán estar en el mismo sistema, o en distintos sistemas conectados por red. Al usar RPC,
los programadores de aplicaciones distribuidas se evitan los detalles de la comunicación por red. La independencia del transporte de RPC aisla la aplicación de los elementos físicos y lógicos de los mecanismos de comunicación de datos y permite usar una variedad de transportes a la aplicación [1].

Cómo funciona

Una llamada RPC es análoga a una llamada a función. Como tal, cuando se realiza una RPC, se pasan los argumentos del programa invocante al procedimiento remoto y se espera una respuesta de éste. En la figura 1 se muestran las actividades que tienen lugar durante una llamada RPC entre dos sistemas en red El cliente hace una llamada a procedimiento, que manda un requerimiento a un servidor, y espera. El programa invocante es bloqueado hasta que se recibe una respuesta, o el tiempo de espera se agota. Cuando se recibe el requerimiento en el servidor, este
dispara una rutina que realiza la solicitud y manda la respuesta al cliente. Luego de completar la llamada RPC, el programa cliente continúa su ejecución.

Procedimiento de llamada RPC
Procedimiento de llamada RPC

Figura 1: ilustración de una llamada a procedimiento remoto

Las llamadas a procedimiento, como hemos dicho, abstraen la complejidad de la comunicación. La invocación a un procedimiento en RPC se convierte en un pasaje de mensajes entre cliente y servidor.

El objetivo del RPC es hacer que una llamada remota se parezca lo más posible a una llamada
local. En la forma más simple, para llamar a un procedimiento remoto, el programa cliente debe utilizar una pequeña biblioteca llamada stub cliente, que representa el proceso servidor en el espacio de direcciones del cliente. Del mismo modo, el servidor utiliza un procedimiento llamado stub servidor. Estos procedimientos ocultan el hecho de que la llamada a procedimiento del cliente al servidor no es local [4].

En la figura 2 se muestran los pasos que se realizan en una llamada RPC. En el paso 1, el cliente llama al stub cliente. Esta llamada es una llamada a un procedimiento local, en la que los parámetros se pasan de la manera normal. En el paso 2, el stub cliente empaqueta los parámetros de la llamada en un mensaje y se hace una llamada al sistema para enviar el menaje. El empaquetar los parámetros se llama marshalling. En el paso 3, el kernel envía el mensaje de la computadora cliente a la computadora servidor. En el paso
4, el kernel recibe el paquete entrante y lo envía al stub servidor. Finalmente, en el paso 5 el stub servidor llama al procedimiento en el servidor. La respuesta hace el recorrido inverso [4].

Esquema de llamada RPC
Esquema de llamada RPC

Figura 2: esquema de una llamada a procedimiento remoto

El punto clave a tener en cuenta es que el procedimiento cliente, escrito por el usuario, hace simplemente una llamada normal, o sea, local, al stub cliente, que tiene el mismo nombre que el procedimiento en el servidor. Como el procedimiento cliente y el stub cliente están en el mismo
espacio de direcciones, los parámetros son pasados de la manera usual. Del mismo modo, el procedimiento servidor es invocado por un procedimiento en su mismo espacio de direcciones, con los parámetros que éste espera. Para el procedimiento servidor, no hay nada inusual. De esta manera, en vez de hacer entrada/salida usando send y receive, la comunicación se realiza imitando una llamada a procedimiento local.

Factores de implementación de RPC

Una cuestión importante en RPC es la semántica de las llamadas. Mientras que las llamadas a procedimientos locales sólo fallan en circunstancias extremas, las RPC pueden fallar, o duplicarse y ejecutarse más de una vez, a causa de errores de red comunes. Puesto que estamos tratando con una transferencia de mensajes por enlaces de comunicación no confiables, es mucho más fácil para un sistema operativo
asegurar que se haya actuado en respuesta a un mensaje más de una vez, que asegurar que se haya actuado en respuesta al mensaje exactamente una vez. En vista de que el sistema operativo asegura que las llamadas a procedimiento locales se ejecutan exactamente una vez, la mayoría de los sistemas tratan de duplicar esta funcionalidad con los RPC. Para ello, los sistemas anexan a cada mensaje una marca de tiempo. El servidor debe mantener un historial de las marcas de tiempo de todos los mensajes que ya procesó, o un historial con la longitud suficiente para asegurar la detección de mensajes repetidos. Los mensajes que llegan y tienen una marca de tiempo que ya está en el historial, se desechan[2]. La generación de esa marca de tiempo se realiza en forma algorítmica usando por ejemplo Reloj lógico de Lamport[3].

Otro aspecto importante es la comunicación entre el servidor y el cliente. Con las
llamadas a procedimientos locales, se realiza alguna forma de vinculación durante el enlace, carga o ejecución, por la cual el nombre de una llamada a procedimiento se sustituye por la dirección en memoria de esa llamada. El esquema RPC requiere una vinculación similar del cliente y el puerto del servidor, pero ¿cómo averigua un cliente los números de puerto del servidor? Ninguno de los dos sistemas tiene información completa acerca del otro porque no comparten memoria. Hay dos estrategias comunes. Primera, la información de vinculación podría decidirse con antelación, en forma de direcciones de puerto fijas. En el momento de la compilación una llamada RPC tiene asociado un número de puerto fijo. Una vez compilado el programa, el servidor no podrá cambiar el número de puerto del servidor solicitado. Segunda, la vinculación puede efectuarse dinámicamente mediante un mecanismo de
encuentros (rendez-vous). Por lo general, un sistema operativo proporciona un demonio de encuentros (también llamado concertador) en un puerto RPC fijo. Un cliente envía entonces un mensaje, que contiene el nombre de la RPC, al demonio de encuentros, solicitando la dirección de puerto de la RPC que necesita ejecutar. El demonio devuelve el número de puerto, y las llamadas RPC se pueden enviar a ese puerto hasta que el proceso termine (o el servidor se caiga). Este método requiere el gasto extra de la solicitud inicial, pero es más flexible que la primera estrategia.[2]

Pasaje de parámetros en RPC y representación externa de datos

La información almacenada en programas en ejecución está representada como estructuras de datos, por ejemplo conjuntos de objetos interconectados, mientras que la información en los mensajes consiste de secuencias de
bytes. Sin importar la forma de comunicación utilizaa, la estructura de datos debe ser "aplastada" (convertida a una secuencia de bytes) antes de transmitirse y reconstruirse al llegar. Los items de datos primitivos que se transmiten en mensajes pueden ser valores de datos de muchos tipos diferentes, y no todas las computadoras guardan los datos primitivos tales como los enteros en el mismo orden. La representación de números de punto flotante también difiere entre arquitecturas. Otro tema es el conjunto de códigos que se utilizan para representar caracteres: por ejemplo, los sistemas Unix usan ASCII, que toma un byte por caracter, mientras que el standard Unicode permite la representación de textos en muchos idiomas diferentes y toma dos bytes por caracter. Existen dos variantes para el orden de enteros: el orden big-endian, en el que el byte más significativo viene primero, y little-endian, en el que viene a lo útimo.

nSe puede usar uno de los métodos siguientes para permitirles a dos computadoras intercambiar datos:

  • Los valores se convierten a un formato externo acordado previamente ante de la transmisión y convertidos
    a la forma local cuando son recibidos; si se sabe que las dos computadoras son del mismo tipo, esta conversión
    puede omitirse.
  • Los valores se transmiten en el formato del emisor, junto con una indicación del formato usado, y el receptor
    convierte los valores si es necesario.

Hay que notar, sin embargo, que los bytes en sí no son alterados nunca durante la transmisión. Para
soportar RPC, cualquier tipo de dato que se puede pasar como argumento o devuelto como resultado tiene que poder "aplastarse" y representar los valores de datos primitivos en un formato acordado. El estándar acordado para la representación de estructuras de datos y valores primitivos se llama
representación externa de datos.

Se denomina Marshalling al proceso de tomar una colección de items de datos y transformarlos en una forma adecuada para transmitirlos en un mensaje. Unmarshalling es el proceso de revertir la transformación a la llegada del dato para producir una colección equivalente de items de datos en el receptor. Así, marshalling consiste en la traducción de items de datos estructurados y valores primitivos en una representación externa de datos. Del mismo modo, unmarshalling consiste en generar valores primitivos a partir de la representación externa de los datos y la reconstrucción de las estructuras de datos.

Se discutirán dos formas alternativas de representación externa y marshalling:

  • La representación común CORBA, que trata de una representación externa de los datos estructurados y primitivos que se puede pasar como
    argumentos, y los resultados de invocaciones remotas de métodos en CORBA. Se puede usar en una variedad de lenguajes de programación.
  • La serialización de objetos de Java, que trata de aplastar y generar la representación externa de un
    único objeto o árbol de objetos que pueden necesitar ser transmitidos en un mensaje o almacenados en un disco. Solamente se usa en el lenguaje Java.

En ambos casos, las actividades de marshalling y unmarshalling serán realizadas por una capa middleware sin ninguna intervención de parte del programador de la aplicación. A causa de que el marshalling requiere considerar los más finos detalles de la representación de componentes primitivos de objetos compestos, el proceso es propenso a tener errores si se hace manualmente. La eficiencia es otro de los puntos a considerar en el diseño de procedimientos de marshalling generados automá
ticamente.

Aplicaciones
RMI, RPC y evetos
Protocolos pedido-respuesta
Representación externa de datos
Sistema Operativo

En esta tabla se observan las capas de software intervinientes en una llamada a procedimiento remoto. Las capas de color gris son capas Middleware.

Las dos alternativas discutidas realizan marshalling de los datos primitivos al formato binario. Otra manera es hacer marshalling de todos los objetos a ser transmitidos en texto ASCII, que es relativamente simple de implementar, pero la forma resultante será más larga. El protocolo HTTP es un ejemplo de este último enfoque.

CORBA CDR

CORBA CDR es la representació
n externa de datos definida con CORBA 2.0. CDR puede representar todos los tipos de dato que se pueden pasar como argumentos y devolver valores en invocaciones remotas en CORBA. Estos consisten de 15 tipos primitivos, que incluyen short (16 bits), long (32 bits) unsigned short, unsigned long, float (32 bits), double (64 bits), char, boolean (true, false), octet (8 bits) y any (que puede representar cualquier tipo básico o construído); junto con una variedad de tipos compuestos, como ser sequence, string, array, etc. Cada argumento o resultado de una invocación remota se representa por una secuencia de bytes en la invocación o resultado [3].

El tipo de datos no se informa junto con la representación de los datos en el mensaje. Esto es porque se asume
que el emisor y el receptor tienen conocimiento común del orden y tipos de datos en el mensaje [3].

Marshaling en CORBA

nLas operaciones de Marshalling se pueden generar automáticamente de la especificación de tipos de dato a ser transmitidos en un mensaje. Los tipos de las estructuras de datos y de los datos básicos se describen en CORBA IDL, que da una notación para describir los tipos de los argumentos y resultados de los métodos RMI.

El compilador de interfaz CORBA genera operaciones de marshalling y unmarshalling para los argumentos y resultados de
métodos remotos de las definiciones de los tipos de sus parámetros y resultados.

Serialización de objetos Java

En Java RMI, tanto los objetos como los tipos primitivos se pueden pasar como argumentos o ser resultado de métodos RMI. Una clase debe implementar la interfaz Serializable. Esto tiene el efecto de permitir que sus instancias sean serializables.

En Java, la palabra serialización se refiere a la actividad de
aplastar el objeto o un conjunto conectado de objetos en una forma en serie que es adecuada para almacenar en disco o transmitir en un mensaje, por ejemplo como argumento o resultado de un RMI. La des-serialización consiste de restaurar el estado de un objeto o conjunto de objetos de su forma serializada. Se asume que el proceso que hace la des-serialización no tiene conocimiento previo de los tipos de los objetos de la forma serializada. Por lo tanto, alguna información acerca de la clase de cada objeto se incluye en la forma serializada. Esta información permite al receptor cargar la clase apropiada cuando se des-serializa un objeto.

Los objetos Java pueden contener referencias a otros objetos. Cuando se serializa un objeto, todos los objetos que éste referencia se serializan juntos con él para asegurar que cuando el objeto es reconstruído, se pueden satisfacer todas sus referencias en el receptor. Las referencias se serializan como &
quot;handles", en este caso el handle es una referencia a un objeto dentro de la forma serializada, por ejemplo el próximo número en una secuencia de enteros positivos. El proceso de serialización debe asegurar que hay una correspondencia 1 a 1 entre referencias a objetos y handles. También debe asegurar que cada objeto se escribe solamente una vez – en la segunda y subsiguientes apariciones del objeto, se escribe el handle en lugar del objeto.

Para serializar un objeto, se escribe su información de clase, seguido de los tipos y nombres de sus variables. Si estas variables pertenecen a nuevas clases, se escribe la información de esas clases, seguido de los tipos y nombres de sus variables. Este procedimiento recursivo continúa hasta que se haya escrito toda la información de las clases, el tipo y nombre de las variables. A cada clase se le da un handle, y no se escribe cada clase más de una vez al flujo de bytes, se
escriben handles como sea necesario.

Uso de Reflection

El lenguaje Java soporta Reflection, la posibilidad de obtener propiedades de las clases, como los nombres y tipos de sus variables y métodos. También permite que se creen clases por los nombres, y que se cree un constructor con argumentos dados para una clase. Reflection hace posible la serialización y des-serialización de una manera completamente genérica. Esto significa que no hay necesidad de generar funciones especiales de marshaling para cada tipo de objeto como se requiere para CORBA [3].

[1] – Remote procedure call en http://www.cs.cf.ac.uk/Dave/C/node33.html
[2] – Silberschatz, Gavin: “Sistemas Operativos
Modernos”, 5ta. ed, Addison-Wesley-Longman, 1999
[3] – Coulouris et al: “Distributed systems, concepts and design”, 3ra. ed, Pearson, 2001
[4] – Andrew Tanenbaum: “Modern Operating Systems”, 2da ed, 1999

Be the first to comment

Leave a Reply

Your email address will not be published.


*