|
Java Tutorial - Parte 2 0.1
Un tutorial per esempi
|
In questo capitolo il lettore impererà l'uso di un protocollo, basato su IP, alternativo al TCP: il protocollo UDP (User Datagram Protocol). Oltre a questo, verrà affrontato il tema della gestione dei risultati intermedi in un wroker-thread poichè, in alcune situazioni particolari (come questa) essa non è affatto banale.
In una partita tra giocatori remoti usando il protocollo TCP/IP vi è il problema per il lato client della connessione di indicare nei parametri di rete l'indirizzo IP o il nome della macchina che funge da server di gioco. E' un problema che si risolve facilemente usando il comando ipconfig dal prompt di MS-DOS:
>ipconfig Indirizzo IPv6 locale rispetto al collegamento . : fe80::d879:5dc6:dbeb:dadb%10 Indirizzo IPv4. . . . . . . . . . . . : 10.117.242.114 Subnet mask . . . . . . . . . . . . . : 255.255.255.0 Gateway predefinito . . . . . . . . . : 10.117.242.7
ma gli user sono pigri e, molti di essi, analfabeti in senso informatico, e la maggior parte di essi ignora persino l'esistenza del comando ipconfig. Dobbiamo implementare un feature che faciliti la ricerca dei giocatori online. La soluzione più professionale sarebbe quella di implementare un server centralizzato a cui i giocatori si connettono; in questo modo tutti i giocatori sarebbero dei client e l'indirizzo IP sarebbe conosciuto.
Se il server fosse su un dominio specifico come per esempio mastermind.net non sarebbe nemmeno necessario conoscere il suo indirizzo IP: ci pensa il DNS (Domain Name System = il sistema dei nomi a dominio) a risolvere il nome del host in un indirizzo IP. Il nome host potrebbe addirittura essere hardcoded nel codice del pannello delle proprietà.
Mi sembra uno spreco di denaro comprare un dominio per un giochino per bambini. Quello che basta ai giocatori di Mastermind è trovare un avversario nei paraggi per esempio un familiare nella stanza accanto oppure un collega dell'ufficio accanto al nostro o al limite al piano di sopra della sede aziendale.
E il protocollo UDP è proprio quello che risolve questa situazione (a gratis).
Come il TCP/IP, il protocollo UDP si basa su IP ed è un protocollo di trasporto che si trova al quarto livello della catena ISO/OSI (vedi Il modello ISO/OSI). A differenza di TCP, che è connection oriented (=orientato alla connessione) il protocollo UDP è connectionless (=senza connessione).
Se TCP somiglia ad una telefonata (vedi Il protocollo TCP/IP) UDP assomiglia alle cartoline postali: il mittente scrive un pacchetto UDP specificando il mittente ed il destinatario. Successivamente, invia il pacchetto UDP sulla connessione di rete: il mittente non ha alcuna garanzia che il pacchetto sia stato letto; non si è nemmeno certi che sia arrivato, o che sia arrivato integro, o che il destinatario sia davvero connesso; potrebbe anche aver disabilitato l'interfaccia di rete (p.es. la modalità aereo nei computer portatili).
Il protocollo TCP connette due endpoints tramite un socket che è unico in ambito Internet (vedi Cos'è un socket?); in pratica TCP connette due, e solo due, dispositivi. UDP, al contrario, consente di inviare i messaggi a molti destinatari senza conoscerne l'indirizzo IP specifico ma usando un indirizzo IP speciale chiamato
L'indirizzo di broadcast è specifico di una rete locale e si ottiene eseguendo la operazione logica OR tra l'indirizzo di rete e l'inverso della sua netmask. L'indirizzo di rete si ottiene eseguendo la operazione logica AND tra l'indirizzo IP del proprio dispositivo e la netmask.
indirizzo host 10.117.202.114
AND netmask 255.255.255.0
---------------
indirizzo di rete 10.117.202.0
OR netmask inversa 0. 0. 0.255
--------------
indirizzo broadcast 10.117.202.255
Pertanto, se io invio un messaggio all'indirizzo IP 10.117.202.255 esso raggiungerà TUTTI i dispositivi collegati alla mia stessa rete locale. E per raggiungere le altre reti? Non si può: i pacchetti UDP broadcast e multicast non possono superare il gateway, cioè il nostro router.
L'indirizzo di broadcast viene usato raramente e solo per scopi specifici come per esempio nel procollo ARP (Adress Resolution Protocol) che è un protocollo di secondo livello del modello ISO/OSI (livello data-link). ARP è usato, per esempio, nelle reti ethernet per risolvere un indirizzo IP in un MAC address, l'indirizzo fisico della scheda ehternet.
Nelle applicazioni si preferisce inviare i pacchetti destinati a più macchine al cosidetto indirizzo multicast (=destinazione multipla) poichè con il multicast è possibile far giungere i pacchetti solo ai destinatari che fanno parte dello stesso "gruppo" cioè solo a coloro che si registrano con lo stesso indirizzo multicast.
Gli indirizzo multicast sono definiti ed assegnati da IANA (=Internet Assigned Numbers Authority = autorità per l'assegnazione dei numeri Internet) e si trovano nel range che va da 224.0.0.0 fino a 239.255.255.255
Come accennato, se io invio un pacchetto UDP ad un indirizzo broadcast o multicast esso raggiunge molti destinatari anche se il mittente non sa nulla di quanti destinatari abbia raggiunto nè se il messaggio, giunto a destinazione regolarmente, sia stato effettivamente letto da una qualche applicazione.
La strategia implementata in mastermind per cercare giocatori in rete è la seguente:
nome_giocatore@hostname/indirizzo_IP:porta Per chi vuole rispolverare le caratteristiche principali delle combo-box ricordo che ne ho già scritto in Il componente JComboBox.
Per inviare i pacchetti UDP ad un indirizzo multicast è necessario registrarsi ad uno specifico "gruppo" in modo che i pacchetti UDP inviati al gruppo raggiungano tutti i membri del gruppo stesso. Il gruppo viene definito con due informazioni
230.18.8.0 in Mastermind essere cambiato nelle proprietà della applicazione.Ogni macchina connessa ad una rete possiede almeno due interfacce di rete:
localhost ed il cui indirizzo IP è il 127.0.0.1 La strategia di Mastermind è quella di inviare e ricevere i pacchetti UDP su tutte / da tutte le interfaccie di rete attive ed abilitate al multicasting: in questo modo il giocatore può connettersi ad un qualsiasi remoto su diverse reti. Per esempio una macchina potrebbe essere collegata ad una rete Ethernet ed anche ad una wireless: nel pannello delle proprietà la lista dei server Mastermind conterrà sia i server di gioco presenti sulla LAN cablata che su quella senza fili.
Il JRE (Jave Runtime Environment) mette a disposizione del programmatore una nutrita serie di classi per gestire i protocollo UDP e per gli indirizzamenti multicast. Queste classi fanno parte del package java.net. Le classi Java usate nel progetto mastermind sono:
NetworkInterface: rappresenta una interfaccia di rete MulticastSocket: il socket associato ad un indirizzo multicast ed ad una specifica interfaccia DatagramPacket: il pacchetto UDP inviato / ricevuto attraverso il socket InetAddress: rappresenta un indirizzo IP, nel caso di Mastermind è un indirizzo IP multicast InetSocketAddress: rappresenta un endpoint di un socket e quindi contiene, oltre al InetAddress anche la porta a cui ci si collegaQuesta classe Java rappresenta una interfaccia di rete che può essere attiva oppure no. La classe non possiede costruttori: per ottenere un oggetto di questa classe è necessario richiamare uno dei suoi metodi statici quali:
getByInetAddress: che ritorna la interfaccia di rete che ha uno specifico indrizzo IP getByName: che ritorna la interfaccia di rete che ha uno specifico nome come per esempio "wireless" o "ethernet" getNetworkInterfaces: che ritorna un enumerato contenente tutte le interfacce di rete presenti sulla macchinaUna volta ottenuto un oggetto di classe NetworkInterface è possibile richiamare i suoi metodi concreti per ottenere una serie di informazioni sulla interfaccia di rete:
isLoopback(): ritorna TRUE se la interfaccia è quella di loopback isUp(): ritorna TRUE se la interfaccia è attiva isVirtual(): ritorna TRUE se la interfaccia è virtuale supportsMulticast(): ritorna TRUE se la interfaccia supporta il multicasting Ovviamente, il secondo e l'ultimo metodo concreto sono proprio quelli che interessano noi per la ricerca dei giocatori in rete nel progetto Mastermind.
Questa classe rappresenta il socket multicast sul quale si inviano e si ricevono i pacchetti UDP. I metodi predisposti allo scopo sono i soliti: send per inviare il pacchetto e receive per ricevere un pacchetto. Un altro metodo definito dalla classe e che ci interessa da vicino è il
il quale, come accennato in precedenza, "registra" questo socket nel "gruppo" multicast. Il primo argomento da fornire al metodo è l'indirizzo multicast del gruppo (nel caso del Mastermind è il "230.18.8.0"); il secondo argomento al metodo è la interfaccia di rete che deve fare parte del gruppo.
Da notare che possono far parte dello stesso gruppo multicast anche due o più interfacce di rete ed infatti è ciò che accade in Mastermind: si cercano giocatori su tutti i collegamenti in rete, su qualaiasi interfaccia.
Un pacchetto UDP è rappresentato dalla classe DatagramPacket che possiede diversi costruttori ma tutti condividono almeno un argomento: una array di byte che costituisce il messaggio vero e proprio che viene chiamato in gergo payload.
Vi è da osservare che l'input/output del protocollo UDP è ben diverso da quello che abbiamo visto per il TCP/IP (vedi JavaNIO (Java New IO)): in UDP non ci sono i canali o i buffers e nemmeno gli streams che abbiamo usato in Un piccolo telnet.
Quello che abbiamo nel protocollo UDP è un buffer di bytes che viene inviato e ricevuto in modalità bloccante: Nella classe MulticastSocket abbiamo solo due metodi di I/O e tutti e due accettano come argomento solo un oggetto di questa classe:
Useremo una istanza di questa classe per creare un oggetto di classe SocketAddress contenete l'indirizzo IP multicast col quale possiamo "registrare" il socket nel gruppo mastermind:
La classe SocketAddress è una classe base astratta non legata ad un particolare protocollo di rete. Derivata da essa vi è la InetSocketAddress che possiede dei costruttori e che rappresenta un endpoint di un socket e cioè l'indirizzo IP ed il numero di porta. I suoi due metodi getter principali consentono di ottenere i due dati di cui sopra.
In particolare, il metodo getAddress restituisce un oggetto di classe InetAddress che contiene l'indirizzo IP del socket-address.
Una ultima nota: la classe InetAddress rappresenta un indirizzo del protocollo IP (=Internet Protocol) ma non è istanziabile poichè vi sono due standard IP:
Inet4Address Inet6Address I files sorgente che implementano la ricerca dei giocatori in rete sono contenuti nel package mastermind.net.udp:
| nome file | descrizione |
|---|---|
| ListInterfaces.java | applicazione CLI di test delle interfacce |
| MCInterface.java | una interfaccia di rete attiva ed abilitata al multicast |
| MulticastWorker.java | la classe base astratta del worker-thread di invio/ricezione |
| MulticastReceiver.java | la classe specializzata nella ricezione dei pacchetti UDP |
| MulticastSender.java | la classe specializzata nel invio dei pacchetti UDP |
| UDPListener.java | il listener degli eventi UDP |
| PropertyPanel09.java | il nuovo pannello delle proprietà della app |
| ConnectionPanel09.java | il nuovo pannello delle connessione remota |
| EmptyListException.java | eccezione per lista interfacce vuota |
| MalformedItemException.java | eccezione per formato non valido |
La ricerca dei giocatori in rete da parte di Mastermind viene affidata alle classi elencate più sopra le quali possono essere configurate attraverso le seguenti proprietà della applicazione:
mcInterval: intervallo di tempo, in millisecondi, tra un invio e l'altro da parte del sender dei pacchetti UDP; il default è 1000 ms; se questa proprietà ha un valore negativo l'invio e la ricezione dei pacchetti UDP sono disabilitati mcTimeour: intervallo di tempo, in millisecondi per il receiver scaduto il quale la primitiva di I/O receive deve rientrare anche se nessun pacchetto UDP è stato ricevuto; il default è 500 ms mcInterface: può contenere un elenco di interfacce di rete specifiche, separate da whitespaces su cui inviare / da cui ricevere i pacchetti UDP multicast; se il valore di questa proprietà è null i pacchetti UDP saranno inviati 7 ricevuti da tutte le interfacce di rete attive ed abilitate al multicast; il valore di default è null mcAddress: rappresenta l'indirizzo IP multicast del gruppo; il valore di default è "230.18.8.0" mcPort: la porta UDP del datagram-socket di invio / ricezione dei pacchetti UDP per il multicasting in Mastermind; il valore di default è "3963"Il file sorgente ListInterfaces.java è una piccola applicazione CLI che potete usare per sperimentare un pò le interfacce di rete presenti sul vostro computer. Potrebbero essercene molte di più di quelle fisiche: nel mio computer ci sono tre interfacce di rete fisiche: il loopback, la scheda Ethernet e la scheda Wireless.
Ma l'elenco ottenuto con la applicazione è molto più lungo:
>java -ea mastermind.net.udp.ListInterfaces -n TEST INTERFACES Interface: ethernet_0 Interface: ethernet_1 Interface: ethernet_2 Interface: ethernet_3 ... omissis ... Interface: ppp_32768 Interface: loopback_0 Interface: wireless_0 Interface: wireless_1 ... omissis ...
Quindi Mastermind invierà pacchetti UDP su tutte quelle interfacce anche se non sono davvero esistenti? Certo che no. Possiamo ottenere informazioni dettagliate sulla interfaccia di rete specificandone il nome come argomento sulla command-line. Per esempio.
>java -ea mastermind.net.udp.ListInterfaces ethernet_2 Information on interface: ethernet_2 Display name: Intel(R) Ethernet Connection (2) I219-LM-WFP 802.3 MAC Layer LightWeight Filter-0000 isLoopback? false isUp? false isVirtual? false supportsMulticast? true Interface Addresses:
Notiamo subito che la interfaccia non è attiva (vedi IsUp()=false) e che ad essa non è associato alcun indirizzo IP. Ovviamente, Mastermind invierà i pacchetti UDP solo sulle interfaccie attive e, fra queste, solo quelle che supportano il multicasting. Per una interfaccia di rete attiva otteniamo molte più informazioni:
>java -ea mastermind.net.udp.ListInterfaces wireless_32768 Information on interface: wireless_32768 Display name: Realtek RTL8188ETV Wireless LAN 802.11n USB 2.0 Network Adapter isLoopback? false isUp? true isVirtual? false supportsMulticast? true Interface Addresses: Address: /fe80:0:0:0:d879:5dc6:dbeb:dadb%wireless_32768 Raw address length: 16 Broadcast addr: N/A Network prefix length: 64 Address: /10.117.242.114 Raw address length: 4 Broadcast addr: /10.117.242.255 Network prefix length: 24
Notiamo anche che alla interfaccia wireless_32768 sono associati due indirizzi IP
Questa classe viene implementata dal sorgente MCInterface.java e rappresenta una singola interfaccia di rete attiva ed abilitata al multicasting. Il worker-thread che invia / riceve i pacchetti UDP mantiene una lista di oggetti di classe MCInterface poichè sia il sender che il receiver iterano sulla lista per inviare ad ogni interfaccia di rete il pacchetto UDP. Oggetti di questa classe vengono istanziati passando un solo argomento al costruttore: l'interfaccia di rete, un oggetto di classe NetworkInterface.
I metodi più importanti di questa classe sono:
isUpEnabled; questo metodo ritorna TRUE se la interfaccia di rete specifica createSocket: il metodo crea il DatagramSocket sul quale saranno inviati / ricevuti i pacchetti UDP multicast close: chiude il socket, quando le operazioni saranno concluse receive: riceve un DatagramPacket dal socket creato con il metodo precedente send: invia un DatagramPacket sul socket creato con il metodo precedente getErrorCount: ritorna il numero di errori di I/O occorsi sul socket, vedi Errori di invio/ricezione per ulteriori informazioniLa classe MulticastWorker è la classe base astratta per il sender ed il receiver; essa si occupa di creare e mantenere la lista delle interfacce di rete attive, rappresentate dalla classe MCInterface. Da questa classe derivano le due classi specializzate:
MulticastSender che si occupa dell'invio dei pacchetti UDP MulticastReceiver che si occupa della ricezione dei pacchetti UDPLa classe MulticastWorker possiede due costruttori pubblici. Il primo di essi accetta tre argomenti di classe:
Il secondo costruttore pubblico di MulticastWorker accetta gli stessi primi due argomenti ma il terzo non è una stringa di nomi di interfacce ma una lista di oggetti MCInterface che rappresenta comunque la lista delle interfacce di rete da usare. Questo costruttore non viene usato nella applicazione Mastermind ma nei test che vengono descritti nella documentazione javadoc del package specifico, vedi il package mastermind.test.udp.
La lista di interfacce di rete da usare nel worker-thread viene creata nel costruttore: questo perchè le interfacce di rete presenti nel computer non cambia nel tempo quindi può essere creata una sola volta nel corso della applicazione.
La classe MulticastWorker è una classe base astratta. I due metodi astratti sono:
start che fà partire il worker-thread di invio o ricezione dei pacchetti UDP; il metodo è specializzato per le due derivate: il sender ed il receiver stop che ferma il worker-thread ma non chiude i sockets: le operazioni possono essere riprese richiamando nuovamente start I seguenti metodi sono responsabili di gestire la lista delle interfacce di rete:
createInterfacesFromIfaces: crea la lista delle interfacce di rete ed apre i socket datagram per ognuna di esse; il metodo verifica anche che le interfacce passate come argomento siano attive ed abilitate al multicast createInterfacesFromStrings crea la lista delle interfacce di rete ed apre i socket datagram per ognuna di esse; il metodo verifica anche che le interfacce passate come argomento siano attive ed abilitate al multicast getInterfaceCount: ritorna il numero di interfacce di rete presenti nella lista getInterfaces: ritorna la lista di interfacce di rete attive ed abilitate close: ferma il worker-thread e chiude i sockets datagramCome tutte le operazioni di Input/Output, anche l'invio dei pacchetti UDP può non andare a buon fine a causa di errori di trasmissione. I prototipi dei metodi dedicati a queste operazioni parlano chiaro:
A differenza del protocollo TCP, che è affidabile, UDP è inaffidabile e gli errori di trasmissione sono molto più frequenti. Ma un errore su una inetrfaccia di rete non significa nulla ed è per questo che il sender invia molte copie del messaggio ad intervalli regolari; anche se può capitare un errore nell'invio di uno di essi, le altre copie raggiungeranno la destinazione.
Ovviamente, non è pratico continuare ad inviare pacchetti su una interfaccia che è sempre in errore: la strategia adottata dal sender e dal receiver di Mastermind è quella di mantenere un contatore degli errori di I/O nella classe MCInterface: dopo cinque errori consecutivi, la interfaccia sarà rimossa dalla lista delle interfacce di rete da usare nel worker-thread.
Se tutte le interfacce di rete della lista vengono rimosse, la lista rimarrà vuota: in questo scenario, non ha più senso tenere in vita il worker-thread il quale ritorna dal metodo eseguito in background e termina.
Le operazioni di invio e ricezione dei pacchetti UDP vengono eseguite nel metodo doInBackground del worker-thread in modo da non bloccare la GUI. Abbiamo già avuto a che fare con i threads, ricordate? In due occasioni:
Benchè trattasi di worker-threads in entrambi i casi, la organizzazione del metodo in background è profondamente diversa tra i due: nel primo caso il thread viene eseguito per completare una singola operazione, l'invio della guess, e poi termina. Nel secondo caso, invece, il thread esegue continuamente la operazione ad esso assegnata e non termina mai, salvo gli eventi eccezionali come per esempio la perdita della connessione: inutile tenere vivo un thread che trasmette messaggi su un canale di comunicazione quando il canale si è interrotto.
Abbiamo quindi imparato a pubblicare i risultati intermedi nel caso delle code dei messaggi: ogni volta che un messaggio viene inviato e/o ricevuto con successo, si richiama il metodo publish fornendo come dato da pubblicare un oggetto Message che rappresenta il messaggio inviato o ricevuto.
Ma questo caso, quello dei pacchetti UDP, è diverso ancora: come detto in precedenza, una eccezione di I/O su una interfaccia di rete non pregiudica affatto il completamento delle operazioni: a parte il fatto che dobbiamo tentare nuovamente, almeno cinque volte, la operazione stessa, ci sono altre interfacce di rete nella lista su cui inviare o da cui ricevere i messaggi.
Pertanto, non possiamo sollevare la eccezione nel metodo doInBackground perchè questo evento fermerebbe il thread e sarebbe poi richiamato il suo metodo done: una volta richiamato done, il thread non può più essere ri-eseguito, nemmeno richiamandone il metodo execute. Da notare che questo è un limite della classe SwingWorker, non dei threads in generale implementati dalla classe java.lang.Thread.
Anche se non possiamo sollevare la eccezione nel meodo in background, dobbiamo pure segnalare l'evento al listener degli eventi in modo da darne notizia allo user o comunque alla applicazione e non solo: ci sono altri due eventi degni di nota che dobbiamo segnalare (ma senza sollevare eccezioni): il fatto che una interfaccia di rete è stata rimossa dalla lista per troppi errori di I/O ed il fatto che la lista delle interfacce di rete è vuota perchè tutte sono state rimosse per troppi errori.
Non sembra particolarmene difficile da scrivere, vero?
Anche se dal punto di vista logico il codice sembrerebbe corretto, esso non funziona. La causa del mancato funzionamento non è nella logica di programmazione ma nelle costrizioni della libreria Java Swing che non permette di accedere ai componenti GUI da un thread diverso dal EDT.
Pertanto, l'aver richiamato il metodo removedForErrors del listener non ci aiuta molto se non possiamo dare alcun feedback visivo allo user sul fatto che una interfaccia è stata rimossa dalla lista delle interfacce. Come risolviamo la faccenda? Ma è ovvio, pubblicando un risultato intermedio! Ricordate? I metodi doInBackground e publish vengono eseguiti nel thread secondario mentre i metodi done e process vengono eseguiti nel EDT.
Abbiamo un altro problema. I dati da pubblicare come risultati intermedi cominciano ad essere troppi e, manco a dirlo, la classe SwingWorker ne accetta uno solo, il tipo "V":
ma a noi ne servono molti di più. La soluzione a questa empasse è quella di definire un nuovo tipo di dato che raggruppa queste inofromazioni e che sarà il tipo di dato da pubblicare come risultato intermedio. Potrebbe essere una classe ma, considerato che sarà immutabile e che i suoi membri dati saranno final, un record è il costrutto ideale:
Gli argomenti al costruttore hanno significati specifici in linea con le informazioni che devono fornire:
iface la interfaccia di rete che è stata elaborata nel metodo in background progr il progressivo del messaggio inviato / ricevuto: se questo valore è negativo significa che l'interfaccia iface è stata rimossa dalla lista delle interfacce di rete; in questo caso, tutti gli altri argomenti saranno null payload il messaggio inviato dal sender; questo argomento sarà null per il receiver o in caso di eccezioni packet il pacchetto datagram ricevuto dal receiver; questo argomento sarà null per il sender o in caso di eccezioni except la eccezione sollevata dalla interfaccia di rete iface nelle operazioni di I/O, se la operazione si è conclusa con successo, questo argomento sarà nullQuesta classe, derivata da MulticastWorker, sovrascrive i due metodi astratti della classe base: start e stop. Il primo fà partire l'invio dei pacchetti UDP mentre il secondo, come facilmente intuibile, lo ferma. Il metodo start fà partire un thread secondario, una classe anonima derivata da SwingWorker: il thread secondario è infinito nel senso che il suo metodo doInBavkground non termina mai salvo la cancellazione del thread.
Lavorando in background, il MulticastSender itera su tutte le interfacce di rete attive ed abilitate al multicast richiamndone il metodo send al quale viene passato il messaggio da inserire nel pacchetto UDP:
L'invio avviene ad intervalli regolari il cui lasso di tempo è specificato dal parametro mcInterval.
Il multicast-receiver è molto simile al sender: sovrascrive i due metodi astratti start e stop il primo dei quali fà partire la ricezione dei pacchetti UDP mentre il secondo la ferma. Anche il metodo doInBackground è praticamente lo stesso: in un ciclo infinito (salvo cancellazione del thread) il metodo itera su tutte le interfacce attive ed abilitate al multicasting e per ognuna di esse ne richiama il metodo receive che ritorna:
DatagramPacket se un pacchetto UDP è stato letto dal socket Anche nel receiver, i risultati delle operazioni vengono pubblicati come risultati intermedi di qualsiasi natura essi siano.
Quali classi di Mastermind sono interessate agli eventi che scaturiscono dalle classi di questo package dedicato al protocollo UDP? Dobbiamo distinguere due casi: il lato server della applicazione ed il lato client
Dal lato del server di gioco, i pacchetti UDP devono essere inviati quando il server è pronto ad accettare connessioni e cioè quando esso è in ascolto sulla porta dedicata al servizio. Quindi la classe inetressata agli eventi è la ConnectionPanel che visualizza il pannello dello stato di avanzamento della connessione che si trova nella fase in cui il server è in attesa di richieste di connessione. Per quanto riguarda gli eventi ai quali il server è interessato possiamo escludere l'invio dei singoli pacchetti UDP: dovrebbe invece essere dato un feedback all'utente se il multicasting è stato attivato oppure no:
Connecting ... port: 18862 (server mode) Multicast sender started
La classe ConnectionPanel deve essere modificata per implementare la nuova feature (=funzionalità). In linea con la strategia di versioning dei sorgenti, deriviamo una nuova classe, che chiamiamo Connectionpanel09 ed implementiamo solo i metodi che dovrebbero essere modificati: tutti gli altri metodi sarano ereditati dalla classe base.
La nuova classe ConnectionPanel09 farà parte del package mastermind.net.udp in linea con la politica di packaging che stabilisce che una classe deve essere inserita nel package dove vi trovano le dipendenze da altre classi e non nel package che ne determina l'uso.
Dal lato client la classe interessata agli eventi UDP è la PropertyPanel ossia il pannello delle proprietà in cui l'user deve inserire l'indirizzo IP del server di gioco. La ricezione di un pacchetto UDP contenente le informazioni sul server di gioco deve essere notificata al pannello delle proprietà in modo che esso aggiorni la combo-box della lista dei server disponibili e dalla quale l'user può selezionare un server a cui connettersi.
Come per il pannello della connessione scriveremo una nuova classe, che chiameremo PropertyPanel09, derivata da PropertyPanel ed implementeremo solo i metodi che dovrebbero essere modificati e quelli nuovi. Tra i metodi nuovi troviamo, ovviamente, quelli che implementano la interfaccia UDPListener che è il listener degli eventi UDP e che elaborano i pacchetti UDP contenenti la stringa di descrizione dei server di gioco Mastermind.
Poichè ci sono almeno due classi interessate agli eventi UDP, la strategia migliore è quella di definire una interfaccia: qualsiasi classe che la implementi può essere passata come argomento listener al costruttore di MulticastWorker. Questi sono i metodi definiti nella inetrfaccia UDPListener:
udpErrorReceive: errore nella ricezione del messaggi UDP. Gli argomenti al metodo sono la inetrfaccia di rete che ha causato l'errore e l'errore stesso udpErrorSend: errore nell'invio di messaggi UDP. Gli argomenti al metodo sono la inetrfaccia di rete che ha causato l'errore e l'errore stesso udpMessageReceived: messaggio UDP ricevuto con successo. Gli argomenti al metodo sono: la interfaccia di rete che ha ricevuto il pacchetto UDP, l'indirizzo del mittente (il server di gioco) ed il messaggio stesso, il cosidetto payload nei pacchetti UDP udpMessageSent: messaggio UDP inviato con successo. Gli argomenti al metodo sono: il progressivo del messaggio e la interfaccia di rete che lo ha inviato udpReceiverStarted: Worker-thread di ricezione UDP eseguito; non ci sono argomenti udpReceiverStopped: Worker-thread di ricezione UDP cancellato; non ci sono argometi udpSenderStarted: Worker-thread di invio UDP eseguito; non ci sono argomenti udpSenderStopped: Worker-thread di invio UDP cancellato; non ci sono argomentiIn questa sezione descrivo alcune particolarità del protocollo IP e della libreria java Swing. La sezione non insegna nulla sul linguaggio Java quindi il lettore può anche saltare alla prossima sezione senza pregiudicare la comprensione di questo tutorial. Tuttavia, se egli ha un pò di tempo da sprecare, consigli di continuare a leggere.
Chi ha osservato da vicino la interfaccia UDPListener sarà rimasto un pò sorpreso nella definizione del metodo:
nel quale viene passato come secondo argomento l'indirizzo IP del server di gioco che ha inviato il pacchetto UDP. in effetti, l'indirizzo IP del server è contenuto nel payload che ha il formato:
nickname@hostname/ip_address:port
dove:
nickname è il nome del giocatore remoto, quello che funge da server hostname è il nome della macchina server; a questo proposito và detto che questa informazione è disponibile solo se la libreria Java ha risolto un nome macchina in un indirizzo IP, nessun reverse lookup (=visura rovesciata) viene eseguito da Java per risolvere un indirizzo IP in un nome host ip_address questo è l'indirizzo IP che il sender ottiene dalla interfaccia di rete usata per inviare il pacchetto UDP; in questa versione di Mastermind vengono estratti solo gli indirizzi IPV4. port è la porta TCP/IP su cui il server è in ascolto per le richieste di connessioneQuindi la domanda che vi starete ponendo sarà: perchè l'indirizzo IP del server viene passato come argomento al metodo considerato che è contenuto nel payload? Ebbene, l'indirizzo IP contenuto nel payload viene inviato dalla macchina remota che però è sconosciuta, in gergo si dice untrusted (=non ci si può fidare).
Chi ci assicura che quell'indirizzo è quello reale? Nessuno. Invece, l'indirizzo IP passato come argomento al metodo è stato ottenuto dal pacchetto UDP ricevuto e quello non si può falsificare se non usando strumenti piuttosto sofisticati.
E' un pò come se in una chiamata con uno sconosciuto gli chiediamo il suo numero di telefono ed egli ce lo comunica a voce durante la chiamata: sarà quello giusto? Ma se invece lo otteniamo dal display del nostro telefono come "numero chiamante" allora possiamo fidarci un pò di più.
Nella sezione Pubblicare i risultati avete imparato che non è possibile richiamare i metodi che interagiscono con i componenti GUI Swing da un thread diverso dal EDT. In altre parole, all'interno del metodo doInBackground non posso richiamare, direttamente o indirettamente, il metodo MasterMind.setStatusbarMessage (per fare un esempio) poichè questo metodo aggiorna il componente Swing JLabel che fà parte della statusbar di Mastermind.
So che per voi è una delusione e che siete quasi tentati di passare ad un altro linguaggio/ambiente: se Java Swing è così limitato, perchè dovrei imparare ad usarlo? Ebbene, quello descritto non è un limite di Java Swing ma del sistema operativo stesso: sia Windows™ che gli ambienti grafici basati su Linux hanno questo limite quindi passare ad un altro linguaggio/ambiente non risolve il problema.
Non sò se Mac abbia lo stesso limite, non ho mai programmato nè mai posseduto un computer della mela; se qualcuno vorrà modificare questa mia opera ed è a conoscenza di questa informazione è il benvenuto.
A dispetto della sua inaffidabilità e propensione agli errori di trasmissione, il protocollo UDP è usato moltissimo nelle applicazioni reali specialmente in quelle real-time. La controparte di UDP è il TCP (Trasmission Control protocol), un protocollo affidabile che garantisce il flusso di dati: ciò che viene trasmesso dal mittente raggiunge il destinatario nello stesso ordine in cui è stato trasmesso, salvo errori di connessione, ovviamente.
Ma entrambi si basano su IP, un protocllo inaffidabile, quindi come viene realizzata la affidabilità del TCP se si basa su IP? Ebbene, le regole per raggiungere la affidabilità del TCP sono, sostanzialmente, le seguenti:
Come potete immaginare, queste operazioni generano una certa latenza che dipende in gran parte dalla qualità della rete ma non solo, molto dipende anche dal caso. Prendiamo ad esempio una telecamera che invia il video di ciò che riprende. Ogni frame del video può essere considerato un messaggio da inviare al computer di sorveglianza; il messaggio è composto da parecchi pacchetti IP poichè un frame a colori di 10 o 20 megapixels pesa parecchio, nonostante la compressione.
Ora supponiamo di usare TCP per avere una connessione affidabile: la telecamera invia un frame composto da qualche centinaio di pacchetti ma uno di essi viene perso per strada costringendo il mittente a ritrasmetterlo. Poichè il TCP aspetta fino all'ultimo pacchetto prima di inoltrare il messaggio (il frame) al computer di sorveglianza otteniamo una certa latenza che potrebbe essere critica.
Usando il protocollo UDP, invece, il computer di sorveglianza può elaborare i pacchetti in tempo reale, nel momento stesso in cui li riceve. Certo, nel frame visualizzato sul computer di sorveglianza mancherà qualche pixel, dovuto a qualche pacchetto perso per strada, ma è molto meglio perdere qualche pixel piuttosto che dover attendere l'intero frame, specialmente se il fattore tempo è un aspetto critico della applicazione.
La documentazione completa del package descritto in questo capitolo può essere visualizzata clikkando il seguente link che riporta alla documentazione Javadoc del progetto: The MasterMind Project Version 0.9
Argomento precedente - Argomento successivo - Indice Generale