Menu


Trascrizione diapositive

1. Input, output e networking

  • Complessità: ALTA.


2. Introduzione all’input-output

  • Nelle unità precedenti abbiamo compreso tutte le tecniche alla base del linguaggio Java, tuttavia ancora non è stato fatto alcun accenno ad uno dei pilastri fondamentali della programmazione: l’input da tastiera.
  • Il motivo per il quale si è deciso di parlare solo a questo punto del processo di input è relativo al grado di complessità richiesto, infatti, se con gli altri linguaggi bastava scrivere una singola linea di codice per acquisire un determinato dato, in Java è necessario scrivere molto più codice poiché sarà indispensabile aprire un flusso dati (detto anche stream).
  • Lo stream permette di prelevare informazioni da una qualsiasi fonte esterna, quindi non solo dalla tastiera ma anche da un file, dalla LAN, dalla rete internet, etc.


3. Come funziona lo stream

  • L’importazione dati da fonte esterna è un processo relativamente semplice, nell’immagine a fianco è mostrato graficamente il flusso operativo richiesto.
[Immagine] Come funziona lo stream


4. Lettura di un input da tastiera

  • Il codice sorgente per acquisire una stringa dalla tastiera riproduce lo schema grafico visto in precedenza: sarà generato un oggetto di tipo «InputStreamReader» cui viene passato il driver della tastiera «System.in», pertanto l’oggetto «isr» rappresenterà lo stream vero e proprio; in seguito sarà generato un altro oggetto di tipo «BufferedReader» chiamato «lettura» a cui viene passato l’oggetto «isr»; «BufferedReader» viene utilizzato perché incorpora il metodo «readLine()» che permette di acquisire tutti i caratteri necessari fino alla pressione del tasto Invio:

[Download]

import java.io.*;
public class Programma {
public static void main(String[] args) {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader lettura = new BufferedReader(isr);

try {
System.out.println("Scrivi una frase:");
String stringa = lettura.readLine();
System.out.println("Hai scritto:");
System.out.println(stringa);
}
catch(IOException error) {
error.printStackTrace();
}
finally { // Chiusura del flusso dati
try {
lettura.close();
} catch(IOException error1) {
error1.printStackTrace();
}
}
}
}

  • Notiamo che l’applicativo mostrerà sullo schermo lo stesso stream composto dall’utente, giacché il suo contenuto sarà immagazzinato nella variabile «stringa».


5. Gestione dell’input

  • Abbiamo visto che il metodo «readLine()» permette di acquisire tutti i caratteri digitati nella tastiera, memorizzando lo stream in una variabile di tipo «String»; questo può essere un vantaggio solo nel caso si intenda acquisire un testo.
  • Se vogliamo acquisire un carattere, un numero intero o un numero in virgola mobile: bisognerà usare delle funzioni particolari; esse vanno implementate sulla stringa restituita da «readLine()», tale stringa sarà trasformata nel genere di dato desiderato.
  • Di seguito una tabella ci mostra quale funzione usare per acquisire qualsivoglia tipo di dato:

Tipo di dato da acquisire

Funzione

String

String stringa = lettura.readLine();

Int

int stringa = Integer.parseInt(lettura.readLine());

Float

float stringa = Float.parseFloat(lettura.readLine());

Double

double stringa = Double.parseDouble(lettura.readLine());

Char

char stringa = (lettura.readLine()).charAt(0);


  • «Int», «Float» e «Double» possono generare una «NumberFormatException» che si verifica quando il valore immesso non può essere convertito.


6. Accenno sul parametro del metodo «main()»

  • Quando sono state presentate le peculiari caratteristiche del metodo «main()»: non abbiamo fatto alcun accenno riguardante il parametro «String args[]».
  • «String args[]» è un argomento obbligatorio di «main()», che permette di dotare il software di un sistema di input da riga di comando.
  • A livello operativo la procedura per avviare l’applicativo non cambia: se prima era necessario eseguire un comando del tipo «java Programma», adesso dobbiamo sapere che sarà anche possibile scrivere «java Programma Parametro1 Parametro2», dove «Parametro1» e «Parametro2» a livello codice sono visti come dei singoli valori; pertanto la stringa «args[0]» avrà valore «Parametro1», mentre la stringa «args[1]» avrà valore «Parametro2».


