Socket in Java 21-22
Socket in Java 21-22
Esercizi risolti....................................................................................................................................................23
Sistema di estrazioni del lotto......................................................................................................................23
Gestione del tabellone del lotto (multicast).................................................................................................25
Crociera panoramica in battello...................................................................................................................28
Appendice.........................................................................................................................................................29
A1 – Generatore di numeri casuali distinti..................................................................................................29
A2 – Deployment di un’applicazione Java su server Linux........................................................................31
A2.1 – Installazione del software Java Runtime Environment (JRE)....................................................31
A2.2 – Memorizzazione dell’applicazione in formato JAR...................................................................31
A2.3 – Trasferimento dell’applicazione sul server.................................................................................32
Applicazioni proposte.......................................................................................................................................34
• Client: Invia una richiesta composta da una sola linea di testo UTF-8 delimitata dal carattere
newline; elabora una risposta a singola linea di testo UTF-8.
comunica(client);
} catch (IOException e) {
System.err.println(String.format("Errore durante la comunicazione
con il client: %s", e.getMessage()));
}
}
} catch (IOException e) {
System.err.println(String.format("Errore server: %s", e.getMessage()));
}
}
comunica(sck);
} catch (UnknownHostException e) {
System.err.format("Nome di server non valido: %s%n", e.getMessage());
} catch (IOException e) {
System.err.format("Errore durante la comunicazione con il server: %s%n",
e.getMessage());
}
}
• Client: Invia una richiesta composta da più linee di testo UTF-8 delimitata dal carattere newline;
elabora una risposta a singola linea di testo UTF-8.
String s2 = sb.reverse().toString();
comunica(sck);
} catch (UnknownHostException e) {
System.err.format("Nome di server non valido: %s%n", e.getMessage());
} catch (IOException e) {
System.err.format("Errore durante la comunicazione con il server: %s%n",
e.getMessage());
}
}
do {
System.out.print("Parola (premere il solo tasto INVIO per terminare): ");
parola = s.nextLine().trim();
if (parola.isEmpty() == false) {
System.out.format("Invio al server: %s%n", parola);
out.println(parola);
}
}
while (parola.isEmpty() == false);
• Client: Invia una richiesta in formato binario secondo le specifiche indicate e riceve la statistica
prodotta dal server.
int numLati;
ArrayList<Double> lati = new ArrayList<Double>();
comunica(sck);
} catch (UnknownHostException e) {
System.err.format("Nome di server non valido: %s%n", e.getMessage());
} catch (IOException e) {
System.err.format("Errore durante la comunicazione con il server: %s%n",
e.getMessage());
}
}
new BufferedOutputStream(sck.getOutputStream()));
• Il client e il server condividono la definizione di due classi Ricerca e Risultato. Queste classi
definiscono la struttura dei messaggi scambiati e devono implementare l’interfaccia Serializable
• Server: supporta l’accesso da parte di più client ma serve un client alla volta (elaborazione
sequenziale); riceve dal client un oggetto di tipo Ricerca da cui ricava i criteri di ricerca; genera e
restituisce al client un oggetto di tipo Risultato contenente i dati dell’impiegato trovato (oppure null
se l’impiegato non esiste).
• Client: legge da tastiera il cognome e il nome di un impiegato, inserisce i dati in un oggetto di tipo
Ricerca che invia al server; riceve un oggetto di tipo Risultato da cui ricava il risultato della ricerca.
try {
// Deserializzazione: ricevo una sequenza di byte e
// la trasformo in un oggetto
ric = (Ricerca) in.readObject();
} catch (ClassNotFoundException e) {
throw new IOException("Tipo di ricerca non supportata.");
}
if (trovato == true) {
ris = new Risultato("Rossi", "Mario", 10012, 1598.50);
}
else {
ris = null;
}
return ris;
}
this.matricola = matricola;
this.stipendio = stipendio;
}
}
comunica(sck);
} catch (UnknownHostException e) {
System.err.format("Nome di server non valido: %s%n", e.getMessage());
} catch (IOException e) {
System.err.format("Errore durante la comunicazione con il server: %s%n",
e.getMessage());
}
}
3. Multiserver TCP
Il server elabora più richieste contemporaneamente distribuendo il carico di lavoro su più thread di
elaborazione.
• Client: richiede l’inserimento di N, A, B da tastiera; genera e invia al server una linea di testo
contenente i tre dati; riceve e stampa a video i numeri casuali.
static Lock lc = new ReentrantLock(); // Usato nell'accesso alle risorse in mutua esclusione
while (true) {
Socket tempSck;
try {
tempSck = server.accept();
esecutore.execute(() -> {
// Copia il socket creato dal main thread: in questo modo la variabile
// tmpSck può essere utilizzata per accettare nuove connessioni.
Socket client = tempSck;
try (client) {
String rem = client.getRemoteSocketAddress().toString();
Thread t = Thread.currentThread();
System.out.format("Thread %d - Client (remoto): %s%n", t.getId(), rem);
} catch (IOException e) {
System.err.println(String.format("Errore durante la comunicazione con
il client: %s", e.getMessage()));
}
});
} catch (IOException e) {
System.err.println(String.format("Errore nella gestione di nuove connessioni:
%s", e.getMessage()));
}
}
} catch (IOException e) {
System.err.println(String.format("Errore del server: %s", e.getMessage()));
}
}
Thread.sleep(RITARDO);
System.out.format("\tThread %d - Inviato numero %d%n", t.getId(), r);
out.println(r);
comunica(sck);
} catch (UnknownHostException e) {
System.err.format("Nome di server non valido: %s%n", e.getMessage());
} catch (IOException e) {
System.err.format("Errore durante la comunicazione con il server: %s%n",
e.getMessage());
}
}
do {
System.out.print("Estremi (inclusi) dell'intervallo: ");
a = s.nextInt();
b = s.nextInt();
}
while (a < MIN_INTERVALLO || a > b || b > MAX_INTERVALLO);
4. Socket UDP
Due applicazioni si scambiano, in modo rapido ma non necessariamente affidabile, dati codificati in binario.
• Client: richiede l’inserimento da tastiera di un nome (stringa UTF-8); genera e invia al server un
datagramma contenente il nome; riceve dal server un nuovo datagramma da cui estrae la frase di
saluto; stampa la frase.
nome = nome.trim().toUpperCase();
System.out.format("Ricevuto dal client %s il nome %s... ", saClient, nome);
bufferOut = nome.getBytes("UTF-8");
InetAddress ipServer = InetAddress.getByName(nomeServer);
DatagramPacket pktOut = new DatagramPacket(bufferOut, bufferOut.length, ipServer, portaServer);
System.out.format("Invio del nome al server...%n");
sck.send(pktOut);
} catch (SocketException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
• Client: dopo essersi unito al gruppo multicast previsto, riceve e visualizza sei aforismi. Al termine
dell’attività il client abbandona il gruppo.
Esercizi risolti
Realizza un sistema di estrazioni del lotto: N giocatori scelgano tre numeri e li comunichino alla ricevitoria.
Quindi effettua le estrazioni del lotto comunicando eventuali vincite.
• N thread di gestione dei giocatori (worker): ognuno di questi thread riceve, valida e memorizza i tre
numeri dal giocatore associato, quindi rimane in attesa dell’estrazione; successivamente, confronta i
numeri ricevuti con quelli estratti e comunica al giocatore l’esito dell’estrazione.
• thread di estrazione: attende che N giocatori abbiano completato l’invio dei numeri, quindi chiude la
ricevitoria (chiudendo il server socket in modo tale da non accettare ulteriori connessioni), procede
all’estrazione di cinque numeri vincenti distinti e sblocca gli N thread di gestione dei giocatori.
• main thread: avvia il thread di estrazione, quindi crea un server socket per accettare connessioni TCP
sulla porta 5060; per ogni connessione accettata avvia la routine di gestione del giocatore su un thread
distinto; alla chiusura del server socket (effettuata dal thread di estrazione), rimane in attesa del
completamento delle attività svolte dagli altri thread
Si opta per un protocollo ad hoc in base al quale client e server si scambiano messaggi testuali UTF-8 a
singola linea.
• Messaggio contenente i numeri del giocatore: linea di testo inviata dal client, contiene i tre numeri
separati da uno spazio seguiti dal carattere newline. Esempio: 5 7 9\n
• Messaggio contenente i numeri del giocatore: linea di testo inviata dal server, composta dai cinque
numeri vincenti (separati da uno spazio), un ulteriore spazio e una frase che riporta l’esito
dell’estrazione. I possibili esiti sono: NESSUN NUMERO ESTRATTO, UN NUMERO ESTRATTO,
AMBO, TERNO
Lo scambio di messaggi tra N = 2 client e il server e la sincronizzazione dei thread del server sono descritti
dal seguente diagramma temporale.
Riceve e
memorizza
numeri
Invia i numeri
1 4 7\n
Riceve e
GA.countdown() memorizza
GA = 1
numeri
EA.await()
ATTESA
GA.countdown()
GA = 0
EA.await() ATTESA
ATTESA
Estrai numeri
ATTESA
vincenti
13579
EA.countdown()
EA = 0 EA = 0
1 3 5 7 9 TERNO\n Calcola esito Calcola esito 1 3 5 7 9 AMBO\n
Invia risultati Invia risultati
Riceve esito Riceve esito
Stampa risultato Stampa risultato
Chiusura connessione Chiusura connessione
Soluzione
Sulla piattaforma Netlab è disponibile la soluzione dell’esercizio Sistema di estrazioni del lotto. La soluzione
consiste in un progetto multimodulo realizzato con il software IntelliJ Idea.
• il server gestisce il tabellone ed effettua una nuova estrazione delle 11 ruote ogni giorno;
• l’estrazione su una nuova ruota, tra le 11 definite, avviene ogni 2 minuti, ed è composta da
cinque numeri differenti in un range 1÷90, e ha la seguente struttura:
<nome> <estratto1>,<estratto2>,<estratto3>,<estratto4>,<estratto5>
I singoli numeri vengono inviati man mano che vengono estratti ai diversi client: se un utente si collega
mentre si è nel mezzo di un’estrazione, viene messo in attesa dell’inizio di un’estrazione su di una nuova
ruota.
A fine giornata il server memorizza i numeri estratti in un file, che ha per nome il numero
dell’estrazione e la data (est20_15_02_2020.txt).
Considerazioni iniziali
Per verificare la correttezza dell’algoritmo di gestione dei tempi di attesa iniziali, si estende la durata
dell’estrazione su una singola ruota inserendo un breve ritardo prima dell’estrazione di un nuovo numero.
Poiché è difficile controllare i tempi di attesa di ogni singolo client con una sola trasmissione multicast, si
opta per la seguente soluzione:
1. Il server rimane in ascolto sulla porta 5070/tcp (connessioni unicast) e contemporaneamente avvia,
mediante un thread addizionale, l’estrazione della prima ruota trasmettendo i numeri risultanti in
multicast sulla porta 5070/udp
2. Il client apre una nuova connessione TCP sulla porta 5070 e rimane in attesa di ricevere un
opportuno messaggio di inizio estrazione
3. Il server accetta la connessione e inserisce il socket del client in una opportuna lista dei socket in
stato d’attesa. Tutti i client che si connettono al server in questa fase sono inseriti nella lista.
4. Poco prima dell’estrazione su una nuova ruota, invia a ciascuno dei socket presenti nella lista il
messaggio “INIZIO” e, subito dopo, chiude il socket TCP. Al termine di questa operazione, la lista è
azzerata
5. Il client, ottenuto il messaggio, chiude a sua volta il socket TCP e inizia ad acquisire in multicast i
numeri delle ruote rimanenti fino a quando riceve, sempre in multicast, il messaggio “FINE”
• main thread: pianifica l’estrazione di una nuova ruota a intervalli regolari avvalendosi della classe
ScheduledExecutorService; accetta nuove connessioni sulla porta 5070/tcp; aggiunge il socket
accettato alla lista dei socket in attesa; attende il completamento delle estrazioni del Lotto da parte del
thread di estrazione, quindi procede al salvataggio su file dell’intero tabellone;
• thread di estrazione ruota: esegue la routine di estrazione ripetutamente secondo i vincoli temporali
imposti dal main thread (a ogni ripetizione, estrae i numeri di una nuova ruota); invia a ogni socket
presente nella lista d’attesa il messaggio “INIZIO”, chiudendo le connessioni TCP; azzera la lista
d’attesa; procede all’estrazione sulla nuova ruota inviando i singoli numeri in UDP all’indirizzo di
multicast 239.1.1.2, porta 5070/udp; dopo aver inviato l’ultima ruota del tabellone, il thread invia
sempre in multicast il messaggio “FINE”.
Server
Client Porta 5060/tcp
Chiusura connessione
ATTESA
Multicast
239.1.1.2
Porta 5070/udp Ruota di Cagliari
Cagliari\n
Numero estratto: 11
11\n
Numero estratto: 75
75\n
Numero estratto: 34
34\n
Numero estratto: 19
19\n
Numero estratto: 58
58\n
Tabellone completato
FINE\n
Protocollo di comunicazione
Si opta per un protocollo ad hoc in cui client e server si scambiano messaggi testuali UTF-8 a singola linea.
Ogni messaggio termina con un carattere di newline.
Soluzione
Sulla piattaforma Netlab è disponibile la soluzione dell’esercizio Gestione del tabellone del lotto (multicast).
La soluzione consiste in un progetto multimodulo realizzato con il software IntelliJ Idea.
• Il client si collega sulla porta 5080/tcp del server per acquistare un biglietto.
• Il server rimane in ascolto a ciclo continuo e risponde con il messaggio "ACCETTATO" se ci sono
posti disponibili sul battello; in caso contrario invia la frase "NON ACCETTATO" e chiude la
connessione.
• Il server riceve la richiesta, decrementa il numero di posti disponibili ed emette il biglietto contenente
i dati: Destinazione (Arona), cognome, nome, età, prezzo. Il prezzo è determinato secondo il seguente
criterio: clienti minorenni = 25,00 Euro, maggiorenni 32,50 Euro. I dati del biglietto sono prima
stampati a video e successivamente inviati al client.
• Il client, ricevuto il biglietto, stampa i dati contenuti e si mette in attesa di ricevere il segnale di inizio
della crociera.
• Il server, dopo aver venduto tutti i biglietti disponibili (N in tutto), invia a tutti i client il messaggio
"PARTENZA" e chiude le connessioni
Protocollo di comunicazione
In questo esercizio i messaggi scambiati sono composti da oggetti Java serializzabili. In particolare, il server
definisce e condivide con il client le classi Richiesta e Biglietto. Poiché la classe Java String è serializzabile,
è possibile usare questo metodo anche per inviare semplici stringhe (non serve in questo caso terminare un
oggetto “stringa” con newline).
Soluzione
Sulla piattaforma Netlab è disponibile la soluzione dell’esercizio Crociera in battello. La soluzione consiste
in un progetto multimodulo realizzato con il software IntelliJ Idea.
Appendice
http://www.netlab.fauser.edu/media/classi5/tecnologia-progettazione/UniqueRandom.jar
La libreria include la classe UniqueRandom specializzata nella generazione di numeri pseudocasuali interi a
32 oppure 64 bit, diversi uno dall’altro, presi all’interno di un intervallo specificato.
Per utilizzare la libreria UniqueRandom all’interno di un progetto IntelliJ Idea, eseguire le seguenti
operazioni:
1. aprire il progetto, quindi selezionare dal menu File il comando Project Structure...
2. selezionare la voce Modules, quindi fare click su Dependencies e successivamente sul pulsante +
posto parte a destra della finestra; selezionare infine JARs or directories...
A questo punto è possibile utilizzare la classe UniqueRandom nel codice del progetto. Per esempio, è
possibile simulare l’estrazione di tutti i 90 numeri della tombola scrivendo il codice seguente:
import edu.fauser.netlab.UniqueRandom;
Il costruttore della classe UniqueRandom accetta tre parametri: il primo specifica il limite inferiore
dell’intervallo (incluso), il secondo il limite superiore (escluso) e il terzo rappresenta il seme (seed) utilizzato
nella generazione dei valori pseudocasuali. Se il terzo parametro è omesso, il seme è ricavato
automaticamente dall’orologio di sistema.
• nextInt(): genera un nuovo elemento della sequenza pseudocasuale diverso da quelli creati
precedentemente; il risultato restituito da questo metodo è un intero con segno a 32 bit. Nel caso in
cui l’intervallo specificato contenga numeri non rappresentabili su 32 bit, l’invocazione del metodo
nextInt() genera un’eccezione;
• nextLong(): opera in maniera analoga a nextInt() ma restituisce un intero con segno a 64 bit e non
genera alcuna eccezione.
java -version
Per generare un file .jar all’interno di un progetto sviluppato con IntelliJ Idea:
2. Aggiungere un nuovo file .jar premendo il pulsante + , quindi selezionando JAR → From modules
with dependencies;
3. Nella finestra Create JAR from Modules indicare la classe contenente il metodo main, quindi
selezionare l’opzione “Copy to the output directory and link via manifest...” e specificare la
directory che conterrà il file di manifesto (in generale coincidente con la cartella che contiene i
sorgenti Java dell’applicazione);
Per generare il file .jar contenente le classi dell’applicazione, eseguire dal menù principale di IntelliJ Idea il
comando:
Build → Build Artifacts… (action: Build).
2. Preparazione del file .service (necessario per eseguire automaticamente l’applicazione ogni volta che
si avvia il server)
[Unit]
Description=Applicazione "Lotteria"
[Service]
User=utente
WorkingDirectory=/opt/server-lotteria
ExecStart=/usr/bin/java -Xms128m -Xmx256m -jar ServerLotteria.jar
TimeoutStopSec=10
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
5. Eseguire alcuni test per verificare il corretto funzionamento del nuovo servizio erogato dal server
(con Putty oppure con un adeguato client)
Applicazioni proposte
I. [Progetto ClientTraduttore] – Un servizio di traduzione di semplici testi1, erogato dall'host
services.netlab.fauser.edu, è attivo sulla porta TCP 23131.
A livello applicativo, il client invia una richiesta al server in formato testuale (codifica UTF-8)
contenente la frase da tradurre e un insieme di informazioni di supporto secondo il seguente formato:
lingua1/frase1/lingua2<EOL>
in cui:
(a) lingua1: è il codice ISO 639-1 della lingua in cui è stata scritta la frase da tradurre;
(b) frase1: è la frase oggetto della traduzione (se la frase contiene il carattere '/', tale carattere deve
essere codificato in "//");
(c) lingua2: è il codice ISO 639-1 della lingua in cui sarà riportata la traduzione.
Per conoscere i codici delle lingue supportate dal servizio di traduzione, consultare la pagina
https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes.
I dati della richiesta sono riportati su un'unica linea e separati dal carattere '/'. La linea deve terminare
con il simbolo EOL (End Of Line, corrispondente al carattere di newline '\n').
dove "frase2" rappresenta il risultato della traduzione nella lingua richiesta. Nel caso in cui non sia
possibile eseguire la traduzione, il server invia un messaggio d'errore nel formato:
ERR/messaggio<EOL>
È richiesta un'adeguata elaborazione dei dati affinché siano nascosti all'utente finale i dettagli della
struttura dei messaggi scambiati (codici ISO delle lingue, caratteri separatori, ecc.).
II. [Progetto ClientMultilingua] – A partire dal progetto precedente, realizzare un'applicazione client
multilingua che permetta all'utente di scegliere le lingue in cui scrivere e tradurre le frasi. Le lingue
sono scelte mediante un apposito menù contenente almeno cinque lingue (tra cui l'italiano)
supportate dal servizio.
1 L’applicazione che eroga il servizio è stata sviluppata in C# nell’ambito delle attività di laboratorio di Sistemi e reti.
Gli studenti interessati a conoscere il funzionamento del servizio possono richiedere al docente il codice sorgente
dell’applicazione.
Il server eroga il servizio di analisi2 sulla porta 5120/tcp e svolge le seguenti attività.
(a) Riceve la formula chimica del composto da cui ricava i simboli degli elementi e il numero di
atomi di ogni elemento (per esempio, due atomi di idrogeno e uno di ossigeno). Se la formula
ricevuta non è corretta, il server restituisce un apposito messaggio d'errore.
(b) Calcola il peso molecolare del composto a partire dai pesi atomici degli elementi rilevati, quindi
determina la percentuale di ogni singolo elemento3. Il server dispone del file di testo
elementichimici.txt contenente il simbolo chimico, il nome e il peso atomico di tutti gli elementi.
Analisi chimica eseguita dal computer SERVER01 in data 2021/11/30 16:21:37 CET
Composto analizzato: H2O
88,81% Ossigeno (O)
11,19% Idrogeno (H)
Il client legge da tastiera un stringa contenente la formula chimica da analizzare, apre una
connessione al server sulla porta specificata, invia la formula, visualizza l'esito dell'analisi e chiude
la connessione. Il client ripete le precedenti attività fino a quando l'utente inserisce una stringa vuota.
2 Il servizio di analisi chimica è anche disponibile online all’indirizzo services.netlab.fauser.edu (porta 5120/tcp).
Il servizio è erogato da un cluster di server.
3 Il peso atomico dell’idrogeno è pa(H) = 1.008, quello dell’ossigeno pa(O) = 15.999. La molecola d’acqua è
composta da due atomi di idrogeno e uno di ossigeno, quindi il suo peso molecolare è:
pm = 2*pa(H) + 1*pa(O) = 2*1.008 + 1*15.999 = 18.015.
La percentuale di idrogeno è pertanto pari a 2*pa(H) / pm * 100% = 2*1.008 / 18.015 * 100% = 11.19%, quella di
ossigeno 1*pa(O) / pm * 100% = 1*15.999 / 18.015 * 100% = 88.81%