27/03/2014 Concorrenza multithreading in Java RICHIAMI SU PARALLELISMO 1 Sistemi multi-processore Possiamo scrivere un programma in cui diverse attività (task) evolvono in parallelo da un punto di vista logico Queste possono evolvere in parallelismo fisico In passato erano costosi e riservati ad applicazioni speciali Oggi sistemi multiprocessore sono largamente disponibili a livello di massa Massimo parallelismo fisico ogni attività parallela ha a disposizione un processore fisico altrimenti esse vengono eseguite da processori condivisi secondo modalità decise da uno scheduler, in generale non controllabile dal programmatore 3 4 1 27/03/2014 Caso 1: Task singolo Multitasking su singolo processore Approccio Processore esegue un task Passa velocemente a un altro Sotto il governo dello scheduler Il processore sembra lavorare sui diversi task concorrentemente Caso 2: Due task momenti di inattività o per esaurimento 5 Multitasking a livello di processi 6 Multitasking a livello di thread Processo Programma eseguibile caricato in memoria Ha un suo spazio di indirizzi (variabili e strutture dati in memoria) Ogni processo esegue un diverso programma I processi comunicano via OS, files, rete Può contenere più thread 7 Un thread condivide lo spazio di indirizzi con gli altri thread del processo e comunica via variabili condivise Ha un suo contesto di esecuzione (program counter, variabili locali) 8 2 27/03/2014 Motivazioni ed esempio La suddivisione in processi e task coglie la struttura logica del problema componenti (processi) che interagiscono ogni componente composto da diversi thread LA SOLUZIONE DI JAVA: THREADS Multiple richieste simultanee da web browser da multipli server, ciascuno multi-threaded 9 Costrutti per la concorrenza a livello di linguaggio C e C++ sono linguaggi «single-threaded» Multitasking in C e C++ richiede di appoggiarsi al sistema operativo, con opportune call alle primitive multithread del SO Java contiene primitive multitasking al suo interno (es. Classe Thread). Es.di thread: il Java garbage collector. Ciò consente di scrivere sw multithread senza occuparsi del SO dependent Questo ha consentito sviluppo di librerie di alto livello che semplificano la costruzione di sw multitasking Thread in Java metodo 1 1. Definire una classe che eredita da Thread e contiene il metodo run() class ListSorter extends Thread { public void run() { gli statements del thread che si vuol definire } } 2. Creare istanza della classe ListSorter concSorter = new ListSorter(list1) 3. Invocare il metodo start(), che a sua volta chiama run() lancia il thread e concSorter.start(); ritorna immediatamente12 3 3 27/03/2014 Esempio Esempio public class HelloThread extends Thread { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) { (new HelloThread()).start(); } } 13 14 Nota La classe Thread in realtà implementa metodo run che contiene il codice del thread Su ciò si basa un modo alternativo 15 16 4 27/03/2014 Thread in Java metodo 2 1. Definire una classe che: 1) implementa l'interfaccia Runnable, 2) eventualmente estende altra classe, 3) contiene il metodo run() metodo più class ListSorter implements Runnable { generale si usa se si deve public void run() { gli statements del thread che si vuol definireereditare da } qualche } classe 2. Creare istanza Esempio public class HelloRunnable implements Runnable { public void run() { System.out.println("Hello from a thread!"); } ListSorter concSorter = new ListSorter(list1); public static void main(String args[]) { (new Thread(new HelloRunnable())).start(); } 3. Creare thread Thread myThread = new Thread(concSorter); 4. Attivare metodo run, attraverso metodo start() myThread.start() 17 Ciclo di vita di un thread (semplificato) born La classe Thread prevede anche metodi e caratteristiche che consentono un controllo molto (a volte troppo!) fine della concorrenza, spesso dipendente dal SO e che qui IGNORIAMO, p.es.: start ready scheduler lock 18 Dettagli vari costruito running I/O } sleep(x) sospende il thread per x msec (stato sleeping) interrupt(): inviare interrupt al thread (ad. es. per terminarlo); il suo run() di norma lancia InterruptedException; join, yield, isalive, setName, getName fine I/O fine lock Ogni thread ha priorità (compresa fra Thread.Min_Priority e Thread.Max_Priority, di default Thread.Norm_Priority) blocked dead 19 La priorità è usata dallo scheduler per decidere quali thread sono running 5 27/03/2014 Dati condivisi Può essere necessario imporre che certe sequenze di operazioni che accedono a dati condivisi vengano eseguite dai task in mutua esclusione class ContoCorrente { private float saldo; public ContoCorrente (float saldoIniz) { saldo = saldoIniz; } public void deposito (float soldi) { saldo += soldi; } public void prelievo (float soldi) { saldo -= soldi; } che succede se due task concorrenti cercano l'uno di depositare e l'altro di prelevare dallo stesso conto? 21 Es: Interferenza Esecuzione concorrente di x+=y e x-=y. Ciascun thread esegue tre operazioni: 1. Lettura del valore di x e y 2. Calcolo di espressione (x+y oppure x-y) 3. Scrittura in variabile x -=y può avere uno dei seguenti effetti: - incrementare x di y - lasciare x immutata - decrementare x di y 22 Altro problema di concorrenza Generalizzazione del problema a volte si vuole che certe sequenze di istruzioni vengano eseguite in isolamento, senza interleaving con istruzioni di altre sequenze parallele che altri thread potrebbero eseguire 23 sequenze, si vogliono imporre certi un thread venga eseguita prima (happens Di solito ciò deriva dal fatto di voler garantire certe proprietà di consistenza della memoria (stato della computazione) 24 6 27/03/2014 Esempio Come rendere i metodi "atomici" public class SynchronizedCounter { private int c = 0; La parola chiave "synchronized" public synchronized void increment() { c++; } class ContoCorrente { private float saldo; public ContoCorrente (float saldoIniz) { saldo = saldoIniz; } synchronized public void deposito (float soldi) { saldo += soldi; } synchronized public void prelievo (float soldi) { saldo -= soldi; } 25 public synchronized void decrement() { c--; } public synchronized int value() { return c; } } 26 Commenti sul lock Metodi synchronized Diverse invocazioni di metodi synchronized sullo stesso oggetto non sono soggette a interleaving quanto un thread X esegue un metodo synchronized di un oggetto, i thread che eseguono invocazioni di metodi synchronized sullo stesso oggetto vengono Java associa un intrinsic lock (monitor) a ciascun oggetto che ha almeno un metodo synchronized Quando un metodo synchronized termina, stabilisce invocazione successiva di metodi synchronized sullo stesso oggetto Ogni estensione di object può avere tali metodi I costruttori non possono essere synchronized. Solo il thread che Quando un metodo synchronized è invocato: se nessun metodo synchronized è in esecuzione , l'oggetto viene bloccato (locked) e quindi il metodo viene eseguito se l'oggetto è bloccato, il task chiamante viene sospeso fino a quando il task bloccante libera il lock 27 Warning: attenzione a non far uscire un riferimento prematuro costruzione sia completata NOTA eventuali dati final (che non possono essere modificati dopo 28 la creazione) possono essere letti con metodi non synchronized 7 27/03/2014 Ulteriori commenti sul lock Liveness pratica rilasciato al ritorno (sia normale che eccezionale che da uncaught exception) Invocazione di metodo static synchronized. static è controllato da un lock speciale, diverso da quelli associati alle istanze della classe 29 concorrente viene eseguita entro accettabili limiti di tempo Principali situazioni da evitare attraverso deadlock, starvation e livelock 30 http://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html Deadlock Due e più thread sono bloccati per Esempio: Anna and Giacomo sono amici e credono nel galateo, che dice che se una persona si inchina a un amico, deve restare inchinata fino a che Problema: inchino reciproco allo stesso tempo 31 public class Friend { private final String name; public Friend(String name) { this.name = name; } public String getName() { return this.name; } public synchronized void bow(Friend bower) { System.out.format("%s: %s" + " has bowed to me!%n", this.name, bower.getName()); bower.bowBack(this); } public synchronized void bowBack(Friend bower) { System.out.format("%s: %s" + " has bowed back to me!%n", this.name, bower.getName()); } } final Friend giacomo = new Friend("Giacomo"); new Thread(new Runnable() {public void run() {anna.bow(giacomo);}}).start(); 32 new Thread(new Runnable() {public void run() {giacomo.bow(anna);}}).start(); 8 27/03/2014 Starvation Situazione in cui un thread ha difficoltà a guadagnare accesso a una risorsa condivisa e quindi ha difficoltà a procedere Esempio: Livelock Errore di progetto che genera una sequenza ciclica di operazioni inutili ai computazione Esempio: frequentemente invocano metodi lunghi ritardano costantemente il thread, o uno scheduler che usa priorità cede sempre 33 precedenza ai task greedy Synchronized statements Per ridurre problemi di liveness si possono usare synch. statements , qui this public void addName(String name) { synchronized(this) { lastName = name; nameCount++; } nameList.add(name); } in questo modo si rilascia il lock su this prima di invocare metodo nameList.add, che potrebbe a sua volta richiedere di attendere il rilascio di un lock (potenziali problemi di liveness 35 34 Synchronized statements su oggetti diversi da this Es. classe XXX ha 2 campi c1 e c2, mai usati assieme. I loro update devono essere synchronized, ma non motivo di impedire di farne interleaving: si ridurrebbe la concorrenza con blocchi non necessari public class XXX { private long c1 = 0; private long c2 = 0; private Object lock1 = new Object(); private Object lock2 = new Object(); public void inc1() { synchronized(lock1) { c1++; } } public void inc2() { synchronized(lock2) { c2++; } } invece di usare synchronized methods o sync. Statement su this, si creano due oggetti solo per fornire un lock } 36 9 27/03/2014 Synchronized statements: Precondizioni per metodi synchronized: guarded blocks Utile in pratica, ma richiede MOLTA attenzione Come evitare il prelievo se il conto va "in rosso"? interleaving degli update di c1 e c2? Controllo così fine spesso induce errori di programmazione, o comunque risulta molto complesso class ContoCorrente { private float saldo; synchronized public void prelievo (float soldi) { while (saldo-soldi<0) wait(); saldo -= soldi; } rilascia il lock sull'oggetto e sospende il 38 task 37 Come risvegliare un task in wait? class ContoCorrente { private float saldo; public ContoCorrente (float saldoIniz) { saldo = saldoIniz; } synchronized public void deposito (float soldi) { saldo += soldi; risveglia un task sospeso notify(); in wait, se esiste nondeterminismo } synchronized public void prelievo (float soldi) { while (saldo-soldi<0) wait(); saldo -= soldi; Attenzione! } if (saldo-soldi<0) wait(); Non è sufficiente: il saldo potrebbe non 39 essere diventato positivo Esempio: una coda fifo condivisa Operazione di inserimento di elemento: sospende task se coda piena while (codaPiena()) wait(); al termine notify(); Operazione di estrazione di elemento: sospende task se coda vuota while (codaVuota()) wait(); al termine notify(); invece notifyAll risveglia tutti i task in sospeso MA uno solo guadagna il lock 40 10 27/03/2014 Schema produttore/consumatore Attenzione ai dettagli risvegliato solo da un notify o notifyAll altrimenti resta in wait per sempre oggetto locked senza bisogno di notify. Chiamando wait() occorre in realtà anche catturare InterruptedException (lanciata se un altro thread invoca metodo interrupt sul thread in sospeso) public synchronized String take() { public synchronized void put(String message) { // Wait until message is // Wait until message has // available. // been retrieved. while (empty) { while (!empty) { try { try { wait(); wait(); } catch (InterruptedException e) {} } catch (InterruptedException e) {} } } // Toggle status. // Toggle status. empty = true; empty = false; // Notify producer that // Store message. // status has changed. this.message = message; notifyAll(); // Notify consumer that status return message; // has changed. } notifyAll(); } 42 Ciclo di vita «completo» di un thread born // Values must be between 0 and 255. private int red; private int green; private int blue; private String name; start notify notifyAll ready scheduler wait Problemi con oggetti mutabili Da http://docs.oracle.com/javase/tutorial/essential/concurrency/syncrgb.html public class SynchronizedRGB { running I/O lock waiting private void check(int red, int green,int blue) { if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { throw new IllegalArgumentException(); } } fine I/O fine lock blocked dead 43 public void set(int red, int green,int blue, String name) { check(red, green, blue); synchronized (this) { this.red = red; this.green = green; this.blue = blue; this.name = name; } } public synchronized int getRGB() { //calcola intero che rappresenta il colore con manipolazione // rappresentazioni binarie: bitwise inclusive or | e shift sx << ||es. 4|1 dà 5, << X esegue shift a sinistra di X posizioni return ((red << 16) | (green << 8) | blue); } public synchronized String getName(){ return name; } public SynchronizedRGB(int red, int green, int blue, String name) { check(red, green, blue); this.red = red; this.green = green; this.blue = blue; this.name = name; } public synchronized void invert() { red = 255 - red; green = 255 - green; blue = 255 - blue; name = "Inverse of " + name; 44 } } 11 27/03/2014 Possibile inconsistenza SynchronizedRGB color = new SynchronizedRGB(0, 0, 0, "Pitch Black"); int myColorInt = color.getRGB(); //Statement 1 String myColorName = color.getName(); //Statement 2 Se un altro thread invoca color.set dopo Statement 1 ma prima di Statement 2, il valore di myColorInt non corrisponde al valore di myColorName. Fix: legare assieme i due statement in un unico synchronized statement synchronized (color) { int myColorInt = color.getRGB(); String myColorName = color.getName(); } Ma abbiamo già visto che alla lunga questo approccio può dare problemi Il problema può sorgere solo Allora si può definire un SynchronizedRGB immutabile 45 Come creare oggetti immutabili Non fornire metodi "setter" Definire tutti gli attributi di istanza final e private Non consentire alle sottoclassi di fare override dei metodi (p.es. dichiarando la classe final oppure dichiarando il costruttore private e costruendo gli oggetti mediante metodi statici, detti factory methods) Se gli attributi di istanza hanno riferimenti a oggetti mutabili, non consentire la modifica di questi ultimi: - Non fornire metodi che modificano oggetti mutabili - Non fare sharing di ref. a oggetti mutabili. Non salvare ref. a oggetti esterni mutabili passati al costruttore, se necessario fare copie e salvare ref. alle copie. Inoltre 46 creare copie degli oggetti interni mutabili se necessario per evitare di restituire gli originali attraverso i metodi Soluzione ImmutableRGB Esempio ImmutableRGB final public class ImmutableRGB { Nella versione mutabile ci sono due metodi setter. Il primo (set avrà alcun corrispettivo nella versione immutabile. Il secondo, invert, viene adattato creando un nuovo oggetto Tutti gli attributi sono già private; vengono ulteriormente qualificati final La classe viene qualificata final Un solo attributo fa riferimento a un oggetto, e nulla per salvaguardare lo stato di eventuali oggetti mutabili contenuti 47 // Values must be between 0 and 255. final private int red; final private int green; final private int blue; final private String name; private void check(int red, int green, int blue) { if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { throw new IllegalArgumentException(); } } public ImmutableRGB(int red, int green, int blue, String name) { check(red, green, blue); this.red = red; this.green = green; this.blue = blue; this.name = name; } public int getRGB() { return ((red << 16) | (green << 8) | blue); } public String getName() { return name; } public ImmutableRGB invert() { return new ImmutableRGB(255 - red, 255 - green, 255 - blue, "Inverse of " + name); } } 48 12 27/03/2014 Concetti avanzati: java.util.concurrent Lock objects Finora abbiamo visto i concetti di base, che però risultano Sono «di basso livello» e richiedono al programmatore di scendere troppo nei dettagli. Il codice synchronized definisce un caso elementare di lock (implicito), ma meccanismi più sofisticati sono forniti dal package java.util.concurrent.locks lock, può essere acquisito Vediamo ora concetti introdotti con Java 5.0, in particolare nei packages java.util.concurrent associati a codice synchronized Esecutori Collezioni concorrenti Variabili atomiche il metodo trylock esce dal lock se questo non è disponibile (immediatamente o dopo un timeout da specificare) il metodo lockInterruptibly consente il ritiro se un altro thread manda un interrupt prima che il lock sia acquisito 49 50 Esempio (2) Esempio: Anna, Giacomo e 2. La classe Friend restituisce vero se riesce ad acquisire il lock di ciascuno dei due attori che consentono di inchinarsi e di restituire class Friend { public void bow(Friend bower) { if (impendingBow(bower)) { try { 1: Creazione dei thread bower.bowBack(this); } finally {//in uscita si rilasciano i lock di this e del bower: lock.unlock(); bower.lock.unlock(); } } else { System.out.format("%s: %s started« + " to bow to me, but saw that" + " I was already bowing to" public static void main(String[] args) { final Friend giacomo = new Friend("Giacomo"); final Friend anna = new Friend("Anna"); new Thread(new BowLoop(giacomo, anna)).start(); new Thread(new BowLoop(anna, giacomo)).start(); } } } public void bowBack(Friend bower) { System.out.format("%s: %s has" + " bowed back to me!%n", this.name, bower.getName()); } giacomo e anna ad intervalli d tempo casuali } 51 52 13 27/03/2014 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.Random; public class Safelock { static class Friend { private final String name; private final Lock lock = new ReentrantLock(); Esempio (3) 2. Ancora la classe Friend il metodo impendingBow class Friend { public Friend(String name) { this.name = name; } public String getName() { return this.name; } public boolean impendingBow(Friend bower) { Boolean myLock = false; Boolean yourLock = false; try { myLock = lock.tryLock(); yourLock = bower.lock.tryLock(); } finally { if (! (myLock && yourLock)) { if (myLock) { lock.unlock(); } if (yourLock) { bower.lock.unlock(); } } } return myLock && yourLock; } public boolean impendingBow(Friend bower) { Boolean myLock = false; Boolean yourLock = false; try { myLock = lock.tryLock(); yourLock = bower.lock.tryLock(); } finally { if (! (myLock && yourLock)) { if (myLock) { lock.unlock(); } if (yourLock) { bower.lock.unlock(); } } } return myLock && yourLock; } 53 public void bow(Friend bower) { if (impendingBow(bower)) { try { System.out.format("%s: %s has" + " bowed to me!%n", this.name, bower.getName()); bower.bowBack(this); } finally { lock.unlock(); bower.lock.unlock(); } } else { System.out.format("%s: %s started" + " to bow to me, but saw that" + " I was already bowing to" + " him.%n", this.name, bower.getName()); } } Codice completo PARTE 1 54 static class BowLoop implements Runnable { private Friend bower; private Friend bowee; public BowLoop(Friend bower, Friend bowee) { this.bower = bower; this.bowee = bowee; } public void run() { Random random = new Random(); for (;;) { try { Thread.sleep(random.nextInt(10)); } catch (InterruptedException e) {} bowee.bow(bower); } } Codice completo PARTE 2 Codice completo PARTE 3 } public void bowBack(Friend bower) { System.out.format("%s: %s has" + " bowed back to me!%n", this.name, bower.getName()); } public static void main(String[] args) { final Friend alphonse = new Friend("Alphonse"); final Friend gaston = new Friend("Gaston"); new Thread(new BowLoop(alphonse, gaston)).start(); new Thread(new BowLoop(gaston, alphonse)).start(); } } 55 } 56 14 27/03/2014 Esecutori Gli strumenti finora disponibili impongono una stretta relazione tra il compito che deve essere Runnable Thread I due concetti possono essere tenuti distinti in applicazioni complesse, mediante interfacce Executor thread pools fork/join Gli esecutori sono predefiniti e consentono una gestione efficiente che riduce il pesanti overhead dovuto alla gestione dei thread57 Interfacce Executors Il package java.util.concurrent definisce 3 interfacce Executor ExecutorService (estende Executor) ScheduledExecutorService (estende ExecutorService) sovraccarico delle prestazioni associato ad inizializzare un nuovo thread, assegnargli memoria memoria per lo stack ecc Un server che riceve continuamente richieste potrebbe bloccarsi completamente Invece di avviare un nuovo thread per ogni task, una task è passata a un thread pool. Non appena il thread pool ha qualche thread inattivo, il task gli viene assegnato ed eseguito. Uso tipico per i server: ogni connessione in entrata è passata al pool. Implementazione di Executors Executors usano thread pools, che consistono di thread che esistono al di fuori di Runnable Un esempio comune è Utilizzo di Executor Se r è un Runnable ed e è un Executor, invece di (new Thread(r)).start(); facciamo xecutor chiamando il factory method newFixedThreadPool della classe java.util.concurrent.Executors I task sono inviati al pool attraverso una coda (BlockingQueue) e.execute(r); thread Thread Pools Utili per limitare il numero di thread in esecuzione allo stesso tempo. 59 60 15 27/03/2014 Collezioni concorrenti Cenno al Fork/Join framework altra implementazione di ExecutorService, utile nel caso di più processori Consente di distribuire task a elementi di un thread pool secondo uno schema ricorsivo: Il package java.util.concurrent include estensioni a collezioni di Java, quali BlockingQueue struttura dati FIFO che blocca thread se si cerca di inserire in coda piena o estrarre da coda vuota Utile come buffer per produttore/consumatore ConcurrentMap consente in maniera atomica di eliminare/modificare una coppia chiave-valore solo se chiave presente e aggiungere chiavevalore solo se assente if (il mio task è abbastanza piccolo) eseguo direttamente else spezzo il mio task in due task invoco i due pezzi e attendo il risultato 61 62 Esempio Variabili atomiche class Counter { private int c = 0; Il package java.util.atomic definisce classi che supportano operazioni atomiche su singole variabili Esse posseggono tutte metodi get e set che si comportano come lettura e scrittura delle corrispondenti variabili non atomiche compareAndSet } 63 class SynchronizedCounter { private int c = 0; public void increment() { c++; } public synchronized void increment() { c++; } public void decrement() { c--; } public synchronized void decrement() { c--; } public int value() { return c; } public synchronized int value() { return c; } } Come evitare problemi di interferenza? 64 16 27/03/2014 Oggetti atomici import java.util.concurrent.atomic.AtomicInteger; class AtomicCounter { private AtomicInteger c = new AtomicInteger(0); public void increment() { c.incrementAndGet(); } Programmazione distribuita public void decrement() { c.decrementAndGet(); } public int value() { return c.get(); } Consentono di evitare i problemi di liveness che possono essere causati dei metodi synchronized 65 Obiettivo 66 Nodi fisici e nodi logici La rete come architettura fisica programmabile Poter allocare computazioni su nodi fisici diversi e consentire il coordinamento tra le computazioni sui diversi nodi 67 Occorre distinguere tra nodi fisici e logici Può essere opportuno progettare Java consente di addirittura di vedere tutto attraverso la nozione di oggetti e di invocazione di metodi, dove 68 17 27/03/2014 Architettura client-server Middleware È il modo classico di progettare applicazioni distribuite su rete Server offre un servizio "centralizzato" attende che altri (client) li contattino per ottenere un servizio esempio: web server Client si rivolge ad apposito/i server per ottenere certi servizi esempio: browser www 69 Per programmare un sistema distribuito vengono forniti servizi specifici, come estensione del sistema operativo Il middleware viene posto tra il sistema operativo e le applicazioni In Java il middleware fa parte del linguaggio, diversamente da altri middleware (es. CORBA) 14 70 15 Socket in Java Middleware package java.net, classi: Socket, ServerSocket Client e server comunicano (pacchetti TCP) attraverso socket (attacchi) socket client 71 socket server Endpoint individuati da: indirizzo IP numero di porta (uno per ogni applicazione) 72 18 27/03/2014 Comunicazione client-server Socket come stream Apertura, lettura e scrittura di socket del tutto simili a gestione IO con stream Differenza: Metodo accept per mettersi in ascolto Metodo connect per iniziare una connessione 1 2 stream di input, da cui leggere, e uno stream di output su cui scrivere 3 1. il ServerSocket del server si mette in attesa di una connessione 2. il socket del client si collega al server socket 3. viene creato un socket nel server e quindi stabilito un canale di comunicazione con i l client Metodi getInputStream e getOutputStream. Attenzione a UDP che non ha gestione degli errori 73 Aprire connessione (lato client) Attesa connessione (lato server) 1. java.net.ServerSocket specificando il numero di porta su cui rimanere in ascolto ServerSocket serverSocket=new ServerSocket(4567); 2. Chiamare il metodo accept() che fa in modo che il server rimanga in ascolto di una richiesta di connessione (la porta non deve essere già in uso) Socket mioSocket=serverSocket.accept(); 3. Quando il metodo completa la sua esecuzione la connessione col client è stabilita e viene restituita 75 1. Aprire un socket specificando indirizzo IP e numero di porta del server Socket socket = new Socket("127.0.0.1", 4567) 2. ascolto un processo server Socket socket=serverSocket.accept(); 3. Se la connessione ha successo si usano (sia dal lato client che dal lato server) gli stream associati al socket per permettere la comunicazione tra client e server (e viceversa) Scanner in = new Scanner(socket.getInputStream()); PrintWriter out = new PrintWriter(socket.getOutputStream()); 76 19 27/03/2014 Esempio di server: EchoServer Si crei un server che accetta connessioni TCP sulla porta 1337 Una volta accettata la connessione il server leggerà ciò che viene scritto una riga alla volta e ripeterà nella stessa connessione ciò che è stato scritto. esecuzione public class EchoServer { private int port; public void startServer() throws IOException { serverSocket = new ServerSocket(port); // apro una porta TCP System.out.println("Server socket ready on port: " + port); Socket socket = serverSocket.accept(); // resto in attesa di una connessione System.out.println("Received client connection"); // apro gli stream di input e output per leggere e scrivere nella connessione appena ricevuta: Scanner in = new Scanner(socket.getInputStream()); PrintWriter out = new PrintWriter(socket.getOutputStream()); while (true) {// leggo e scrivo nella connessione finche' non ricevo "quit": String line = in.nextLine(); if (line.equals("quit")) { break; } else { out.println("Received: " + line); out.flush(); } } System.out.println("Closing sockets"); // chiudo gli stream e il socket in.close(); out.close(); socket.close(); serverSocket.close(); } 77 Esempio di client: LineClient Si crei un client che si colleghi, con protocollo TCP, alla porta 1337 Una volta stabilita la connessione il client legge una riga alla volta dallo standard input e invia il testo digitato al server Il client inoltre stampa sullo standard output le risposte ottenute dal server Il client deve terminare quando il server 79 chiude la connessione 78 public class LineClient { private String ip; private int port; public void startClient() throws IOException { Socket socket = new Socket(ip, port); System.out.println("Connection established"); Scanner socketIn = new Scanner(socket.getInputStream()); PrintWriter socketOut = new PrintWriter(socket.getOutputStream()); Scanner stdin = new Scanner(System.in); try { while (true) { String inputLine = stdin.nextLine(); socketOut.println(inputLine); socketOut.flush(); String socketLine = socketIn.nextLine(); System.out.println(socketLine); } } catch(NoSuchElementException e) { System.out.println("Connection closed"); } finally { stdin.close(); socketIn.close(); socketOut.close(); socket.close(); } } 80 20 27/03/2014 Creare EchoServer e LineClient public class LineClient { public class EchoServer { private String ip; private int port; private int port; private ServerSocket serverSocket; public void startClient() throws IOException { public EchoServer(int port) { public LineClient(String ip, int port) { this.port = port; this.ip = ip; } this.port = port; public static void main(String[] args) { } EchoServer server = new public static void main(String[] args) { EchoServer(1337); LineClient client = new try { LineClient("127.0.0.1", 1337); server.startServer(); try { } catch (IOException e) { client.startClient(); System.err.println(e.getMessage()); } catch (IOException e) { } System.err.println(e.getMessage()); } } } } } Serializzazione E se devo inviare oggetti? Posso inviare tramite socket anche un oggetto a patto che sia «serializzabili». Serializzazione=trasformazione di un oggetto in un byte stream Serializable 81 Es. serializzazione ClasseSerializzabile o = new ClasseSerializzabile (); //Decorazione flusso uscita del socket ObjectOutputStream objOut = new ObjectOutputStream(socket.getOutputStream()); objOut.writeObject(o); //Decorazione flusso uscita del socket: ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); //Lettura di un oggetto dal socket: o = (ClasseSerializzabile)objInputStream.readObject(); Architettura del server Il server che abbiamo visto accetta una sola connessione (da un solo client) Un server dovrebbe essere in grado di accettare connessioni da diversi client e di In questo modo è possibile accettare più client contemporaneamente 84 21 27/03/2014 Esempio: EchoServer multithread Spostiamo la logica che gestisce la comunicazione con il client in una nuova classe ClientHandler che implementa Runnable La classe principale del server si occupa solo di istanziare il ServerSocket, eseguire la accept() e di creare i thread necessari per gestire le connessioni accettate La classe ClientHandler si occupa di gestire la comunicazione con il client associato al socket assegnato 85 EchoServer multi-thread (Client handler) public class EchoServerClientHandler implements Runnable { private Socket socket; public EchoServerClientHandler(Socket socket) { this.socket = socket; } public void run() { try { Scanner in = new Scanner(socket.getInputStream()); PrintWriter out = new PrintWriter(socket.getOutputStream()); // leggo e scrivo nella connessione finche' non ricevo "quit" while (true) { String line = in.nextLine(); if (line.equals("quit")) { break; } else { out.println("Received: " + line); out.flush(); } } // chiudo gli stream e il socket in.close(); out.close(); socket.close(); } catch (IOException e) { System.err.println(e.getMessage()); } } 86 } EchoServer multi-thread (Server) public class MultiEchoServer { private int port; public MultiEchoServer(int port) { this.port = port; } public void startServer() { ExecutorService executor = Executors.newCachedThreadPool(); ServerSocket serverSocket; try { serverSocket = new ServerSocket(port); } catch (IOException e) { System.err.println(e.getMessage()); // porta non disponibile return; } System.out.println("Server ready"); while (true) { try { Socket socket = serverSocket.accept(); executor.submit(new EchoServerClientHandler(socket)); } catch(IOException e) { break; // entrerei qui se serverSocket venisse chiuso } } executor.shutdown(); } public static void main(String[] args) { MultiEchoServer echoServer = new MultiEchoServer(1337); echoServer.startServer(); } 87 } Chiusura connessioni Per chiudere un ServerSocket o un Socket si utilizza il metodo close() Per ServerSocket, close() fa terminare la accept() con IOException Per Socket, close() fa terminare le operazioni di lettura o scrittura del socket con eccezioni che dipendono dal tipo di reader/writer utilizzato Sia ServerSocket sia Socket hanno un metodo isClosed() che restituisce vero se il socket è stato chiuso 88 22 27/03/2014 RMI Remote Method Invocation Middleware che porta i vantaggi della programmazione orientata gli oggetti nel contesto distribuito Si possono essere invocati metodi su oggetti remoti, per i quali si ha un riferimento remoto Aspetti innovativi passaggio dei parametri per valore (per oggetti NON REMOTI) e indirizzo (oggetti REMOTI) possibilità di download automatico dalla rete delle classi necessarie per la valutazione remota 89 Architettura client-server con RMI Caso tipico Server crea oggetti remoti, li rende visibili e aspetta che i client invochino metodi su di essi Client ottiene riferimenti a oggetti remoti e invoca metodi su di essi RMI fornisce il meccanismo con cui il server e i client comunicano per costituire l'applicazione distribuita 90 Architettura "esterna" viene usato anche un normale server web per caricare (dinamicamente) le classi (in bytecode), da client a 91 92 23 27/03/2014 Funzionalità di RMI (1) Funzionalità di RMI (2) Localizzazione di oggetti remoti Gli oggetti remoti sono registrati presso un registro di nomi (rmiregistry) che fornisce una "naming facility", oppure Le operazioni passano come parametri e/o restituiscono riferimenti a oggetti remoti Comunicazione con oggetti remoti Client ottiene servizi dal server invocando metodi di oggetti remoti con una sintassi identica a quella per gli oggetti locali (residenti sulla stessa JVM), Dynamic class loading essendo possibile passare oggetti ai metodi di oggetti remoti, oltre a trasmettere i valori dei parametri, RMI consente di trasferire il codice degli oggetti a run time (potrebbe trattarsi di un nuovo sottotipo) È un esempio di codice mobile 93 94 Definizioni Vincoli Oggetto remoto: oggetto i cui metodi possono essere Le interfacce che definiscono le funzionalità degli oggetti remoti (cioè resi accessibili da altri oggetti non locali tramite il sistema RMI) devono ereditare da java.rmi.Remote I metodi definiti in tali interfacce devono dichiarare l'eccezione java.rmi.RemoteException 95 risiede Interfaccia remota: interfaccia che dichiara quali sono i metodi che possono essere invocati da una diversa JVM Server: insieme di uno o più oggetti remoti che, implementando una o più interfacce remote, offrono delle risorse (dati e/o procedure) a macchine esterne distribuite sulla rete Remote Method Invocation (RMI): invocazione di un metodo presente in una interfaccia remota implementata da un oggetto remoto (sintassi di invocazione 96 remota è identica a quella locale) 24 27/03/2014 Registry Architettura interna È un programma che viene lanciato dalla finestra dei comandi Il client colloquia con un proxy (ossia un «rappresentante») locale del server, detto stub rmiregistry -JDjava.rmi.server.hostname=0.0.0.0 È un programma di rete che ascolta su Occorre dargli comando per realizzare il binding tra l'oggetto locale esportato come remoto e il nome col quale ad esso si riferiscono i client lo stub rappresenta il server sul lato client implementa l'interfaccia del server è capace di fare forward di chiamate di metodi il client ha un riferimento locale all'oggetto stub Esiste anche un proxy del client sul lato server, detto skeleton è una rappresentazione del client chiama i servizi del server sa come fare forward dei risultati lo skeleton ha un riferimento all'oggetto 97 98 Architettura interna Il client colloquia con un proxy locale del server, detto stub lo stub rappresenta il server sul lato client implementa l'interfaccia del server è capace di fare forward di chiamate di metodi il client ha un riferimento locale all'oggetto stub Esiste anche un proxy del client sul lato server, detto skeleton è una rappresentazione del client chiama i servizi del server sa come fare forward dei risultati lo skeleton ha un riferimento all'oggetto 99 100 25 27/03/2014 Fasi di creazione di un sistema Creazione di interfaccia remota import java.rmi.Remote; import java.rmi.RemoteException; public interface HelloServer extends Remote { String sayHello() throws RemoteException; } 1. Definire una INTERFACCIA remota per la comunicazione tra client e server 2. server convenzione: stesso nome dell'interf. e suffisso Impl Un'interfaccia remota 1. deve essere pubblica 2. deve estendere java.rmi.Remote 3. ogni metodo deve dichiarare java.rmi. RemoteException 4. ogni oggetto remoto passato come parametro o valore di ritorno deve essere dichiarato di tipo interfaccia remota 102 3. Definire client che usa un riferimento all'interfaccia remota per accedere all'oggetto remoto 4. Compilare ed eseguire 101 Lato server obj stub usando la classe java.rmi.server.UnicastRemoteObject: Lato server: esempio IR. IR stub = (IR) UnicastRemoteObject.exportObject(obj, 0); import java.rmi.registry.Registry; import java.rmi.registry.LocateRegistry; import java.rmi.server.UnicastRemoteObject; public class HelloServerImpl implements HelloServer { public String sayHello() { return "Hello, world!"; } 3. Registrare lo stub nel "registry" obj potrebbe avere metodi che restituiscono riferimenti agli altri oggetti remoti, evitando così di dover passare attraverso il registry per accedere ad essi. Registry registry = LocateRegistry.getRegistry(); NOME Quando si esegue il server bisogna specificare come parametro della JVM: -Djava.rmi.server.codebase=file:classDir/ dove classDir è la directory che contiene i file compilati (deve essere raggiungibile sia dal client che dal registry!). In alternativa bisogna rendere i file compilati disponibili nel classpath del client e del registry. 103 public static void main(String args[]) { try { HelloServerImpl helloServer = new HelloServerImpl(); HelloServer helloServerStub = (HelloServer) UnicastRemoteObject.exportObject(helloServer, 0); nel // registry e lo identifica con la stringa "Hello". Registry registry = LocateRegistry.getRegistry(); registry.rebind("Hello", helloServerStub); System.err.println("Server ready"); } catch (Exception e) { System.err.println("Server exception: " + e.toString()); e.printStackTrace(); } 104 26 27/03/2014 package distrib1.rmi; evita il rischio di "bind"qualora import java.rmi.*; esista già un oggetto import java.rmi.server.*; registrato import java.rmi.registry.*; con lo stesso nome import java.net.*; public class PerfectTimeImpl extends UnicastRemoteObject implements PerfectTime { public long getPerfectTime() throws RemoteException { return System.currentTimeMillis(); } public PerfectTimeImpl() throws RemoteException { super(); //inutile, peraltro } public static void main (String args[]) throws Exception { System.setSecurityManager(new RMISecurityManager()); PerfectTime pt = new PerfectTimeImpl(); Naming.rebind ("//localhost/PerfectTimeServer", pt); System.out.println("Ready to do time"); stringa di registrazione: } 106 //host:porta/OggettoRem } Registry È un programma che viene lanciato dalla finestra dei comandi rmiregistry È un programma di rete che ascolta su una porta, per default "1099" Occorre dargli comando per realizzare il binding tra il l'oggetto locale esportato come remoto e il nome col quale ad esso si riferiscono i client 105 33 se registry sul server: //localhost/OggettoRem Uso dell'oggetto remoto Vita degli oggetti remoti Se main() termina, l'oggetto remoto registrato continua ad esistere finchè rmiregistry resta running Ecco perché è necessario fare rebind (potrebbe esistere un oggetto con lo stesso nome) Si può anche fare 107 package distrib1.rmi; import java.rmi.*; import java.rmi.registry.*; public class DisplayPerfectTime { public static void main(String[] args) throws Exception { System.setSecurityManager(new RMISecurityManager()); //lega oggetto locale a remoto PerfectTime t=(PerfectTime) Naming.lookup("//localhost/PerfectTimeServer"); for(int i=0; i<10; i++) System.out.println("Perfect time = "+ t.getPerfectTime()); } } 34 108 35 27 27/03/2014 Parametri Serializzazione Semantica dell'invocazione di un metodo remoto m(obj) se obj remoto, normale semantica se no, m opera su una copia serializzata di obj; eventuali modifiche allo stato di obj hanno effetto solo sulla copia, non sull'originale in pratica il passaggio di oggetti non remoti è per valore, mentre per gli oggetti remoti è per riferimento 109 E` la trasformazione di un oggetto in un byte stream E` possibile effettuarla su oggetti che implementano Oggetti passati a, o ritornati da, un oggetto remoto devono implementare l'interfaccia Serializable se invece si passano riferimenti remoti, basta implementare Remote Stubs e skeletons effettuano automaticamente la serializzazione e deserializzazione si dice che fanno il "marshal" (schieramento) e "unmarshal" di argomenti e risultati 36 110 37 Scaricamento da remoto delle classi Che cosa dobbiamo fare noi? Il tool rmic (rmi compiler) crea i files necessari per stub e skeleton Quando si usa RMI è possibile scaricare dal sito del server: rmic distrib1.rmi.PerfectTime genera due classi: Gli stub degli oggetti remoti Le classi corrispondenti ai parametri PerfectTime_Stub.class PerfectTime_Skel.class Per esempio, il client riceve un valore di ritorno da una chiamata remota che ha un certo tipo statico noto al client, ed un tipo dinamico non noto (in Java 2 solo stub) 111 38 112 39 28 27/03/2014 Scaricamento da remoto delle classi Security manager e file di policy Le classi possono essere scaricate attraverso un web server Per indicare il web server che si utilizzerà, si lancia il server con il seguente comando: SecurityManager è una classe in java.lang che offre metodi per controllare la possibilità di accesso a varie risorse (file, socket,...) RMI usa un security manager per controllare i permessi di uso dei socket da parte di java -Djava.rmi.server.codebase= "http://brahms:8000/" PerfectTimeImpl Perchè client e server possano istanziare correttamente le classi scaricate, è necessario configurare il security manager Un utente può definire le politiche di sicurezza editando un file di policy Questo impedisce alle classi scaricate di eseguire azioni non legali 113 40 Un esempio di file di policy 114 41 Come "far partire" il tutto? grant { Far partire il registry Se necessario, far partire il web server Far partire il server indicando il file di policy da usare. Per esempio: } java Djava.security.policy=./distrib1/rmi/permit distrib1.rmi.PerfectTimeImpl Far partire il/i client indicando il file di policy da usare 115 42 116 43 29
© Copyright 2024 Paperzz