7. Gestione dei file

  • Proprio come avveniva per il processo di input da tastiera: per modificare un file sarà necessaria l’apertura di uno o più stream.
  • In sostanza per leggere/scrivere su una risorsa locale dovremo creare un oggetto di tipo «File» e aprire su di esso un flusso dati, pertanto se lo stream sarà di lettura dovrà essere generato un oggetto di tipo «FileInputStream», mentre se intendiamo generare un flusso dati in uscita(di scrittura) dovrà essere creato un oggetto di tipo «FileOutputStream».
  • Nell’esempio illustrato nella prossima slide creeremo una copia di backup di un file immesso in input da parametro, il processo richiesto ci permetterà di comprendere al meglio come gestire un flusso dati non di rete.


8. Scrivere e leggere da un file


[Download]

(Prima parte del codice)

import java.io.*;
public class Programma {
public static void main(String[] args) {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
if (args.length == 0) { // Se non viene specificato alcun parametro…
System.out.println ("Specificare il nome del file!"); // Messaggio di errore
System.exit(0); // Uscita dal programma
}
File inputFile = new File(args[0]); // Apertura del file specificato nel parametro
File outputFile = new File(args[0] + ".backup"); // Generazione di un nuovo file con estensione .backup
inputStream = new FileInputStream(inputFile); // Stream di lettura sul file passato sotto forma di parametro
outputStream = new FileOutputStream(outputFile); // Stream di scrittura sul nuovo file
for (int b = 0; ((b = inputStream.read()) != -1);) { // Lettura di ogni singolo byte sul file passato da parametro
outputStream.write(b); // Scrittura di ogni singolo byte sul nuovo file generato
}
System.out.println("Copia del file eseguita in " + args[0] + ".backup");
// Messaggio informativo di conferma
}

9. Scrivere e leggere da un file


[Download]

(Seconda parte del codice)

catch (FileNotFoundException error) { // Se il file specificato non è stato trovato…
System.out.println("File non trovato!"); // Messaggio di errore
System.exit(0); // Uscita dal programma
}
catch (IOException error1) { // Se si verifica un errore Input/Output…
error1.printStackTrace(); // Messaggio di errore
}
finally {
try {
inputStream.close(); // Chiusura del flusso di stream in lettura
outputStream.close(); // Chiusura del flusso di stream in scrittura
}
catch (IOException error2) { // Se si verifica un errore Input/Output…
error2.printStackTrace(); // Messaggio di errore
}
}
}
}


10. Esempio di funzionamento

11. Altri metodi della classe «File» di java.io

  • Per ultimare il discorso che riguarda il File System, è opportuno segnalare alcuni metodi che possono essere invocati da un oggetto istanziato di tipo «File»:
  • File test = new File ("C:\Test\Test.txt");
  • - I metodi «getName()» e «getPath()» forniscono rispettivamente sotto forma di stringa: il nome del file ed il percorso assoluto (se specificato) dello stesso.
  • - Il metodo «exists()» ritorna un valore di tipo «boolean» e permette di capire se il file o la directory che è stata specificata esiste; in modo analogo i metodi «isFile()» e «isDirectory()» consentono di comprendere se il percorso che è stato indicato è relativo ad un file o ad una directory esistente.
  • - Il metodo «list()» permette di restituire la lista completa di tutti i file e di tutte le directory presenti nel percorso che è stato specificato, percorso che in questo caso deve essere necessariamente relativo ad una cartella.
  • - Il metodo «mkdir()» permette di creare una directory nel File System secondo il percorso relativo che è stato indicato; ritorna valore «true» se l’operazione è andata a buon fine o «false» se l’operazione non è stata eseguita con successo; similmente il metodo «delete()» rimuove una directory (se vuota) o un file dal PC.
  • - Il metodo «renameTo(inputFile)» accetta in argomento un altro oggetto di tipo «File»; il compito del metodo in questione è quello di cambiare il nome del file o della directory specificata, nel file o nella directory specificata in argomento.


