MinMax – a Java Distributed Application SISTEMI DISTRIBUITI

MinMax – a Java Distributed Application
SISTEMI DISTRIBUITI
Fabrizio G. Ventola
CdL Magistrale Informatica 2012/2013
Introduzione
Uno degli obiettivi dell’informatica moderna è quello di offrire servizi sempre più completi
mediante la risoluzione di grandi istanze di problemi complessi nel minor tempo possibile. Per far
fronte a questo problema si necessita di grandi risorse di calcolo che difficilmente possono risiedere
su un unico elaboratore, di conseguenza si ricorre alla computazione distribuita mediante l’uso di
sistemi distribuiti ove un algoritmo può essere eseguito e scomposto in più processi comunicanti
dislocati su diverse macchine di calcolo. La condivisione di risorse dislocate su più elaboratori ha
posto nuove sfide riguardo la comunicazione tra processi, la sincronizzazione tra loro e la sicurezza
in questi sistemi. I moderni linguaggi di programmazione e relative piattaforme di sviluppo offrono
oramai diversi strumenti che rendono più trasparente allo sviluppatore la gestione delle risorse
distribuite facilitandogli in maniera consistente il compito. Originariamente i linguaggi di
programmazione offrivano esclusivamente le socket per dare la possibilità allo sviluppatore di
realizzare applicazioni di rete senza definire alcun protocollo di supporto per la comunicazione tra
processi dislocati su vari elaboratori. In seguito sono state ideate le RPC – remote procedure call
che forniscono un ulteriore livello di astrazione dando allo sviluppatore la capacità di chiamare
delle procedure di calcolo dislocate quasi come se fossero presenti sull’elaboratore locale.
Successivamente con l’ascesa del paradigma object-oriented si è sentito il bisogno di implementare
qualcosa di simile al meccanismo RPC, adatto alla programmazione di tipo procedurale, portando la
stessa idea su sistemi object-based. Da questo principio è nato il meccanismo RMI – remote method
invocation che consente allo sviluppatore di invocare metodi di oggetti remoti, ossia dislocati
fisicamente su diversi calcolatori, come se fossero metodi di oggetti presenti sulla macchina locale
nello stesso address space del processo client. In questo elaborato si presenta lo sviluppo di una
semplice applicazione distribuita object-based realizzata in Java 6.0 mediante l’utilizzo del
meccanismo Java RMI messo a disposizione dal linguaggio stesso. L’applicazione consente l’invio
da parte di un client di una lista di valori reali ad un oggetto remoto posto sul server per poi dare la
possibilità all’utente di calcolare il valore minimo, massimo e l’ordinamento crescente della lista
dei valori. Le computazioni sono eseguite sul server mediante l’invocazione di metodi remoti da
parte del client per mettere in evidenza le potenzialità del meccanismo RMI messo a disposizione
dal linguaggio di programmazione Java 6.0.
Java RMI
Il linguaggio di programmazione Java è un linguaggio object-oriented pseudo-compilato ovvero
all’atto della compilazione del codice Java viene generato un linguaggio intermedio bytecode che
sarà poi interpretato ed eseguito da una macchina virtuale JVM - Java Virtual Machine. Questo
consente di sviluppare applicazioni platform-indipendent, portabili e di eseguire lo stesso codice su
architetture diverse con l’unico requisito necessario di avere in esecuzione una macchina virtuale
JVM. Un altro aspetto importante del linguaggio Java è dato dalla possibilità di realizzare le
interfacce di classi di oggetti. L’interfaccia permette la netta separazione tra l’implementazione di
una classe e le operazioni possibili sugli oggetti istanze della classe. Questo oltre a determinare i
diversi vantaggi dell’information hiding e dell’incapsulamento consente di dislocare le interfacce e
le implementazioni delle classi su diversi calcolatori venendo così incontro all’esigenza degli
sviluppatori di sistemi distribuiti di avere le istanze della classe che implementa l’interfaccia
sull’object server e l’interfaccia, che definisce le operazioni possibili sugli oggetti di quella classe,
sull’object client. Di conseguenza il client mediante il meccanismo di RMI invoca i metodi degli
oggetti a disposizione sull’object server e riceve i risultati.
Come si è accennato le applicazioni distribuite realizzate mediante Java RMI presentano almeno
due entità principali: l’object client e l’object server. L’object server si preoccupa di ospitare gli
oggetti e metterli a disposizione del client mentre l’object client ha l’onere di localizzare gli oggetti
remoti, instaurare una connessione con l’object server e invocare i metodi degli oggetti remoti
ospitati dall’object server. In Java per realizzare una classe che dia la possibilità di istanziare oggetti
remoti bisogna estendere l’interfaccia java.rmi.Remote e bisogna dotare ogni metodo
dell’interfaccia della clausola di lancio dell’eccezione java.rmi.RemoteException importando i
relativi Java package. L’architettura di un’applicazione distribuita realizzata mediante Java RMI
presenta una struttura a pila formata da tre livelli principali (Figura 1):
•
Stub (client) / Skeleton (server)
•
Remote Reference Layer
•
Transport Layer
Al livello più alto si trova lo stub presente sul client che ha il compito di svolgere da rappresentativa
dell’oggetto remoto che vive sul server, mentre lo skeleton ha la funzione di trasformare la richiesta
remota pervenuta dal client in una richiesta locale all’oggetto presente sul server. Date le funzioni
di intermediazione tra le richieste locali e le richieste remote svolte dallo stub e dallo skeleton
solitamente in letteratura sono denominati anche come proxy. Al livello successivo si pone il
Remote Reference Layer che ha principalmente il compito di effettuare il marshalling e
l’unmarshalling dei parametri. Per marshalling s’intende la codifica dei parametri dei metodi o dei
risultati in un formato che consente l’invio tramite rete degli stessi (o il salvataggio su file) e
l’unmarshalling, come suggerisce il nome, prevede la decodifica di suddetti parametri dei metodi o
dei risultati. Dato che i parametri in Java possono essere o tipi primitivi o oggetti istanze di classi è
necessario che tutti i tipi dei parametri implementino l’interfaccia java.io.Serializable che prevede
dei metodi standard per la serializzazione degli oggetti. In Java i tipi primitivi e le classi fornite
dalla libreria standard implementano tale interfaccia (escluso alcune classi platform-dependent). Nel
livello più basso invece si posizione il Transport Layer che ha l’onere di instaurare e gestire una
connessione “fisica” di rete tra il client e il server. Il sottosistema Java RMI rende trasparente allo
sviluppatore la connessione stratificata tra client e server consentendogli di gestire in maniera più
agile lo sviluppo dell’applicazione a oggetti distribuita fornendo implicitamente e in maniera
trasparente meccanismi di streaming di oggetti e di set-up della connessione Client-Server.
Figura 1: Architettura Java RMI
MinMax - un’applicazione distribuita
Per sviluppare un’applicazione distribuita mediante Java RMI occorre procedere per fasi per
realizzare le varie componenti dell’applicazione. Innanzitutto è necessario realizzare l’interfaccia
remota che definisce i metodi che il client potrà invocare sugli oggetti remoti, indicando il tipo dei
parametri di input e il tipo dei dati dei restituiti dai metodi invocati. Successivamente è necessario
implementare le classi che a loro volto implementano una o più interfacce remote. Le istanze di
questi oggetti saranno ospitate sull’object server pronti per essere invocati dallo skeleton. In seguito
bisogna realizzare la classe client e la classe server, compilarle mediante il compilatore Java,
renderle disponibili in rete mediante il servizio RMI Registry, avviare il server ed infine eseguire il
client.
Componenti
MinMax, come accennato precedentemente, è una semplice applicazione distribuita che fornito in
input una lista di valori reali effettua il calcolo in remoto del valore minimo, del valore massimo e
dell’ordinamento crescente della lista dei valori. L’applicazione è realizzata mediante quattro
componenti (classi Java):
•
MinMax
•
MinMaxImplementation
•
Client
•
Server
MinMax è l’interfaccia della relativa implementazione MinMaxImplementation e definisce i metodi
pubblici invocabili dal client, mentre Client e Server sono le classi che, come suggerisce il nome,
implementano le funzioni di client e server dell’applicazione distribuita. Di seguito è fornito il
codice sorgente Java delle quattro componenti.
Classe Server.java
/**
* @author Fabrizio Ventola
*/
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Server {
private void startServer(String host, int port){
try {
System.setProperty("java.rmi.server.hostname", host);
// create on selected port
Registry registry = LocateRegistry.createRegistry(port);
// create the service called minmax
registry.rebind("minmax", new MinMaxImplementation());
} catch (Exception e) {
System.out.println("Error raised: " + e.getMessage());
}
System.out.println("RMI MinMax server is ready now!");
}
public static void main(String[] args) {
Server server = new Server();
// start the server
server.startServer(args[0], Integer.parseInt(args[1]));
}
}
Classe Client.java
/**
* @author Fabrizio Ventola
*/
import
import
import
import
import
import
java.io.BufferedReader;
java.io.IOException;
java.io.InputStreamReader;
java.rmi.registry.LocateRegistry;
java.rmi.registry.Registry;
java.util.ArrayList;
public class Client {
private void startClient(String host, int port){
try {
// connect to selected host and port
Registry myRegistry = LocateRegistry.getRegistry(host, port);
// look for the service
MinMax minmaxImplementation = (MinMax) myRegistry.lookup("minmax");
boolean terminate = false;
// ask info to the remote object
System.out.println(minmaxImplementation.getInfo());
// show menu
while(!terminate) {
System.out.println("Select one option:");
System.out.println("1: Insert values");
System.out.println("2: Find min value");
System.out.println("3: Find max value");
System.out.println("4: Sort values");
System.out.println("5: Print values");
System.out.println("6: Terminate and quit the client");
System.out.print("Insert your command: ");
// read input from the user
InputStreamReader reader = new InputStreamReader(System.in);
BufferedReader input = new BufferedReader (reader);
String comandInput;
int command = 0;
try {
comandInput = input.readLine();
command = Integer.parseInt(comandInput);
} catch(IOException e){
System.out.println ("Something wrong: " + e.getMessage());
}
switch(command){
// insert values
case 1:{
boolean endInsert = false;
ArrayList<Double> list = new ArrayList();
while (!endInsert) {
System.out.print("Insert a value, put T to finish: ");
String value;
try {
value = input.readLine();
if ("T".equals(value) || "t".equals(value))
endInsert = true;
else
list.add(Double.parseDouble(value));
} catch (IOException e) {
System.out.println ("Something wrong: " +
e.getMessage());
}
}
}
minmaxImplementation.input(list);
System.out.println();
break;
// compute min value
case 2: {
System.out.println("Min value is: " +
minmaxImplementation.min());
System.out.println();
break;
}
// compute max value
case 3: {
System.out.println("Max value is: " +
minmaxImplementation.max());
System.out.println();
break;
}
// compute sorted list
case 4: {
ArrayList<Double> sortedList = minmaxImplementation.sort();
System.out.println("Sorted list is:");
for (Double element : sortedList){
System.out.print(" " + element);
}
System.out.println();
System.out.println();
break;
}
// request values list
case 5: {
ArrayList<Double> listSent =
minmaxImplementation.getList();
System.out.println("Inserted values are:");
for (Double element: listSent){
System.out.print(" " + element);
}
System.out.println();
System.out.println();
break;
}
// exit
case 6: {
System.out.println("Quitting...");
terminate = true;
break;
}
}
}
} catch (Exception e) {
System.out.println ("Something wrong: " + e.getMessage());
}
}
public static void main(String[] args) {
Client client = new Client();
// start the client
client.startClient(args[0], Integer.parseInt(args[1]));
}
}
Classe MinMax.java
/**
* @author Fabrizio Ventola
*/
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.ArrayList;
// interface defines the public methods that client can invoke
public interface MinMax extends Remote {
// information about the distributed application
String getInfo() throws RemoteException;
// gives the values list as input
void input(ArrayList<Double> list) throws RemoteException;
// compute the min value
Double min() throws RemoteException;
// compute the max value
Double max() throws RemoteException;
// compute the sorted list
ArrayList<Double> sort() throws RemoteException;
// get the values list
ArrayList<Double> getList() throws RemoteException;
}
Classe MinMaxImplementation.java
/**
* @author Fabrizio Ventola
*/
import
import
import
import
import
java.rmi.RemoteException;
java.rmi.server.ServerNotActiveException;
java.rmi.server.UnicastRemoteObject;
java.util.ArrayList;
java.util.Collections;
// Implementation of the MinMax interface
// The UnicastRemoteObject class defines a non-replicated remote object whose
references are valid only while the server process is alive.
// The UnicastRemoteObject class provides support for point-to-point active object
references (invocations, parameters, and results) using
// TCP streams.
public class MinMaxImplementation extends UnicastRemoteObject implements MinMax {
private ArrayList<Double> list;
public MinMaxImplementation() throws RemoteException {
}
// gives info about the application
@Override
public String getInfo() throws RemoteException {
String infoMessage = new String("MinMax RMI implementation. MinMax it's a
little application based on Java RMI that makes simple calculations.");
try {
System.out.println ("Info request from " +
UnicastRemoteObject.getClientHost());
} catch (ServerNotActiveException e) {
System.out.println ("Something wrong: " + e.getMessage());
}
return infoMessage;
}
// takes in input the values list
@Override
public void input(ArrayList<Double> list) throws RemoteException {
this.list = list;
try {
System.out.println ("List of values received from " +
UnicastRemoteObject.getClientHost());
} catch (ServerNotActiveException e) {
System.out.println ("Something wrong: " + e.getMessage());
}
}
// computes the min value
@Override
public Double min() throws RemoteException {
Double min = Collections.min(list);
try {
System.out.println ("Min value calculation request from " +
UnicastRemoteObject.getClientHost());
} catch (ServerNotActiveException e) {
System.out.println ("Something wrong: " + e.getMessage());
}
return min;
}
// compute the max value
@Override
public Double max() throws RemoteException {
Double max = Collections.max(list);
try {
System.out.println ("Max value calculation request from " +
UnicastRemoteObject.getClientHost());
} catch (ServerNotActiveException e) {
System.out.println ("Something wrong: " + e.getMessage());
}
return max;
}
// compute the sorted list of values
@Override
public ArrayList<Double> sort() throws RemoteException {
//make a copy of the list of values
ArrayList<Double> sortedList = new ArrayList<Double>(list.size());
for (Double number : list) {
sortedList.add(new Double(number));
}
try {
System.out.println ("Sort calculation request from " +
UnicastRemoteObject.getClientHost());
} catch (ServerNotActiveException e) {
System.out.println ("Something wrong: " + e.getMessage());
}
//sort the list and return the sorted list;
Collections.sort(sortedList);
return sortedList;
}
// gives the original list of values taken in input
@Override
public ArrayList<Double> getList(){
try {
System.out.println ("Sending values list to " +
UnicastRemoteObject.getClientHost());
} catch (ServerNotActiveException e) {
System.out.println ("Something wrong: " + e.getMessage());
}
return this.list;
}
}
Esecuzione dell’applicazione distribuita MinMax
Si presentano di seguito alcuni screenshot durante l’esecuzione dell’applicazione distribuita
MinMax, sia il server sia il client vengono lanciati sulla macchina locale all’indirizzo IP 127.0.0.1
associato all’alias localhost, sulla porta 8080. Si deve considerare che sia il client ed il server
possono essere eseguiti su macchine separate connesse alla rete e che utilizzino lo stack TCP/IP.
Figura 2: Avvio del server Figura 3: Avvio del client e invocazione metodi Figura 4: Client, invocazione di ulteriori metodi Figura 5: Server in esecuzione, arrivo delle richieste dal client Conclusioni
Il sistema fornito da Java per effettuare l’invocazione remota di metodi solleva lo sviluppatore
dall’incarico di realizzare un protocollo di comunicazione tra client e server e di realizzare il codice
per effettuare le operazioni di marshalling e unmarshalling rendendo lo sviluppo di applicazioni a
oggetti distribuite molto più semplice. L’alto livello di astrazione fornito però presenta degli
svantaggi nel caso in cui sia necessario l’accesso concorrente di processi a più oggetti remoti
replicati.
Riferimenti
1. A. Tanenbaum, M. Van Steen Distributed Systems, Principles and Paradigms. PearsonPrentice Hall, 2nd edition, 2006
2. The Oracle Java RMI Tutorial: http://docs.oracle.com/javase/tutorial/rmi/
3. Java SE 6 API Specification: http://docs.oracle.com/javase/6/docs/api/