Affrontiamo un ulteriore problema legato alla comunicazione: un programma client invia i messaggi ad un server e nel frattempo deve ricevere i messaggi inviati dal server.
Gestire la comunicazione nel caso in cui io invio un messaggio e subito dopo leggo la risposta non è molto di complicato... il problema nasce quando l'invio di un messaggio da parte del server avviene in un momento non predicibile (e questa avviene in moltissimi casi). Questo è un problema perché se il nostro programma attende l'input dell'utente non può allo stesso tempo attendere i messaggi dal server, nel caso contrario se si fermasse in attesa dei messaggi l'interfaccia grafica non risponderebbe all'input dell'utente che magari nel frattempo vuole inviare delle informazioni al server.
Possiamo però fare in modo che il nostro programma si divida in due flussi di elaborazione: uno per gestire l'input dell'utente e inviare le informazioni al server e l'altro per attendere i messaggi dal server e visualizzarli.
classe principale
Il programma che scriviamo è ridotto ai minimi termini: una sola lista per contenere ciò che ci invia il server e una casella per inviare le informazioni al server.
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.Socket; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.ListView; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; import javafx.stage.Stage; public class Finestra extends Application { ListView<String> messaggi = new ListView<>(); TextField casellaMessaggio = new TextField(); Writer uscita; // lo userò per inviare le informazioni BufferedReader ingresso; // lo userò per ricevere le informazioni public static void main(String args[]) { launch(args); } @Override public void start(Stage ps) throws Exception { GridPane g = new GridPane(); g.add(messaggi, 0, 0); g.add(casellaMessaggio, 0, 1); Scene s = new Scene(g); ps.setScene(s); ps.setTitle("messaggi"); ps.show(); // action su una casella è la pressione del tasto invio casellaMessaggio.setOnAction( e-> aggiungiMessaggio()); // creo tutti gli oggetti necessari per collegarmi ad un server remoto Socket connessione = new Socket("localhost",7777); uscita = new OutputStreamWriter(connessione.getOutputStream()); InputStreamReader isr = new InputStreamReader(connessione.getInputStream()); ingresso = new BufferedReader(isr); // creo un thread che riceverà i messaggi e poi non mi curo più // del canale di ingresso in questa classe ThreadRicevitore lavoratore = new ThreadRicevitore(ingresso, messaggi); lavoratore.start(); } private void aggiungiMessaggio() { String testo = casellaMessaggio.getText(); try { uscita.write(testo+"\n"); uscita.flush(); } catch (IOException e) { e.printStackTrace(); } } }
Il programma è davvero molto breve... troppo... per esercizio va sistemato chiudendo tutte le risorse magari aggiungendo un pulsante chiudi che si occupa di ciò.
thread che ascolta
import java.io.BufferedReader; import java.io.IOException; import javafx.application.Platform; import javafx.scene.control.ListView; public class ThreadRicevitore extends Thread { BufferedReader br; ListView<String> elenco; String letto=null; public ThreadRicevitore(BufferedReader br, ListView<String> elenco) { this.br = br; this.elenco = elenco; } public void run() { do{ try { letto = br.readLine(); // letto sarà null se è stato raggiunta la fine dello stream // questo nel caso della comunicazione client/server succede non quando // non ci sono ulteriori righe da leggere (nel qual caso aspetta), // ma quando lo stream viene chiuso if(letto!=null){ Platform.runLater( ()->{ elenco.getItems().add(letto); }); } }catch (IOException e) { e.printStackTrace(); } }while( letto!=null ); } }
Il thread non fa altro che attendere un messaggio dal server e aggiungerlo alla lista, usa
Platform.runLater
per eseguire modifiche sull'interfaccia
grafica JavaFX siccome questa libreria non garantisce che modifiche fatte alll'interfaccia
da un thread diverso dal suo vadano a buon fine.
runLater
si comporta un po' come il setOnAction
che si usa per impostare
l'azione da legare ad un pulsante, l'unica differenza è che in questo caso non abbiamo
parametri (mentre l'azione sul pulsante aveva l'evento associato al click del mouse)
quindi quello che era e ->
diventa () ->
.