12. Serializzazione di oggetti

  • Il principio mediante il quale è possibile salvare un insieme di oggetti in un determinato istante di tempo, viene chiamato processo di «serializzazione».
  • Per serializzare un oggetto il linguaggio Java mette a disposizione l’interfaccia «Serializable» inclusa nella libreria «java.io», dunque per procedere sarà necessario implementare tale interfaccia in ogni classe istanziata.
  • Inoltre, per ciascuna classe è necessario dichiarare e inizializzare una costante chiamata «SerialVersionUID», che servirà nella fase di recupero dei dati; se il «SerialVersionUID» del destinatario è diverso da quello del mittente: il processo di «deserializzazione» si concluderà sollevando un’eccezione chiamata «InvalidClassException».
  • In caso di assenza della costante «SerialVersionUID»: la JVM né predisporrà una di default, ciò é fortemente sconsigliato giacché il suo valore è molto suscettibile; se la classe serializzata del mittente è diversa da quella del destinatario (basta cambiare un solo attributo), il «SerialVersionUID» calcolato sarà differente e quindi diverrà impossibile leggere i dati.


13. Codice per serializzare oggetti


[Download]

(Prima parte del codice)

package Serializzazione;
import java.io.*;
import java.util.*;

class Persona implements Serializable {
private static final long serialVersionUID = -7176050266479335730L; // Dichiarazione del valore di controllo
private String nome; // Dichiarazione del nome
private String cognome; // Dichiarazione del cognome

public Persona(String nome, String cognome) {
this.nome = nome; // Impostazione del nome della persona
this.cognome = cognome; // Impostazione del cognome della persona
}
public String getNome() {
return this.nome; // Restituisce il nome della persona
}
public String getCognome() {
return this.cognome; // Restituisce il cognome della persona
}
}


14. Serializzazione di oggetti

  • Una volta definita la classe (o le classi) da serializzare, è necessario istanziare tre oggetti:

  • - L’oggetto di tipo «File»: che permette di dichiarare il file (nel nostro caso «oggetto.txt») passato in argomento.
  • - L’oggetto di tipo «FileOutputStream»: per generare un flusso dati di scrittura sul file (argomento del costruttore), se il file non esiste sarà creato.
  • - L’oggetto di tipo «ObjectOutputStream» cui è passato in argomento l’oggetto di tipo «FileOutputStream»; «ObjectOutputStream» è utilizzato perché incorpora il metodo «writeObject()» che ci permette di scrivere l’oggetto.

  • Per rendere più interessante l’esempio proposto: è stato scritto un «ArrayList» sul file «oggetto.txt», in modo tale da memorizzare più elementi.


15. Codice per serializzare oggetti


[Download]

(Seconda parte del codice)

public class Serializza {
public static void main(String[] args) {
ArrayList lista = new ArrayList(100); // Crea una lista con capacità iniziale pari a 100
lista.add(new Persona("Mario", "Rossi")); // Inserisce il primo elemento nella lista
lista.add(new Persona("Luigi", "Bianchi")); // Inserisce il secondo elemento nella lista
FileOutputStream fileOutputStream = null;
ObjectOutputStream objectOutputStream = null;

try {
File oggetto = new File("oggetto.txt"); // Dichiarazione del file
fileOutputStream = new FileOutputStream(oggetto); // Stream di scrittura sul file
objectOutputStream = new ObjectOutputStream (fileOutputStream); // Stream di scrittura oggetto
objectOutputStream.writeObject (lista); // Scrittura della lista
System.out.println("Copia dell'oggetto salvata correttamente sul file!"); // Messaggio informativo di conferma
}
catch (IOException error) { // Se si verifica un errore Input/Output...
error.printStackTrace(); // Messaggio di errore
}
finally {
try {
fileOutputStream.close(); // Chiusura del flusso di stream in scrittura sul file
objectOutputStream.close(); // Chiusura del flusso di stream in scrittura oggetto
}
catch (IOException error1) { // Se si verifica un errore Input/Output...
error1.printStackTrace(); // Messaggio di errore
}
}
}
}


16. Deserializzazione di oggetti

  • Per deserializzare l’«ArrayList» bisogna applicare il processo inverso, quindi è necessario istanziare tre oggetti:
    - L’oggetto di tipo «File»: che permette di dichiarare il file (nel nostro caso «oggetto.txt») passato in argomento.
    - L’oggetto di tipo «FileInputStream»: per generare un flusso dati di lettura sul file (argomento del costruttore), se il file non esiste sarà generata un’eccezione di tipo «FileNotFoundException».
    - L’oggetto di tipo «ObjectInputStream» cui è passato in argomento l’oggetto di tipo «FileInputStream»; «ObjectInputStream» è utilizzato perché incorpora il metodo «readObject()» che ci permette di leggere l’oggetto.


