RPC/RMI LSUB GSYC 20 de marzo de 2013 (cc) 2013 Laboratorio de Sistemas, Algunos derechos reservados. Este trabajo se entrega bajo la licencia Creative Commons Reconocimiento NoComercial - SinObraDerivada (by-nc-nd). Para obtener la licencia completa, véase http://creativecommons.org/licenses/. También puede solicitarse a Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. Las imágenes de terceros conservan su licencia original. RPC • Llamada a un procedimiento que ejecuta en otro espacio de direcciones. • Sigue el modelo cliente/servidor, petición-respuesta. • Al usuario se le ocultan los detalles de la comunicación y de la distribución: transparencia de acceso y de distribución. ecución de una RPC RPC PC involucra una serie de etapas que se ejecutan tanto en la máquina que realiza omo en la máquina que ejecuta el procedimiento remoto (servidor). Figura 2: Pasos en la ejecución de una RPC RPC: fallos ¿Y si el servidor falla mientras se está haciendo? Hay distintas semánticas: • Exactly once: la operación se realiza una vez. Es caro. • At most once: la operación se ejecuta como mucho una vez, pero puede que ninguna. Se retorna error al cliente, que no puede saber si se ha realizado la operación o no. • At least once: la operacion se ejecuta al menos una vez, puede que varias. Si la operación es idempotente no hay problema. Serialización • Las máquinas del sistema distribuido pueden ser homogéneas o heterogéneas. • En ambos casos, tiene que haber una representación canónica para los datos. • Cada extremo debe convertir los datos de su formato local al formato canónico y viceversa: serialización (marshaling o aplanado). • Podemos definir nuestra propia serialización o usar uno de los múltiples estándars: XDR (Sun rpc), JSON, ASN.1, XML-RPC, etc. Serialización Para realizar nuestra propia serialización tenemos dos alternativas: • Texto. Factores a tener en cuenta: • Codificación (p. ej. UTF-8). • Terminación de las cadenas. • Binario. Factores a tener en cuenta: • Longitud de los datos. • Endianess. • Formato (p. ej: coma flotante IEEE 754, IEEE 754-2008, etc.). Serialización Ejemplo: serializar un array de 3 enteros : 1, -2, 3. • Texto (UTF-8): "[[1], [-2], [3]]" • Binario (little endian, 4 bytes por entero, complemento a 2): 0x03 0x01 0xfe 0x03 0x00 0x00 0xff 0x00 0x00 0x00 0xff 0x00 0x00 0x00 0xff 0x00 Ejemplo: JSON • Es un formato de texto. • Es independiente del lenguaje. • Permite aplanar: • Una colección de pares atributo/valor con el que se pueden serializar objetos, records, diccionarios, etc. • Lista de valores con el que se pueden aplanar arrays, listas, conjuntos, etc. Ejemplo: JSON Objeto: { ” rectangulo ” : { ” v i s i b l e ” : true , ” color ” : ” verde ” , ” supIzq ” : { ”x” : 34 , ” y ” : 30 }, ” infDer ” : { ”x” : 43 , ” y ” : 10 } } } Ejemplo: JSON Lista: { ”listaCoor”: [ {”x” : 0 , ”y” : 0} , {”x” : 2 , ”y” : 1} , {”x” : 4 , ”y” : 5} , {”x” : 0 , ”y” : 1} , { ” x ” : −1 , ” y ” : 23} ] } Serialización en Java • Cliente y servidor (ambos en Java) se pueden despreocupar del formato de los datos. • Una clase que cumpla con la interfaz Serializable se puede serializar. Esa interfaz no tiene ningún método. • Aplana el objeto y todos los objetos referenciados (web of objects). • Si un miembro es transient no forma parte del estado persistente del objeto y por tanto no se serializa. • Si un objeto no se puede serializar, se levanta la excepción NotSerializableException. Código F i g u r e s = new S q u a r e ( p1 , p2 ) ; ObjectOutputStream os = new O b j e c t O u t p u t S t r e a m ( s o c k . g e t O u t p u t S t r e a m ( ) ) ; os . w r i t e O b j e c t ( s ) ; Serialización en Java Las clases que necesitan personalizar la serialización deben definir los métodos con estas cabeceras: p r i v a t e void w r i t e O b j e c t ( j a v a . i o . ObjectOutputStream out ) throws IOException private void readObject ( java . io . ObjectInputStream in ) throws IOException , ClassNotFoundException ; p r i v a t e void readObjectNoData ( ) throws ObjectStreamException ; Nombrado • Necesitamos nombrar los recursos para poder acceder a ellos (p. ej. hacer una RPC sobre un recurso concreto). • Transparencia de localización: permite encontrar los recursos con independencia de su localización fı́sica. • El servicio puede ser centralizado o distribuido. • El esquema de nombrado puede ser plano o estructurado. Ejemplo: DHT • Una tabla hash distribuida es un servicio de nombres planos. Como en una tabla hash, a una clave le pertenece un valor asociado. Ejemplo: DNS • El DNS es un servicio distribuido de nombres estructurados para traducir nombres a direcciones IP. (cc) George Shuklin Nombres estructurados • Los nombres estructurados forman un espacio de nombres (name space) • Generalmente se representan como un grafo dirigido con nodos contenedores y nodos hoja, • Los objetos se pueden nombrar con nombres absolutos o relativos. • La resolución puede ser iterativa o recursiva. Resolución de nombres iterativa (c) A. Tanenbaum Resolución de nombres recursiva • Reduce los mensajes en el cliente (puede afectar a la latencia en gran medida) y facilita el caching. (c) A. Tanenbaum Java RMI RMI • OOM (Object Oriented Middleware) de Java. • Permite: • Localizar objetos remotos. • Invocar métodos de objetos remotos. • Cargar dinámicamente definiciones de clases remotas. • El compilador genera los stubs. • Usa la serialización de Java. • El RMI registry se encarga del nombrado. RMI En resumen: • La clase remota debe estar partida en una interfaz y una clase que la implemente. • Todos los métodos remotos deben estar especificados en la interfaz. • La interfaz debe extender java.rmi.remote. • Todos los métodos remotos deben especificar que levantan RemoteException. • Si los métodos usan (argumentos, retorno) otro objeto remoto, este objeto también debe ser definido por una interfaz. Registry • Es un servicio de nombrado sencillo para localizar los objetos remotos. • En la máquina en la que creamos el registro debemos ejecutar el comando: rmiregistry RMI Argumentos en RMI: • Los objetos remotos se pasan por referencia. El objeto remoto en realidad es una instancia del stub de cliente que le representa. • Los objetos locales se pasan por valor, se envı́a una copia del objeto serializado. Se copian todos los miembros menos los estáticos y transient. RMI: servidor • Si una interfaz exiende la interfaz Remote hace que los métodos sean accesibles por RMI. • Las excepciones extra provocadas por los mecanismos de de RMI (el servidor no responde, error de aplanado, etc.) se engloban en RemoteException (es checked). p u b l i c i n t e r f a c e C l o c k S e r v e r e x t e n d s Remote { p u b l i c Date getTime ( ) t h r o w s R e m o t e E x c e p t i o n ; } RMI: Exportar el objeto • Hay que instanciar un objeto de la clase y exportarlo. • El método estático exportObject() de UnicastRemoteObject permite crear el stub de servidor para aceptar peticiones. Su segundo argumento es el puerto TCP que debe usar (0 significa que el puerto es anónimo). • Dicho método retorna el stub de cliente para acceder a este objeto, que debemos registrar. C l o c k S e r v e r c = new C l o c k ( ) ; ClockServer stub = ( ClockServer ) UnicastRemoteObject . exportObject ( c , 0 ) ; RMI: Exportar el objeto (ii) • Para registrar el stub de cliente tenemos que localizar el registro. La clase LocateRegistry permite crear un registro en esta JVM (método createRegistry()) o usar un registro existente(método getRegistry()). • El método rebind() registra el stub con un nombre dado. Una aplicación sólo puede registrar objetos remotos en su localhost. Registry r e g i s t r y = LocateRegistry . createRegistry (9999); r e g i s t r y . rebind ( ” MasterClock ” , stub ) ; Código p u b l i c c l a s s Clock implements C l o c k S e r v e r { p u b l i c Clock ( ) throws RemoteException{ super ( ) ; } p u b l i c Date getTime ( ) t h r o w s R e m o t e E x c e p t i o n { r e t u r n new Date ( ) ; } p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { try { C l o c k S e r v e r c = new C l o c k ( ) ; ClockServer stub = ( ClockServer ) UnicastRemoteObject . exportObject ( c , 0 ) ; Registry r e g i s t r y = LocateRegistry . createRegistry (9999); r e g i s t r y . rebind ( ” MasterClock ” , stub ) ; System . o u t . p r i n t l n ( ” C l o c k bound ” ) ; } catch ( Exception e ) { System . e r r . p r i n t l n ( ” C l o c k e x c e p t i o n : ” ) ; e . printStackTrace ( ) ; } } } RMI: cliente • Debe conseguir una referencia al registro: Registry registry = L o c a t e R e g i s t r y . g e t R e g i s t r y ( ” pc1 . e x a m p l e . o r g ” , 9 9 9 9 ) ; RMI: cliente • Después consigue el stub de cliente: ClockServer c = ( ClockServer ) r e g i s t r y . lookup ( ” MasterClock ” ) ; Código import org . l s u b . c l o c k s e r v e r . C l oc k Se r ve r ; public class Client { p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { try { Registry r e g i s t r y = LocateRegistry . getRegistry ( args [0] , new I n t e g e r ( a r g s [ 1 ] ) ) ; ClockServer c = ( ClockServer ) r e g i s t r y . lookup ( ” MasterClock ” ) ; System . o u t . p r i n t l n ( ” M a s t e r C l o c k s a y s : ” + c . getTime ( ) ) ; } catch ( Exception e ) { System . e r r . p r i n t l n ( ” C l o c k e x c e p t i o n : ” ) ; e . printStackTrace ( ) ; } } } Ejecución 1 Máquina servidora (omac.lsub.org): rmiregistry& 2 Máquina servidora (omac.lsub.org): java -cp rmiclock.jar org.lsub.clockserver.Clock 3 Máquina cliente (ignatz.lsub.org): java -cp rmiclock.jar org.lsub.clockclient.Client omac.lsub.org 9999 Salida: MasterClock says: Fri Mar 15 13:23:18 CET 2013 RMI: descarga dinámica de clases • RMI nos permite descargar clases dinámicamente si el cliente o el servidor no tienen todas las clases necesarias. • Este mecanismo no es imprescindible, lo más sencillo es distribuir un jar con todas las clases. • Complica la configuración y puede suponer un problema de seguridad si no se configura con cuidado → no debemos usarlo si realmente no lo necesitamos. RMI: descarga dinámica de clases (c) Oracle RMI: descarga dinámica de clases • java.rmi.server.codebase es la propiedad que indica la URL que puede usar un cliente para conseguir las clases si no están en su classpath local. • La URL se puede especificar como argumento para la VM al ejecutar: -Djava.rmi.server.codebase=http://example.org/classes.jar RMI: descarga dinámica de clases Hay que instalar un Security Manager en el cliente y en el servidor: • Determina los permisos (p. ej. el acceso al FS) del código descargado dinámicamente. • Es necesario instalar uno para descargar clases. i f ( System . g e t S e c u r i t y M a n a g e r ( ) == n u l l ) { System . s e t S e c u r i t y M a n a g e r ( new S e c u r i t y M a n a g e r ( ) ) ; } RMI: descarga dinámica de clases • Por último, hay que generar ficheros de polı́ticas de seguridad para que el SecurityManager autorice las acciones del código descargado. • El fichero de polı́ticas se puede especificar como argumento para la VM al ejecutar: -Djava.security.policy=file:/home/q/java/sec.policy