Indice
LPR-B-09: Esercizi assegnati a lezione
Test di ingresso
Inviare gli esercizi svolti a acorradi@cli.di.unipi.it con Subject “[LPR-B] Esercitazione 1”
- Si realizzi un programma Java che permette di visualizzare (sullo standard output) uno o più file di testo, mostrandone 20 righe alla volta (si pensi al comando Unix
less
). I nomi dei file da mostrare sono passati come argomenti della linea di comando, ad esempio:
java Less pippo.txt /usr/share/dict/words
Il programma deve leggere qualcosa in input prima di mostrare le prossime 20 righe. Se un file non esiste, deve segnalarlo all'utente, chiedendo se interrompere l'esecuzione o proseguire con i prossimi file.
- Si consideri il programma Java Leggimi.
- Spiegare in un commento di poche righe cosa fa il programma, indicando quali classi e quali metodi significativi usa.
- Scrivere un programma Java che prende come argomento da linea di comando il nome di una classe, e ne stampa tutte le variabili, indicando per ognuna di esse se è una variabile di classe o di istanza, e se è dichiarata
final
oppure no.
Tasks e Threads
Inviare gli esercizi svolti a acorradi@cli.di.unipi.it con Subject “[LPR-B] Esercitazione 2”
Esercizio 1
Scrivere un programma JAVA che attivi K thread, chiamati “T1”, “T2”, …, “TK”. Tutti i thread sono caratterizzati dallo stesso comportamento: ogni thread stampa i primi N numeri naturali, senza andare a capo (K e N sono dati in input dall'utente). Accanto ad ogni numero deve essere visualizzato il nome del thread che lo ha stampato, ad esempio usando il formato “: n [Tk] :”. Tra la stampa di un numero e quella del numero successivo ogni thread deve sospendersi per un intervallo di tempo la cui durata è scelta in modo casuale tra 0 e 1000 millisecondi.
Sviluppare due diverse versioni del programma che utilizzino le due tecniche per l'attivazione di threads presentate in questa lezione.
Esercizio 2: Interrompere un thread
Scrivere un programma che avvia un thread che va in sleep per 10 secondi. Il programma principale interrompe il thread dopo 5 secondi. Il thread deve catturare l'eccezione e stampare il tempo (in millisecondi) trascorso in sleep.
Per ottenere l'ora corrente usare il metodo System.currentTimeMillis(), consultandone la documentazione on line.
Esercizio 3: Calcolo di PiGreco
Scrivere un programma che attiva un thread T che effettua il calcolo approssimato di pigreco. Il programma principale riceve in input da linea di comando due argomenti:
- un parametro che indica il grado di accuratezza (accuracy) per il calcolo di pigreco;
- il tempo massimo di attesa dopo cui il programma principale interrompe il thread T.
Il thread T effettua un ciclo infinito per il calcolo di pigreco usando la serie di Gregory-Leibniz (pigreco = 4/1 – 4/3 + 4/5 - 4/7 + 4/9 - 4/11 …).
Il thread esce dal ciclo quando una delle due condizioni seguenti risulta verificata:
- il thread è stato interrotto, oppure
- la differenza in valore assoluto tra il valore stimato di pigreco ed il valore Math.PI (della libreria Java) è minore di accuracy.
Prima della terminazione il thread stampa il valore approssimato di pigreco calcolato fino a quel momento.
Esercizio 4
Modificare il programma dell'esercizio precedente in modo che il valore approssimato di pigreco calcolato dal thread venga stampato dal main.
Thread Pool
Inviare gli esercizi svolti a acorradi@cli.di.unipi.it con Subject “[LPR-B] Esercitazione 3”
Esercizio 1: Calcolatore Asincrono
Si realizzi un calcolatore asincrono, in grado cioè di calcolare espressioni senza dover attendere la fine di un calcolo prima di accettare l'espressione successiva.
Il programma deve accettare in input un'espressione in una delle seguenti forme:
- PI:precisione – calcola il valore di PI greco, come abbiamo visto in precedenza, fino all'approssimazione di 1/10precisione
- FIB:n – calcola il valore dell'n-esimo numero di Fibonacci, col metodo ricorsivo (con due chiamate)
- FACT:n – calcola il fattoriale di n, col metodo ricorsivo (con una chiamata)
- QUIT – termina il programma (una volta finiti i calcoli in sospeso)
Ad ogni espressione, il programma aggiunge un task a un threadpool, che effettuerà il calcolo e stamperà il risultato a video, e torna immediatamente a chiedere il prossimo input
I task di calcolo devono stampare, a fine esecuzione: il nome del loro thread, l'istante di creazione, l'istante di inizio esecuzione, l'istante di completamento, e il risultato del calcolo.
Si suggerisce di definire una classe per ogni tipo di task di calcolo, fattorizzando le funzionalità comuni (come la stampa finale) in una superclasse astratta comune.
Soluzione: AsynchronousCalculator.java, Task.java, PITask.java, FibTask.java, FactTask.java
Esercizio 2: Raffinamenti
Con riferimento all'esercizio precedente:
- Si faccia in modo che il ciclo principale del calcolatore stampi un prompt per indicare la disponibilità a ricevere input
- Per esempio:
“Inserisci espressione: “
- Quando un calcolo termina, verrà stampato il risultato su video. Fare in modo che la stampa non confonda il prompt
- In particolare, occorre che il ciclo principale ristampi il prompt se è stato stampato un risultato
Si sperimenti poi con diverse politiche di pooling
- In particolare: qual'è il numero di thread eseguibili contemporaneamente su queste macchine, in maniera tale da minimizzare i tempi d'attesa?
- Tempo fra l'invio di una richiesta e il suo completamento
- Tempo fra l'inizio del servizio di una richiesta e il suo completamento
Esercizio 3: Un Executor Custom
- Si usi una struttura dati “coda ordinata” per implementare un Executor che scheduli i task in base a un campo priorità, indicato dal task stesso.
- La priorità (che viene stabilita in fase di creazione e/o sottomissione del task) deve anche essere usata come priorità del thread relativo.
- Si tenga presente che i thread possono essere riutilizzati per task a priorità diversa (la priorità va quindi stabilita al momento della “presa in carico” di un task).
- Si crei infine un main di prova che sottometta vari task al vostro Executor, e ne visualizzi l'ordine di sottomissione, inizio e fine esecuzione (come negli esempi visti a lezione)
Indirizzi IP e Callable
Inviare gli esercizi svolti a acorradi@cli.di.unipi.it con Subject “[LPR-B] Esercitazione 4”
Esercizio 1
Scrivere un programma che enumeri e stampi a video tutte le interfacce di rete del computer, usando i metodi della classe java.net.NetworkInterface.
- Usare il metodo statico getNetworkInterfaces() per ottenere una Enumeration di NetworkInterface.
- Per ogni NetworkInterface, stampare gli indirizzi IP associati ad essa (IPv4 e IPv6) e il nome dell’interfaccia.
Esercizio 2
Scrivere un programma Java Resolve che traduca una sequenza di nomi simbolici di host nei corrispondenti indirizzi IP. Resolve legge i nomi simbolici da un file, il cui nome è passato da linea di comando oppure richiesto all'utente.
- Si deve definire un task che estenda l’interfaccia Callable, e che, ricevuto come parametro un nome simbolico, provvede a tradurre il nome ritornando un InetAddress.
- Per ottimizzare la ricerca, si deve attivare un pool di thread che esegua i task in modo concorrente. Ogni volta che si sottomette al pool di thread un task, si ottiene un oggetto Future<InetAddress>, che deve essere aggiunto ad un ArrayList.
- Infine, si scorre l’ArrayList, stampando a video gli InetAddress.
Esercizio 3
Scrivere un programma che ricerca una parola chiave (key) nei file contenuti in una directory (fornita dall'utente) e nelle sue sottodirectory. Per ogni file che contiene key, si deve visualizzare il nome dei file e il contenuto della prima riga trovata che contiene key.
- Creare una classe FindKeyword che implementa Callable, alla quale si può passare una directory come parametro del costruttore, e che ritorna un array di stringhe del formato
<nome file> : <contenuto riga che contiene key>
- Il metodo search della classe FindKeyword implementa la ricerca di key all’interno di un singolo file, e ritorna una stringa formattata come sopra oppure null se key non compare.
- Creare un pool di thread a cui vengono sottomessi un task FindKeyword per ogni directory/sottodirectory, e usare gli oggetti Future restituiti per stampare a video i risultati ottenuti.
Sincronizzazione di Thread
Inviare gli esercizi svolti a acorradi@cli.di.unipi.it con Subject “[LPR-B] Esercitazione 4”
Esercizio 1
Si scriva un programma Java che dimostri che si possono verificare delle race conditions anche con una singola istruzione di incremento di una variabile.
- Scrivere una classe Counter che offre un metodo next() che incrementa una variabile intera locale, e un metodo getCount() che ne restituisce il valore.
- Scrivere un task TaskCounter che implementa Callable e che riceve nel costruttore un Counter e un intero n. Il task invoca la next() del Counter un numero casuale di volte compreso tra n/2 e n, e restituisce il numero casuale calcolato.
- Il main crea un Counter e un pool di threads, in cui esegue M copie di TaskCounter passando a ognuna di esse il Counter e un valore N; quindi stampa la somma dei valori restituiti dagli M threads, e il valore finale del contatore ottenuto con getCount(): se questi due valori sono diversi c'è stata una race condition. M e N devono essere forniti dall'utente.
Esercizio 2
Si consideri il metodo next() della classe Counter dell'Esercizio 1. Modificarlo in modo da renderne l'esecuzione non interrompibile, e rieseguire il programma controllando che non si verifichino più race conditions. Fare questo nei tre modi visti:
- usando un comando synchronized
- usando un lock esplicito
- dichiarando synchronized il metodo next()
Esercizio 3
- La classe Buffer presentata a lezione ha una politica Last In First Out (LIFO), quindi non preserva l'ordine. Scrivere la classe CircularBuffer che estende Buffer e realizza una politica FIFO, gestendo l'array in modo circolare.
- Definire le interfacce generiche Producer<E>, Consumer<E> e Buffer<E>, che definiscono un sistema produttore/consumatore per un generico tipo di dati E.
- Implementare le interfacce in modo che il produttore produca una sequenza di stringhe, leggendole da un file passato come parametro al task, e il consumatore scriva le stringhe che prende dal buffer in un altro file.
- Nel main, creare e attivare un produttore e due o più consumatori. Verificare che la concatenazione dei file generati dai consumatori sia uguale, a meno dell'ordine delle righe, al file letto dal produttore.
Esercizio 4
Il laboratorio di Informatica del Polo Marzotto è utilizzato da tre tipi di utenti, studenti, tesisti e professori ed ogni utente deve fare una richiesta al tutor per accedere al laboratorio. I computer del laboratorio sono numerati da 1 a 20. Le richieste di accesso sono diverse a seconda del tipo dell'utente:
- i professori accedono in modo esclusivo a tutto il laboratorio, poichè hanno necessità di utilizzare tutti i computers per effettuare prove in rete;
- i tesisti richiedono l'uso esclusivo di un solo computer, identificato dall'indice i, poichè su quel computer è istallato un particolare software necessario per lo sviluppo della tesi.
- gli studenti richiedono l'uso esclusivo di un qualsiasi computer.
I professori hanno priorità su tutti nell'accesso al laboratorio, i tesisti hanno priorità sugli studenti.
Scrivere un programma JAVA che simuli il comportamento degli utenti e del tutor. Il programma riceve in ingresso il numero di studenti, tesisti e professori che utilizzano il laboratorio ed attiva un thread per ogni utente. Ogni utente accede k volte al laboratorio, con k generato casualmente.
Simulare l'intervallo di tempo che intercorre tra un accesso ed il successivo e l'intervallo di permanenza in laboratorio mediante il metodo sleep. Il tutor deve coordinare gli accessi al laboratorio. Il programma deve terminare quando tutti gli utenti hanno completato i loro accessi al laboratorio.
Il protocollo UDP
Inviare gli esercizi svolti a acorradi@cli.di.unipi.it con Subject “[LPR-B] Esercitazione 5”
Avviso: Per eseguire gli esercizi su di un unico host
- Attivare il client/sender ed il server/receiver in due diverse shell
- Se l’host è connesso in rete: utilizzare come indirizzo IP del mittente/destinatario l’indirizzo dell’host su cui sono in esecuzione i due processi (reperibile con InetAddress.getLocalHost( ))
- Se l’host non è connesso in rete utilizzare l’indirizzo di loopback (“localhost” o 127.0.0.1)
- Tenere presente che mittente e destinatario sono in esecuzione sulla stessa macchina, quindi devono utilizzare porte diverse
- Mandare in esecuzione per primo il server/receiver, poi il client/sender
Avviso: Per eseguire gli esercizi su più host distinti
- Se siete nel Laboratorio M, potete usare come host remoti gli altri computer del laboratorio: i nomi sono
fujim2
,fujim3
, …,fujim40
. Per controllare se un host è sotto Linux e raggiungibile, eseguire da shell il comandossh <nomeHost>
e fornire la password: questo aprirà una shell sul computer<nomeHost>
, con directory corrente la vostra home-directory. - Usando questa shell potete mandare in esecuzione su quell'host uno dei processi dell'esercizio. Se il processo non deve interagire con l'utente, lo potete eseguire in background (
&
dopo il comando) e usare la shell per far partire altri processi.
Esercizio 1: Invio di Datagram UDP
Scrivere un'applicazione composta da un processo Sender e un processo Receiver. Il Receiver riceve da linea di comando la porta su cui deve porsi in attesa. Il Sender riceve da linea di comando una stringa e l’indirizzo del Receiver (indirizzo IP + porta), e invia al Receiver la stringa. Il Receiver riceve la stringa e stampa, nell'ordine, la stringa ricevuta, l'indirizzo IP e la porta del mittente.
Considerare poi i seguenti punti:
- Cosa cambia se mando in esecuzione prima il Sender, poi il Receiver rispetto al caso in cui mando in esecuzione prima il Receiver?
- Nel processo Receiver, aggiungere un time-out sulla receive, in modo che la receive non si bocchi per più di 5 secondi. Cosa accade se attivo il receiver, ma non il sender?
- Modificare il codice del Sender in modo che usi lo stesso socket per inviare lo stesso messaggio a due diversi receivers. Mandare in esecuzione prima i due Receivers, poi il Sender. Controllare l'output dei Receiver.
- Modificare il codice del Sender in modo che esso usi due sockets diversi per inviare lo stesso messaggio a due diversi receivers. Mandare in esecuzione prima i due Receivers, poi il Sender.
- Modificare il codice ottenuto al passo precedente in modo che il Sender invii una sequenza di messaggi ai due Receivers. Ogni messaggio contiene il valore della sua posizione nella sequenza. Il Sender si sospende per 3 secondi tra un invio ed il successivo. Ogni receiver deve essere modificato in modo che esso esegua la receive in un ciclo infinito.
- Modificare il codice ottenuto al passo precedente in modo che il Sender non si sospenda tra un invio e l’altro. Cosa accade?
- Modificare il codice iniziale in modo che il Receiver invii al Sender un ack quando riceve il messaggio. Il Sender visualizza l’ack ricevuto.
Esercizio 2: CountDown Server
Si richiede di programmare un server CountDownServer che fornisce un semplice servizio: ricevuto da un client un valore intero n, il server spedisce al client i valori n-1,n-2,…,1,0 in sequenza.
Il client deve calcolare il numero di pacchetti persi e quello di quelli ricevuti fuori ordine e lo visualizza alla fine della sessione. Utilizzare le classi ByteArrayOutput/InputStream per la generazione/ricezione dei pacchetti.
La interazione tra i clients e CountDownServer è di tipo connectionless. Si richiede di implementare due versioni di CountDownServer
- realizzare CountDownServer come un server iterativo. L’applicazione riceve la richiesta di un client, gli fornisce il servizio e solo quando ha terminato va a servire altre richieste
- realizzare CountDownServer come un server concorrente. Si deve definire un thread che ascolta le richieste dei clients dalla porta UDP a cui è associato il servizio ed attiva un thread diverso per ogni richiesta ricevuta. Ogni thread si occupa di servire un client.
Per testare il funzionamento del client, può essere utile usare la classe UnreliableDatagramSocket, che offre le stesse funzionalità di DatagramSocket
, ma perde il pacchetto da inviare con probabilità “threshold
”, una quantità compresa tra 0 e 1, modificabile con il metodo setThreshold(double)
e con valore default 0.1
.
Il protocollo UPD (2)
Inviare gli esercizi svolti a acorradi@cli.di.unipi.it con Subject “[LPR-B] Esercitazione 6”
Esercizio 1: Objects to DatagramPackets
Scrivere la classe Obj2DP che fornisce due metodi statici:
public static Object dp2obj(DatagramPacket dp)
che restituisce l'oggetto contenuto nel pacchetto passato per argomento, deserializzandolo, e
public static DatagramPacket obj2dp(Object obj)
che restituisce un pacchetto contenente l'oggetto passato per argomento, serializzato, nel payload.
Semplificare le classi UDP_SendObject e UDP_ReceiveObject viste a lezione usando i metodi della classe Obj2DP, senza più usare le classi ObjectOutput/InputStream e ByteOutput/InputStream.
Usare la classe Obj2DP per i prossimi esercizi, trasmettendo oggetti serializzati con UDP invece di dati di tipi primitivi.
Esercizio 2: MiniTalk Server e Client con UDP
Si realizzino un Server e un Client UDP che realizzano un semplice Instant Messanger: ogni linea scritta nella shell del Server viene copiata nella shell del Client e viceversa. La trasmissione delle linee inizia dopo una semplice fase di handshaking, descritta di seguito.
- Il Server viene lanciato da shell, fornendo il numero di porta su cui ricevere i pacchetti UDP.
- Il Client viene lanciato da un'altra shell (eventualmente su un altro computer), fornendo tre dati: host e porta del Server, e numero di porta locale su cui ricevere pacchetti UDP.
- Il Client manda una richiesta di connessione al Server, indicando nel messaggio la propria porta. Se non riceve un messaggio di ack entro 3 secondi, riprova a mandare la richiesta altre 5 volte, poi termina. Se invece riceve l'ack, inizia la trasmissione delle linee scritte nella shell locale e la ricezione e stampa delle linee scritte nella shell del Server.
- Quando il Server riceve una richiesta di connessione, recupera indirizzo e porta del Client dalla richiesta e manda un messaggio di ack al Client, quindi comincia la trasmissione e ricezione delle stringhe come descritto per il Client.
- Il Client decide quando disconnettersi in base a una condizione a vostra scelta (per esempio, allo scadere di un timeout, oppure se la linea da mandare è vuota, oppure se la stringa è “CLOSE”,…). Per disconnettersi, il Client manda una richiesta di disconnessione al Server e aspetta un ack, quindi termina l'esecuzione.
- Quando il Server riceve una richiesta di disconnessione interrome la trasmissione delle linee, manda un messaggio di ack, e si rimette in attesa di una richiesta di connessione.
Il Client e il Server devono scambiarsi unicamente oggetti della classe TalkMsg, usando i metodi della classe per crearne istanze e per ispezionare i messaggi arrivati.
Esercizio 3: MiniTalk Messanger con UDP
Riusando il più possibile il codice sviluppato per l'esercizio precedente, realizzare un programma Messanger che offre le stesse funzionalità, ma in cui non si distinguono un Server e un Client.
Due istanze di Messanger devono essere lanciate in due shell diverse, fornendo ad ognuna tre dati: la porta locale, e l'host e la porta dell'altro Messanger. Ideare un opportuno protocollo di handshaking, che permetta di stabilire una connessione (concettuale) tra le due istanze di Messanger.
I messaggi scambiati devono essere tutti oggetti di una stessa classe. Usare la classe TalkMsg, oppure estenderla o definirne una analoga se necessario.
Esercizio 4: TFTP con UDP (Trivial File Transfer Protocol)
Questa è la specifica di TFTP da WIKIPEDIA:
- L'host A invia un pacchetto RRQ (read request) o WRQ (write request) all'host B, contenente il nome del file e la modalità di trasferimento.
- B risponde con un ACK (acknowledgement) packet, che serve anche a dire ad A quale porta sull'host B dovrà usare per i restanti pacchetti.
- L'host di origine invia dei pacchetti DATA numerati all'host di destinazione, tutti tranne l'ultimo contenenti un blocco di dati completo. L'host di destinazione risponde con un pacchetto ACK numerato per ogni pacchetto DATA.
- Il pacchetto DATA finale deve contenere un blocco di dati non pieno ad indicare che si tratta dell'ultimo. Se la dimensione del file trasferito è un multiplo esatto della dimensione dei blocchi, la sorgente invia un ultimo pacchetto di dati contente 0 byte di dati.
Realizzare un Server TFTP che implementa il comportamento dell'host B e un Client TFTP che implementa l'host A. In particolare:
- Client e Server devono scambiarsi solo oggetti di una classe, TFTPmsg, usati sia per messaggi di servizio (RRQ, WRQ, ACK) che per i pacchetti DATA: definire opportunamente la classe TFTPmsg.
- Per il trasferimento dei file, considerarli come file binari, usando quindi opportuni Output/InputStreams (e non Writer/Reader).
- Inviare le porzioni di file in array di byte all'interno di un'istanza di TFTPmsg.
Per testare il programma:
- Confrontare il file originale spedito dal mittente con quello ricevuto dal destinatario e scritto nel file system.
- Usare la classe UnreliableDatagramSocket per controllare che i pacchetti persi vengano reinviati correttamente.
Il Protocollo TCP
Inviare gli esercizi svolti a acorradi@cli.di.unipi.it con Subject “[LPR-B] Esercitazione 7”
Esercizio 1: Compressione di file
Progettare un'applicazione client/server in cui il server fornisca un servizio di compressione di dati. Il client legge chunks di bytes da un file e li spedisce al server che provvede alla loro compressione. Il server restituisce i bytes in formato compresso al client che provvede a creare un file con lo stesso nome del file originario e con estensione gz, che contiene i dati ricevuti dal server.
La comunicazione tra client e server utilizza il protocollo TCP. Per la compressione si può utilizzare la classe JAVA GZIPOutputStream
.
Individuare le condizioni necessarie affinchè il programma scritto generi una situazione di deadlock e verificare che tale situazione si verifica realmente quando tali condizioni sono verificate.
Suggerimento: Scrivere il server in modo che riceva i byte e li rispedisca nello stesso loop. Scrivere una prima versione del client che prima invia tutto il file al server e solo alla fine riceve il file compresso dal server. Verificare che per file grandi questa soluzione va in deadlock, e scrivere in un commento perché. Scrivere una seconda versione del client che, usando ancora un solo thread, non abbia questo problema, intercalando l'invio di chunks del file al server con la ricezione di pezzi del file compresso.
Esercizio 2: Compressione di file, estensioni
Si realizzi il client con due thread, uno per la lettura del file originale e l'invio al server, l'altro per la lettura dei dati compressi dal server e la scrittura nel file .gz (si curi, ove necessario, la sincronizzazione fra i due e la gestione degli errori)
Si realizzi il server in modo da poter gestire in maniera efficiente connessioni multiple da parte di più client – si ipotizzi anche che il numero di richieste di servizio possa essere elevato
Quali costrutti per il multithreading sono maggiormente indicati in ciascuno dei due casi di cui sopra?
Suggerimento: Scrivere il server in modo che riceva i byte e li rispedisca nello stesso loop. Scrivere una prima versione del client che prima invia tutto il file al server e solo alla fine riceve il file compresso dal server. Verificare che per file grandi questa soluzione va in deadlock, e scrivere in un commento perché. Scrivere una seconda versione del client che non abbia questo problema.
Esercizio 3: Interazione con server predefiniti
Utilizzando come protocollo HTTP (come visto nel corso di Reti) sulla porta 80, e come host un web server (per esempio, www.cli.di.unipi.it
o www.di.unipi.it
),
scrivere un semplice client HTTP in grado di effettuare una breve sequenza di richieste al server, stampando i risultati. Per testare il protocollo si può usare il comando telnet <webServer> 80
, che apre una connessione TCP attraverso la quale si possono scambiare stringhe.
Protocollo TCP e Multicast
Inviare gli esercizi svolti a acorradi@cli.di.unipi.it con Subject “[LPR-B] Esercitazione 8”
Esercizio 1: Asta Elettronica
Sviluppare un programma client server per il supporto di un'asta elettronica.
Ogni client possiede un budget massimo B da investire. Il client può richiedere al server il valore V della migliore offerta pervenuta fino ad un certo istante e decidere se abbandonare l'asta, oppure rilanciare. Se il valore ricevuto dal server supera B,l'utente abbandona l'asta, dopo aver avvertito il server. Altrimenti, il client rilancia, inviando al server un valore maggiore di V.
Il server invia ai client che lo richiedono il valore della migliore offerta ricevuta fino ad un certo momento e riceve dai client le richieste di rilancio. Per ogni richiesta di rilancio, il server notifica al client se tale offerta può essere accettata (nessuno ha offerto di più nel frattempo), oppure è rifiutata. Il server deve attivare un thread diverso per ogni client che intende partecipare all'asta.
La comunicazione tra clients e server deve avvenire mediante socket TCP. Sviluppare due diverse versioni del programma che utilizzino, rispettivamente una codifica testuale dei messaggi spediti tra client e server oppure la serializzazione offerta da JAVA in modo da scambiare oggetti tramite la connessione TCP
Esercizio 2: TimeServer Multicast
Definire un server TimeServer, che invia su un gruppo di multicast dategroup, ad intervalli regolari, la data e l’ora. L’attesa tra un invio ed il successivo può essere simulata mediante il metodo sleep( ). L’indirizzo IP di dategroup viene introdotta linea di comando.
Definire quindi un client TimeClient che si unisce a dategroup e riceve, per dieci volte consecutive, data ed ora, le visualizza, quindi termina.
Esercizio 3: Streaming Audio
La pagina http://en.wikipedia.org/wiki/WAV
contiene alcuni file di esempio di file
audio nel popolare formato WAV (audio non compresso)
- Questi dati possono generalmente essere riprodotti, su macchine UNIX, semplicemente scrivendo il loro contenuto su
/dev/dsp
o/dev/audio
- Attenzione: su alcune installazioni, il dispositivo
/dev/dsp
è permanentemente occupato da un demone per il mixing audio comeesd
,alsa
,pulseaudio
ecc. Può essere necessario uccidere tale demone perché la riproduzione abbia luogo.
- Si scriva un server streaming audio che, ricevuta sulla riga di comando l'URL di un file WAV, lo scarichi dal web e trasmetta il contenuto, con adeguata temporizzazione, a un gruppo multicast il cui indirizzo è pure fornito sulla riga di comando
- Si scriva poi un client streaming audio che, ricevuto sulla riga di comando l'indirizzo IP di un gruppo, si aggiunga ad esso e riproduca sulla macchina locale l'audio
Non ci si allarmi se l'audio suona “strano”, stiamo trascurando una quantità di parametri…
Remote Method Invocation
Inviare gli esercizi svolti a acorradi@cli.di.unipi.it con Subject “[LPR-B] Esercitazione 9”
Esercizio 1: Gestione elezione
Sviluppare una applicazione RMI per la gestione di un’elezione. Il server esporta un insieme di metodi:
public void vota (String nome)
: Accetta come parametro il nome del candidato. Non restituisce alcun valore. Registra il voto di un candidato in una struttura dati opportunamente scelta.
public int risultato (String nome)
: Accetta come parametro il nome di un candidato e restituisce i voti accumulati da tale candidato fino a quel momento.
- un metodo che consenta di ottenere i nomi di tutti i candidati, con i rispettivi voti, ordinati rispetto ai voti ottenuti.
Il client invoca un certo numero di volte i metodi del server su opportuni argomenti (eventualmente forniti interattivamente dall'utente), stampando i risultati ottenuti. Testare che il sistema funzioni con server e client sullo stesso host e su host diversi. Nel secondo caso, provare due versioni: con il registry sull'host del server (come negli esempi visti), e con il registry sull'host del client.
Esercizio 2: Passaggio di parametri con RMI
Scrivere opportune classi e interfacce per verificare che nel caso di valori di tipo riferimento (oggetti e array), una invocazione di metodo remota passa al metodo chiamante una copia dell'oggetto passato come parametro, diversamente da quanto accade nel caso di una invocazione locale. Mostrare che invece, se il parametro è un oggetto remoto, allora viene passato un riferimento all'oggetto e non una sua copia.
RMI e Callback
Esercizio 1: Gestione elezione
Modificare l’Esercizio 1 dell'esercitazione precedente in modo che il server notifichi ogni nuovo voto ricevuto a tutti i clients che hanno votato fino a quel momento. La registrazione dei clients sul server avviene nel momento del voto.
Esercizio 2: Forum
Si vuole implementare un sistema che implementi un servizio per la gestione di forum in rete. Un forum è caratterizzato da un argomento su cui diversi utenti, iscritti al forum, possono scambiarsi opinioni via rete. Il sistema deve prevedere un server RMI che fornisca le seguenti funzionalità:
- apertura di un nuovo forum, di cui è specificato l'argomento (esempio: giardinaggio)
- registrazione ad un forum, di cui è specificato l'argomento
- inserimento di un nuovo messaggio indirizzato ad un forum identificato dall'argomento (es: è tempo di piantare le viole, indirizzato al forum giardinaggio); il messaggio deve essere inviato agli utenti iscritti al forum
- reperimento dell'ultimo messaggio inviato ad un forum di cui è specificato l'argomento.
Quindi il messaggio può essere richiesto esplicitamente dal client oppure può essere notificato ad un client precedentemente registrato.