17. Codice per deserializzare oggetti


[Download]

(Prima parte del codice)

package Serializzazione;
import java.io.*;
import java.util.*;

public class Deserializza {
public static void main(String[] args) {
ArrayList lista = null; // Crea una lista vuota
FileInputStream fileInputStream = null;
ObjectInputStream objectInputStream = null;
try {
File oggetto = new File("oggetto.txt"); // Dichiarazione del file
fileInputStream = new FileInputStream(oggetto); // Stream di lettura sul file
objectInputStream = new ObjectInputStream(fileInputStream); // Stream di lettura oggetto
lista = (ArrayList) objectInputStream.readObject(); // Lettura della lista
// Visualizzazione degli elementi nella lista creata
for (int i = 0; i < lista.size(); i++) {
System.out.print(((Persona) lista.get(i)).getNome() + " ");
System.out.println(((Persona) lista.get(i)).getCognome());
}

18. Codice per deserializzare oggetti


[Download]

(Seconda parte del codice)

}
catch (FileNotFoundException error) { // Se il file non esiste...
error.printStackTrace(); // Messaggio di errore
}
catch (ClassNotFoundException error1) {
// Se la classe non esiste...
error1.printStackTrace(); // Messaggio di errore
}
catch (IOException error2) {
// Se si verifica un errore Input/Output...
error2.printStackTrace(); // Messaggio di errore
}
finally {
try {
fileInputStream.close(); // Chiusura del flusso di stream in lettura sul file
objectInputStream.close(); // Chiusura del flusso di stream in lettura oggetto
}
catch (IOException error3) { // Se si verifica un errore Input/Output...
error3.printStackTrace(); // Messaggio di errore
}
}
}
}


19. Il concetto di indirizzo IP nel flusso dati di rete

  • Per permettere ad una applicazione di scambiare informazioni, è necessario conoscere alcuni concetti riguardanti l’organizzazione del software di rete.
  • Come accade nella vita reale, se intendiamo scambiare un’informazione con qualcuno è essenziale conoscere l’indirizzo del destinatario, perciò anche per quanto riguarda il mondo dei PC ogni computer deve avere una propria residenza, che viene chiamata indirizzo IP.
  • L’indirizzo IP è formato da quattro valori interi di otto bit (inclusi tra 0 e 255), in cui ogni valore è separato da un punto; ad esempio «192.168.1.100» è un indirizzo IP possibile.
  • Ogni workstation presente in rete (WAN o LAN) possiede un indirizzo IP per cui è raggiungibile dagli altri PC.


20. Il concetto di client e server nel flusso dati di rete

  • Il «server» è un semplice PC che mette a disposizione uno o più servizi che saranno offerti ad altri PC chiamati «client»; giacché un «client» potrebbe effettuare delle richieste verso un «server» in qualsiasi momento, il «server» dovrà essere acceso e disponibile per tutto l’arco della giornata.
  • Un classico esempio di architettura client/server è data da internet, dal momento che quando navighiamo sul web, il nostro computer si comporta da «client» che inoltra delle richieste verso un «server», che ci risponderà mostrandoci un determinato sito.
  • Ogni applicazione installata sul server che offre un servizio in grado di scambiare informazioni, deve specificare un numero di porta per permettere al «client» l’accesso; ad esempio se un «client» intende visualizzare un sito web, dovrà inoltrare una richiesta sulla porta «80» del «server», mentre se intende scambiare un file, la richiesta dovrà essere trasmessa sulla porta «21», etc.
  • Le porte virtuali disponibili sono «65.535», di cui le prime «1.023» sono porte conosciute dette Well Known Port.
  • Senza entrare nello specifico di ogni protocollo, dobbiamo sapere che la coppia «IndirizzoIP:Porta» rappresenta il «socket», cioè permette ad un «client» di instaurare una connessione con un «server».
  • Il codice mostrato nella prossima slide è un esempio di chat in Java, che ci permette di comprendere come un «server» possa instaurare una connessione con un singolo «client».


21. Codice relativo al computer server


[Download]

(Prima parte del codice)

package Networking;
import java.net.*;
import java.io.*;

