Download Acceder - Departamento de Lenguajes y Ciencias de la Computación

Document related concepts
no text concepts found
Transcript
DISEÑO DE UNA ARQUITECTURA CLIENTE/SERVIDOR MEDIANTE
OBJETOS DISTRIBUIDOS EN JAVA
José Luis Pastrana Brincones ([email protected])
Dpto. Lenguajes y Ciencias de la Computación. Universidad de Málaga
Abstract
El desarrollo de aplicaciones cliente/servidor usando sockets conlleva el diseño
de un protocolo consistente en un lenguaje común entre el cliente y el servidor. El
diseño de dicho protocolo no siempre es sencillo y es una fuente de errores tales como el
deadlock.
En vez de trabajar directamente con sockets , las aplicaciones cliente/servidor,
pueden ser desarrolladas mediante la invocación de métodos remotos en Java (JavaRMI). Java-RMI es un paquete que puede ser usado para el desarrollo de sistemas
distribuidos. Dicho paquete le permite invocar métodos de otra máquina virtual Java
(posiblemente en otro host). El sistema RMI es muy similar (y generalmente más fácil de
usar) a la llamada a procedimientos remotos (RPC) que podemos encontrar en otros
sistemas. En RMI el programador tiene la sensación de realizar llamadas a métodos
locales de una clase local, mientras el sistema es el encargado de pasar los argumentos,
ejecutar el método y devolver los resultados de la máquina remota al objeto que ha
realizado la llamada.
Uno de los aspectos distintivos de RMI es su simplicidad. El conjunto de
características soportadas por RMI son aquellas que hacen más fácil el desarrollo de
sistemas distribuidos: transparencia en la invocación , recolector automático de basura
distribuido, acceso conveniente a Streams, etc. La invocación remota es transparente ya
que se realiza de idéntica manera que la llamada aun método local, y el recolector
automático de basura distribuido nos libera de la necesidad de preocuparnos por la
liberación de memoria de un objeto que ya no va a ser utilizado con independencia de
que éste sea local o remoto.
Objetivos de Java-RMI
Los objetivos del Java-RMI según se definen en el manual de especificaciones son los
siguientes:
• Soporte de invocación de objetos remotos en diferentes máquinas virtuales Java.
• Soporte de retorno de resultados desde el servidor a los clientes.
• Integrar el modelo de objetos distribuidos en el lenguaje Java de una forma natural,
mientras se mantiene la semántica de objetos ya definidas en Java.
• Realizar aplicaciones distribuidas lo más simples y legibles posibles.
• Preservar la seguridad que provee el runtime system de Java.
RMI y el Modelo de Referencia OSI.
El modelo de referencia OSI establece 7 niveles de para la comunicación por red. En
la siguiente figura se muestra cómo RMI puede ser descrito por dicho modelo.
Nivel de Aplicación
Aplicación de Usuario
cliente
servidor
Stubs
skeletons
Nivel de Presentación
Nivel de Sesión
Nivel de Referencias Remotas
Nivel de Transporte
TCP
Nivel de Red
IP
Nivel de Enlace de datos
Interfaz Hardware
Nivel físico
RED
La aplicación del usuario está en el nivel superior y usa un esquema de representación
de datos para hacer transparente la comunicación con los objetos remotos.
El sistema RMI consiste en 3 etapas:
• El nivel Stub/Skeletons
• El nivel de Referencia Remota.
• El nivel de Transporte.
El nivel Stub/Skeletons es una interfaz entre una aplicación y el resto del sistema RMI.
Su propósito es transferir los datos al nivel de Referencia Remota. Aquí es dónde la
serialización de objetos entra en juego para permitir que los objetos Java sean transmitidos
entre los espacios de direcciones. El nivel de Referencia Remota es responsable del manejo
de la semántica de la invocación, transmitir los datos al nivel de transporte usando una
conexión orientada a Streams (TCP). El nivel de Transporte en la implementación actual de
RMI está basado en TCP, pero podría sustituirse por UDP. Finalmente, el nivel de
Transporte es el responsable de establecer y manejar la conexión.
Desarrollo de Aplicaciones Cliente/Servidor en Java-RMI
El desarrollo de aplicaciones cliente/servidor mediante Java-RMI requiere 6 etapas:
1. Definir una interfaz remota.
2. Implementar dicha interfaz remota. (Servidor).
3. Implementar la aplicación que usa los objetos remotos. (Cliente).
4. Generar los Stubs (Proxies cliente) y Skeletons (entidades servidor).
5. Ejecutar el registry (servidor de nombres).
6. Ejecutar el servidor y los clientes.
Realizaremos un estudio de cada etapa con un ejemplo que nos ayudará a la mejor
comprensión de cada una de ellas. Vamos a realizar un servidor de operaciones para matrices
y vectores y un cliente que hará uso de los servicios que éste nos proporciona.
Definición de la Interfaz Remota.
El primer paso es definir una interfaz remota para los objetos remotos. Esto es
necesario para que el usuario cliente conozca las operaciones que le son permitidas, así como
los tipos, parámetros y clases que puede utilizar.
/* Archivo InterfazMatriz.java Interfaz del servidor remoto de Matrices
*/
public interface InterfazMatriz extends java.rmi.Remote
{
/* Operaciones para Vectores */
public int[] suma(int a[], int b[] ) throws java.rmi.RemoteException;
public int[] resta(int a[], int b[] ) throws java.rmi.RemoteException;
public int[] entero_por(int n, int a[] ) throws java.rmi.RemoteException;
public int producto_escalar(int a[], int b[] ) throws java.rmi.RemoteException;
/* Operaciones para Matrices */
public int[][] suma(int a[][], int b[][] ) throws java.rmi.RemoteException;
public int[][] resta(int a[][], int b[][] ) throws java.rmi.RemoteException;
public int[][] entero_por(int n, int a[][] ) throws java.rmi.RemoteException;
public int[][] producto(int a[][], int b[][] ) throws java.rmi.RemoteException;
public int[][] inversa(int a[][] ) throws java.rmi.RemoteException;
public int determinante(int a[][] ) throws java.rmi.RemoteException;
}
Nótese que la interfaz debe declararse pública para que los clientes puedan usarla, así como
debe heredar de Remote para que el servidor se vea como un objeto remoto. también es
obligatorio que todos los métodos a implementar eleven la excepción RemoteException, ya
que esta se produce en determinados casos como fallos de conexión, o si cae el servidor, etc.
Otro detalle que puede ser interesante es el uso del polimorfismo, se puede ver que
hemos utilizado el mismo nombre de operador para las operaciones de vectores y matrices,
pero la máquina Java sabrá cuál debe usar en función del tipo de sus parámetros.
Ahora deberemos compilar nuestra interfaz, mediante: % javac InterfazMatriz.java
Implementación del Interfaz Remoto.
El siguiente paso en el ciclo de vida del desarrollo de software distribuido mediante
Java-RMI es la implementación del interfaz remoto.
/* Archivo ServerMatriz.java. Implementación del Interfaz del servidor remoto */
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
/* Nuestra clase hereda de UnicastRemoteObject porque va a ser creada
*/
/* para implementar un objeto remoto que usa el mecanismo por defecto
*/
/* de comunicación de RMI.
*/
public class ServerMatriz extends UnicastRemoteObject implements InterfazMatriz
{ private String nombreServidor;
public ServerMatriz ( String s ) throws RemoteException
{ nombreServidor = s; } /* Constructor de la clase. */
/* Operaciones para Vectores */
public int[] suma(int a[], int b[] ) throws RemoteException
{ int c[];
int i;
if (a.length() != b.length()) return null; /* uso null como código de error */
c = new int [a.length()]
for(i=0;i<a.length(); ++i ) c[i] = a[i] + b[i];
return c;
}
.....................................
public static void main(String argv[])
{ ServerMatriz servidor;
System.setSecurityManager( new RMISecurityManager() );
/* Se crea e instala el manejador de seguridad para proteger
/* al servidor de códigos "maliciosos" desde el cliente.
*/
try
{ servidor = new ServerMatriz ( "ServidorMatriz" ); /* Objeto remoto */
Naming.rebind("//NombreMáquina/ServidorMatriz",servidor);
/* Registramos el objeto remoto */
System.out.println("ServidorMatriz instalado en NombreMáquina");
}
catch (Exception e) /* Cualquier tipo de excepción */
{ System.out.println("Se ha producido el siguiente error:");
System.out.println(e.getMessage());
e.printStackTrace();
}
} }
Ahora deberemos compilar nuestra servidor, mediante: % javac ServerMatriz.java
Creación de una aplicación cliente que use el interfaz remoto.
Vamos a realizar un sencillo ejemplo de un cliente que desea realizar la suma de dos
vectores y para ello utilizará el servidor remoto.
/* Archivo DemoMatriz.java */
import java.rmi.*;
import java.net.*;
public class DemoMatriz
{ public static void main(String argv[])
{ int a[] = { 1, 2, 3, 4, 5 },b[] = { 6, 7, 8, 9, 0 }, i , resultado[] = new int[5];
InterfazMatriz servidorRemoto;
try
{ String url = "//NombreMáquina/ServidorMatriz";
/* Referenciamos el objeto remoto servidor */
ServidorRemoto = (InterfazMatriz)Naming.lookup(url);
/* Realizamos la llamada al servidor */
resultado = ServidorRemoto.suma(a,b);
/* Imprimimos los resultados */
System.out.print(“(”);
for(i=0;i<4;++i) System.out.print(a[i] + “, ”);
System.out.print(a[4] + “) + ( ”);
for(i=0;i<4;++i) System.out.print(b[i] + “, ”);
System.out.print(b[4] + “) = ( ”);
for(i=0;i<4;++i) System.out.print(c[i] + “, ”);
System.out.println(c[4] + “)”);
}
catch (Exception e) /* Cualquier tipo de excepción */
{ System.out.println("Se ha producido el siguiente error:");
System.out.println(e.getMessage());
e.printStackTrace();
}
} }
Ahora deberemos compilar nuestro cliente, mediante: % javac DemoMatriz.java
Generación de Stubs y Skeletons.
Ahora estamos preparados para generar los Stubs y Skeletons, para ello deberemos
usar el compilador rmic. Un Stubs es un cliente proxy y un Skeletons es una entidad servidor
que realiza las llamadas al objeto remoto servidor. Estos son cargados dinámicamente cuando
son necesitados en tiempo de ejecución.
Para ello usaremos: % rmic ServerMatriz
Esto generará los archivos ServerMatriz_Skel.class y ServerMatriz_Stub.class.
Nótese que la variable de entorno CLASSPATH debe apuntar al directorio donde están
siendo compiladas y almacenadas las clases para que éstas puedan ser localizadas. Un
ejemplo típico del valor de esta variable sería:
setenv CLASSPATH .:/disk2/softw/jdk1.1.1/lib/classes.zip
RMI registry
Antes de poder ejecutar nuestra aplicación, debemos ejecutar el rmiregistry , que es
un servidor de nombre que permite a los clientes obtener las referencias a los objetos
remotos. Para ello ejecutaremos: % rmiregistry &
El rmiregistry por defecto usa el puerto 1099, si por alguna razón estamos usando
dicho puerto para otro cometido o deseamos utilizar otro puerto diferente, deberemos usar :
% rmiregistry <puerto>&.
En este último caso, deberemos indicar también el número del puerto en la
llamada a Naming.Rebind y Naming.lookup añadiendo ‘:<puerto<‘ al nombre de la máquina.
Ejecución del cliente y el servidor.
Ahora ya estamos preparados para ejecutar el servidor en la máquina remota, para
ello ejecutaremos: % Java ServerMatriz &
Y obtendremos como resultado: ServidorMatriz instalado en NombreMáquina
Posteriormente en nuestra máquina local ejecutaremos el cliente: % Java DemoMatriz
Y obtendremos como resultado:( 1, 2, 3, 4, 5 ) + ( 6, 7, 8, 9, 0 ) = ( 7, 9, 11, 13, 5 )
Serialización de Objetos.
Un tema importante a tener en cuenta es la serialización de objetos. Debido a que
tanto el paso de parámetros como el retorno de resultados se realiza vía Streams, dichos
parámetros deben ser serializables, es decir, que se puedan enviar como una tira de bytes.
Para los tipos y clases standard en Java esto no es ningún problema, ya que son serializables,
el problema viene dado cuando implementamos una clase propia que queremos que sea
enviada como parámetro a un objeto remoto, o recibirla de éste.
Para solucionar esto se debe importar en dicha clase el paquete java.io.Serializable y
definir dicha clase como que implementa la interfaz Serializable.
Conclusiones.
El desarrollo de aplicaciones cliente/servidor mediante sockets conlleva el diseño de
un protocolo o lenguaje de comunicación entre el cliente y el servidor que es fuente de gran
cantidad de errores. Mediante el uso de RMI se consigue una transparencia en la
comunicación entre el cliente y el servidor, trantándose el servidor desde el punto de vista del
cliente como si fuera un objeto local.
Referencias
• Remote Method Invocation System. Sun Microsystem Inc.
• Getting Started using RMI.Sun Microsystem Inc.
• RMI and Object Serialization.Sun Microsystem Inc.
• Distributed Object Programming Using Java.Qusay H. Mahmond.