PROGRAMACION DISTRIBUIDA Distribución basada en el patrón proxy-servant Héctor Pérez 2 Distribución de aplicaciones basadas en objetos Objetivo: rediseño de una aplicación orientada a objetos para ser desplegada en una plataforma de ejecución distribuida. Particiones: Los objetos de la aplicación se distribuyen en diferentes particiones. Cada partición es un programa independiente que se ejecuta en el procesador que especifique el plan de despliegue. Patrón de diseño Proxy-Servant: Mediante este patrón, el rediseño para la distribución no requiere modificar el código de las clases originales, sino sólo añadir nuevas clases de comunicación: «ClientProxy» y «ServerProxy». Uso de objetos distribuidos: En este caso, el rediseño modifica mínimamente la aplicación orientada a objetos ya diseñada para que pueda ejecutarse sobre una plataforma distribuida. obj-1 obj-3 particiona proc0 Ethernet particionb obj-1 obj-3 proc1 obj-0 obj_2 particionc obj-0 obj-2 proc2
3 Situación no distribuida «main» Principal_Y_Raiz + main(args:string[]) Diseño uncliente elservidor «client» Cliente server «server» Servidor + Client(s:Servidor) + run() + servicio(dato:double): String Ejecución uncliente:cliente JVM elservidor.servicio(43.67) elservidor:servidor Un único nudo procesador Espacio único de direcciones 4 Situación distribuida Despliegue Proc_A IP=[?-?.?.?] PORT=? «client» Cliente + Client(s:Servidor) + run() Proc_B IP=[225.4.3.1] PORT=13634 «server» Servidor + servicio(dato:double): String Ethernet Diseño «main» Principal_A + main(args:string[]) «main» Principal_B + main(args:string[]) Partición B uncliente «client» Cliente + Client(s:Servidor) + run() server «proxy-client» Servidor Partición A + servicio(dato:double): String S Socket S «proxy-server» Servidor_server «server» Servidor + servicio(dato:double): String + Servidor_server(s:Servidor) + run() server
5 Patrón proxy-servant (1/2) Si los objetos pertenecen a diferentes particiones, la invocación directa no es posible Con el patrón proxy, se coloca un nuevo objeto «clientproxy» en la partición del cliente y un nuevo objeto «serverproxy» en la partición del servidor. oper1(dato:double):string uncliente:cliente :Servidor servicio(dato:double):string «clientproxy» Servidor Mensaje [op,dato] Mensaje [op,return] servicio(dato:double):string «serverproxy» Servidor_server 6 Patrón proxy-servant (2/2) El «clientproxy» representa al servidor en la partición cliente ofrece los mismos métodos que el servidor el cliente (sin cambiar su código) lo invoca como si fuera el verdadero servidor Al invocar un método del «clientproxy», éste envía un mensaje por la red al «serverproxy», el cuál realiza por delegación la invocación sobre el servidor. Una vez finalizada la invocación, el «serverproxy» envía el resultado por la red al «clientproxy», el cuál concluye la invocación retornando el resultado al cliente oper1(dato:double):string uncliente:cliente :Servidor servicio(dato:double):string «clientproxy» Servidor Mensaje [op,dato] Mensaje [op,return] servicio(dato:double):string «serverproxy» Servidor_server
7 Ventajas de utilizar el patrón proxy-servant Independiza los aspectos de distribución y comunicación de los de negocio No requiere modificar el código de negocio de la aplicación pero sí requiere conocer las interacciones entre el código (es decir, quién invoca a quién?) Tiene una estructura homogénea y sistemática (siempre se hace lo mismo) y por tanto resulta fácil de automatizar. implementado automáticamente por el middleware 8 MobileTracker: Especificación Telemetría Torre de control Telemetría Coche A Coche B
9 MobileTracker: Diseño 10 MobileTracker: Ejecución Concurrente «active» putposition() «protected» «active» mobile:mobile buffer:buffer follower:follower MobileProcessor FollowerProcessor «active» putposition() «protected» Distribuida mobile:mobile buffer:buffer «active» follower:follower Request message Reply message Ethernet
11 MobileTracker: Distribución por patrón proxy/servant MobileProcessor FollowerProcessor «active» putposition() «protected» mobile:mobile putposition() «proxy-client» buffer:buffer buffer:buffer putposition() «proxy-server» server:buffer_server «active» follower:follower socket Request message socket @IP:Port? Reply message @IP:Port Ethernet 12 MobileTracker: Distribución por patrón proxy/servant Cada Mobile tiene una instancia del proxy del Buffer el proxy proporciona localmente a Mobile el conjunto de operaciones del objeto Buffer que representa (putposition) Cada Follower presenta una instancia del servant del Buffer el servant ejecuta por delegación las operaciones (putposition) en el objeto Buffer Interacciones de Mobile con el objeto Buffer invoca un método del correspondiente proxy (local) el proxy, a través de un socket, se comunica con el correspondiente servant el servant ejecuta la interacción con el objeto Buffer
13 MobileTracker: MobilePartition MobilePartition Buffer putposition() Se crea y conecta el socket Se escriben los parámetros en outputstream Se obtiene el inputstream del socket Se espera valor de retorno Se cierra el socket Se retorna el valor recibido 14 MobileTracker: FollowerPartition Buffer_server: run() (Se repite por cada invocación) FollowerPartition Se espera conexión del cliente Se obtiene el inputstream del socket Se leen los parametros del inputstream Se invoca el servicio requerido Se obtiene el outpustream Se escribe el retorno en outputstream Se ordena la transmisión del mensaje Se cierra el socket
MobileTracker: Generación del código distribuido Progr. concurrente Aplicación distribuida mobilepartition followerpartition Proxy-Server (código) Main partición (código) Proxy-Client (código) Main partición (código) 15 16 Ejemplo: Código de MobileLauncher package mobilepartition; public class MobileLauncher { public static void main(string[] args) { Buffer buffer = new Buffer(); Mobile mobile = new Mobile(buffer); // Se crea el proxy-client // Se crea el objeto de negocio mobile try{ Thread.sleep(50000); catch (InterruptedException e) { mobile.turnoff(); // Se suspende 50s // Finaliza la aplicación
17 Ejemplo: Código del proxy-client Buffer (1/2) package mobilepartition; public class Buffer { byte[] FOLLOWER_IP={127,0,0,1; int PORT=12000; InetAddress SERVER_ADDRESS; // IP del servidor // Puerto del servidor // IP como InetAddress // Constructor public Buffer() { try { // Construye el InetAddress a partir del IP SERVER_ADDRESS=InetAddress.getByAddress (FOLLOWER_IP); catch (UnknownHostException e) { System.out.println( "El computador del servidor es desconocido"); 18 Ejemplo: Código del proxy-client Buffer (2/2) public boolean putposition(long time,double x,double y,double z){ Socket server=null; // Referencia al socket boolean returndata=false; // Dato de retorno try { server =new Socket(SERVER_ADDRESS,PORT); // Se crea el socket con el servidor DataOutputStream dos = new DataOutputStream(server.getOutputStream()); // Se envían los cuatro argumentos como mensaje dos.writelong(time); dos.writedouble(x); dos.writedouble(y); dos.writedouble(z); dos.flush(); // Se ordena que sea transferido de forma imperativa DataInputStream dis = new DataInputStream(server.getInputStream()); returndata= dis.readboolean(); // Se espera a recibir un mensaje (bloqueante) // Se cierran los recursos asociados con las comunicaciones dos.close (); dis.close(); server.close(); catch (IOException e) {System.out.println ("El socket no se ha podido crear"); return returndata; // Se retorna el resultado al cliente
19 Ejemplo: Código de FollowerLauncher package followerpartition; public class FollowerLauncher { public static void main(string[] args) { Buffer buffer = new Buffer(); Follower follower = new Follower(buffer); // Se crea el objeto de negocio Buffer // Se crea el objeto de negocio Follower Buffer_server server = new Buffer_server(buffer); // Se crea el server proxy 20 Ejemplo: Código del proxy server Buffer_server (1/2) package followerpartition; import ; public class Buffer_server extends Thread { // La clase server es activa (Hereda de Thread) int PORT=12000; // Puerto por el que atiende Buffer buffer; // Referencia al objeto de negocio Buffer ServerSocket serversocket; // ServerSocket de espera a la conexión Socket clientsocket; // Referencia al socket que se utiliza // Constructor public Buffer_server(Buffer buffer) { this.buffer=buffer; // Se almacena la referencia recibida del raíz try { serversocket=new ServerSocket(PORT); // Se crea el ServerSocket this.setdaemon(true); // Se declara el thread como demonio start(); // Se inicia la actividad del thread catch (IOException e){ System.out.println("Problema en la creación del ServerSocket");
21 Ejemplo: Código del proxy server Buffer_server (2/2) public void run(){ // Actividad del thread del server while(!interrupted()){ // Itera para todas las invocaciones que se produzcan try { clientsocket = serversocket.accept(); // Espera a que un socket se conecte al puerto // Se obtiene el stream de entrada convertido a DataStream DataInputStream dis = new DataInputStream (clientsocket.getinputstream()); long time=dis.readlong(); // Se leen los valores de los parámetros double x=dis.readdouble(); double y=dis.readdouble(); double z=dis.readdouble(); boolean res=buffer.putposition(time, x, y, z); // Se invoca el metodo de la clase de negocio // Se obtiene el stream de salida convertido a DataStream DataOutputStream dos = new DataOutputStream (clientsocket.getoutputstream()); dos.writeboolean(res); // Se escribe el valor booleano de retorno. dos.flush(); // Se ordena su envio inmediato. dis.close(); dos.close(); // Se cierran los streams catch (IOException e) {System.out.println("Fallo en la ejecución del server"); try {serversocket.close(); // Cuando se cancele el servidor, se cierra el ServerSocket catch (IOException e) {System.out.println("Fallo en el cierre del server"); 22 Serialización de datos mediante streams Java Dada la siguiente invocación: updaterecord (int id, double pos, long time, String label); El envío de un DatagramPacket requiere serializar los parámetros como un array de bytes: En el cliente: {int id, double pos, long time, String label => byte[] data En el servidor: byte[] data => {int id, double pos, long time, String label Esto se puede hace en Java encadenando el uso de dos streams Aplicación writeboolean() writeint() writelong() writeutf(). DataOutputStream ByteArrayOutputStream tobytearray() DatagramPacket byte[] data DatagramPacket byte[] data ByteArrayIntputStream DataIntputStream readboolean() readint() readlong() readutf(). Aplicación
23 Código Java para serializar datos a un array de bytes import java.io.*; public class prustream { public static void main(string[] args) { ByteArrayOutputStream baostream = new ByteArrayOutputStream(); DataOutputStream dostream = new DataOutputStream(baoStream); try { dostream.writeint(48); dostream.writedouble(math.pi); dostream.writelong(1010101010101l); dostream.writeutf("hola"); catch (IOException e) { byte[] ba = baostream.tobytearray(); ByteArrayInputStream baistream = new ByteArrayInputStream(ba); DataInputStream distream = new DataInputStream(baiStream); try { int id=distream.readint(); double pos=distream.readdouble(); long time=distream.readlong(); String label=distream.readutf(); catch (IOException e) {