public class Server {
private ServerSocket server; // Inizializzazione della variabile relativa al server
private Socket connessione; // Inizializzazione della variabile relativa al canale di comunicazione
private InputStreamReader client; // Inizializzazione della variabile di lettura dal client
private BufferedReader dalClient; // In questo modo usufruiamo del metodo readLine() per il client
private PrintStream alClient; // Inizializzazione della variabile di scrittura verso il client
private String nome; // Inizializzazione della variabile relativa al nickname

public Server(String nome) {
this.nome = nome; // Impostazione del nickname di accesso alla chat
try {
server = new ServerSocket(2000); // Il server viene istanziato sulla porta 2000
System.out.println("Server attivo"); // Messaggio di conferma
connessione = server.accept(); // Il server si pone in attesa di una connessione
client = new InputStreamReader (connessione.getInputStream()); // Aperto un flusso dati di lettura dal client
dalClient = new BufferedReader(client); // In questo modo usufruiamo del metodo readLine() per il client
alClient = new PrintStream (connessione.getOutputStream()); // Aperto un flusso dati di scrittura verso il client
}

22. Codice relativo al computer server


[Download]

(Seconda parte del codice)

catch (Exception error) { // Se si verifica un errore…
System.out.println("Impossibile avviare il server"); // Messaggio di errore
}
}
public void conversazione() {
String messaggio = ""; // Inizializzazione della variabile relativa al messaggio da inviare
InputStreamReader isr = new InputStreamReader(System.in); // Aperto un flusso dati di lettura dalla tastiera del server
BufferedReader lettura = new BufferedReader(isr); // In questo modo usufruiamo del metodo readLine() per il server

try {
alClient.println("Benvenuto nella chat - Sei connesso al server."
+ " Digita 'Esci' per uscire.");
// Messaggio inviato al client
while (!messaggio.endsWith("Esci")) { // Fino a che la fine della stringa messaggio è diversa da Esci...
messaggio = dalClient.readLine(); // Leggi il testo dal client e memorizza il contenuto in messaggio
System.out.println (messaggio); // Stampa il testo del client
if (!messaggio.endsWith("Esci") ) { // Se la fine della stringa messaggio valorizzata dal client è diversa da Esci...
messaggio = lettura.readLine(); // Leggi il testo dalla tastiera del server e memorizza il contenuto in messaggio
alClient.println(nome + " scrive: " + messaggio); // Stampa il testo del server nel client
}
}
}

23. Codice relativo al computer server


[Download]

(Terza parte del codice)

catch (Exception error1) { // Se si verifica un errore…
System.exit(0); // Esci
}
}

public static void main(String[] args) {
Server comunica = new Server("Domenico"); // Generiamo l’oggetto server e impostiamo il nickname
comunica.conversazione(); // Avviamo la conversazione!
}
}


24. Connessione da parte del client

  • Al momento dell’esecuzione del codice sorgente relativo alla macchina «server», il PC sarà posto in stato di attesa di una connessione sulla porta «2000»; la macchina «client» che intenderà connettersi, dovrà conoscere il numero di porta e l’indirizzo del «server».
  • Se l’indirizzo del «server» è uguale a quello del «client»: la connessione dovrà essere eseguita su IP numero «127.0.0.1» (come vedremo nel prossimo esempio).
  • Se il «server» non si trova nella macchina del «client» ma è comunque raggiungibile attraverso la rete locale: per connettersi si dovrà conoscere l’indirizzo IP del «server», che possiamo ottenere attraverso il comando «ipconfig».
  • Se il «server» dovrà essere reso raggiungibile tramite la rete internet, sarà necessario impostare il relativo router in maniera tale da porre il «server» in DMZ; a questo punto il «client» potrà effettuare connessioni verso il «server» attraverso l’IP pubblico assegnato dall’ISP.
  • Se l’indirizzo IP pubblico è di tipo dinamico, si ha il problema di un diverso IP ad ogni connessione; in questo caso è possibile usufruire di alcuni servizi gratuiti di DNS dinamico che permettono di utilizzare un dominio fisso come «mionome.server.com» che identificano proprio un indirizzo IP dinamico, così ad ogni connessione supponendo che l’IP assegnato dall’ISP sia ad es. «98.40.55.21», verrà tradotto in questo modo:

  • http://mionome.server.com (nome di dominio) -> 98.40.55.21(IP pubblico assegnato dall’ISP) -> 192.168.1.50 (indirizzo locale del server).


25. Fornitori di servizi di DNS dinamico


Fornitore

Nomi di dominio disponibili

[Immagine] DNSdynamic

*.dyndns.org, *.dvrdns.org, *.dyndns.tv, *.dyndns.info, *.homeip.net, *.dyndns.biz, *.mine.nu, *.dnsalias.com, *.dyndns-ip.com, *.selfip.com, *.at-band-camp.net, *.barrel-of-knowledge.info, *.barrell-of-knowledge.info, *.better-than.tv, *.blogdns.com, *.blogdns.net, *.blogdns.org, *.blogsite.org, *.boldlygoingnowhere.org, *.broke-it.net, *.buyshouses.net, *.cechire.com. *.dnsalias.com, *.dnsalias.net, *.dnsalias.org, etc.

[Immagine] no-ip

*.3utilities.com, *.bounceme.net, *.hopto.org, *.myftp.biz, *.myftp.org, *.myvnc.com, *.no-ip.biz, *.no-ip.info, *.no-ip.org, *.redirectme.net, *.servebeer.com, *.serveblog.net, *.servecounterstrike.com, *.serveftp.com, *.servegame.com, *.servehalflife.com, *.servehttp.com, *.servemp3.com, *.servepics.com, *.servequake.com, *.sytes.net, *.zapto.org.


  • I fornitori riportati in tabella sono stati selezionati a titolo di esempio, il lettore è libero di scegliere il servizio di reindirizzamento che ritiene più adatto alle proprie esigenze.
  • Con il simbolo dell’asterisco (*) s’intende un nome a vostra scelta.


26. Codice relativo al computer client


[Download]

(Prima parte del codice)

package Networking;
import java.net.*;
import java.io.*;

public class Client {
private Socket connessione; // Inizializzazione della variabile relativa al canale di comunicazione
private InputStreamReader server; // Inizializzazione della variabile di lettura dal server
private BufferedReader dalServer; // In questo modo usufruiamo del metodo readLine() per il server
private PrintStream alServer; // Inizializzazione della variabile di scrittura verso il server
private String nome; // Inizializzazione della variabile relativa al nickname

public Client(String nome) {
this.nome = nome; // Impostazione del nickname di accesso alla chat
try {
connessione = new Socket("127.0.0.1", 2000); // Connessione al server sulla porta 2000 della stessa macchina
server = new InputStreamReader (connessione.getInputStream()); // Aperto un flusso dati di lettura dal server
dalServer = new BufferedReader (server); // In questo modo usufruiamo del metodo readLine() per il server
alServer = new PrintStream (connessione.getOutputStream()); // Aperto un flusso dati di scrittura verso il server
}
catch (Exception error) { // Se si verifica un errore…
System.out.println("Connessione non riuscita"); // Messaggio di errore
}
}

27. Codice relativo al computer client


[Download]

(Seconda parte del codice)

public void conversazione() {
String messaggio = ""; // Inizializzazione della variabile relativa al messaggio da inviare
InputStreamReader isr = new InputStreamReader(System.in); // Aperto un flusso dati di lettura dalla tastiera del client
BufferedReader lettura = new BufferedReader(isr); // In questo modo usufruiamo del metodo readLine() per il client

try {
while (!messaggio.endsWith("Esci")) { // Fino a che la fine della stringa messaggio è diversa da Esci...
messaggio = dalServer.readLine(); // Leggi il testo dal server e memorizza il contenuto in messaggio
System.out.println(messaggio); // Stampa il testo del server
if (!messaggio.endsWith("Esci")) { // Se la fine della stringa messaggio valorizzata dal server è diversa da Esci...
messaggio = lettura.readLine(); // Leggi il testo dalla tastiera del client e memorizza il contenuto in messaggio
alServer.println(nome + " scrive: " + messaggio); // Stampa il testo del client nel server
}
}
}
catch (Exception error1) { // Se si verifica un errore…
System.exit(0); // Esci
}
}

28. Codice relativo al computer client


[Download]

(Terza parte del codice)

public static void main(String[] args) {
Client comunica = new Client("Marco"); // Generiamo l’oggetto client e impostiamo il nickname
comunica.conversazione(); // Avviamo la conversazione!
}
}