• Non ci sono risultati.

WebSocket Symphony Orchestra

N/A
N/A
Protected

Academic year: 2021

Condividi "WebSocket Symphony Orchestra"

Copied!
55
0
0

Testo completo

(1)

Correlatore

Giancarlo Corti

Committente

Nicola Rizzo

Corso di laurea

Ingegneria informatica

Modulo

M00002 Progetto di diploma

Anno

2018

Data

(2)
(3)

Indice

Abstract 1 Progetto Assegnato 3 Introduzione 5 1 Stato dell’arte 7 2 Struttura generale 9 2.1 Producer . . . 10 2.2 Dispatcher . . . 10 2.3 Consumer . . . 10 3 Metodologia di sviluppo 13 4 Producer 15 4.1 Connessione al dispatcher . . . 15 4.1.1 Gestione touch . . . 17

4.2 Produzione singola nota . . . 17

4.2.1 Stato della connessione con il server . . . 18

4.3 Selezione nota musicale e strumento . . . 19

4.3.1 Stabilire connessione . . . 19

4.3.2 Selezionare nota . . . 19

4.3.3 Selezionare strumento . . . 20

4.3.4 Controllo lunghezza nota con touch . . . 20

4.3.5 Sensore di prossimità . . . 20

4.4 Produzione di una melodia . . . 22

4.4.1 IP server dinamico . . . 23

4.4.2 Nuovo protocollo di comunicazione . . . 24

4.5 Polifonia 1 . . . 25

4.5.1 Produzione melodia nota per nota . . . 25

(4)

ii INDICE

4.5.3 Costruzione di un ID univoco . . . 26

4.5.4 Feedback dal consumer . . . 27

4.6 Polifonia 2 . . . 27

5 Dispatcher 29 5.1 Connessione con producer(s) . . . 29

5.1.1 DispatcherLogger . . . 30

5.1.2 Classe WebSocketServer . . . 32

5.2 Comunicazione verso il consumer . . . 33

5.2.1 Tabella dei producer collegati . . . 33

5.3 Comunicazione nota, strumento o comando . . . 34

5.4 Comunicazione melodia . . . 35

5.4.1 DispatcherEmitter . . . 35

5.4.2 ConsumerEmitter . . . 36

5.4.3 Comunicazione indirizzo IP al producer . . . 36

5.5 Comunicazione polifonia . . . 37

5.5.1 Comunicazione preloading . . . 37

5.6 Deploy dispatcher . . . 37

6 Consumer 39 6.1 Introduzione a Tone.js . . . 39

6.2 Riproduzione di una nota . . . 40

6.2.1 Riproduzione nota nel consumer . . . 41

6.2.2 Visualizzazione stato della connessione con il dispatcher . . . 41

6.2.3 Visualizzazione operazioni del consumer . . . 42

6.3 Riproduzione nota con strumento selezionato . . . 42

6.3.1 Selezione nota . . . 43

6.3.2 Selezione strumento . . . 43

6.4 Riproduzione melodia . . . 44

6.4.1 Riproduzione singola nota . . . 45

6.4.2 Mappatura note e strumenti . . . 45

6.5 Riproduzione polifonia . . . 45

6.6 Deploy consumer . . . 46

(5)

Elenco delle figure

2.1 Architettura del progetto . . . 9

3.1 Processo di sviluppo software SCRUM . . . 13

4.1 Layout producer prodotto dall’IDE Android Studio . . . 15

4.2 Panoramica SocketClient . . . 16

4.3 Codice che gestisce i tentativi di connessione . . . 18

4.4 Codice che gestisce gli eventi del sensore di prossimità . . . 21

4.5 Spartito Fra Martino . . . 22

4.6 Estratto dello spartito dell’Aria sulla quarta corda . . . 27

5.1 Prima implementazione della classe ListenerServer . . . 30

5.2 Prima implementazione del DispatcherLogger . . . 31

5.3 Costruzione del SocketIOServer . . . 32

5.4 Metodo in WebSocketServer che comunica con il dispatcherLogger . . . 32

5.5 Tabella che rappresenta i producer connessi . . . 33

6.1 Meotodo che riproduce la nota . . . 41

6.2 Logica del bottone che mostra lo stato della connessione tra consumer e dispatcher . . . 41

6.3 Metodo che riproduce la nota . . . 42

(6)
(7)

Abstract

Il progetto presenta un architettura divisa in tre componenti principali, ognuno dei quali ha un compito preciso all’interno del ciclo di vita dell’applicazione. L’utente utilizzando un’ap-plicazione android per smartphone è in grado di produrre delle note musicali utilizzando il touchscreen del device oppure il sensore di prossimità. Inoltre è in grado di produrre due melodie: Fra Martino e l’Aria sulla quarta corda di Bach. Le note vengono trasmesse ad un server centrale con il protocollo socket e dal server sono trasmesse ad una pagina web tra-mite protocollo websocket. Nella pagina web le note sono riprodotte in tempo reale rispetto alle azioni che l’utente compie sul suo smartphone. Il sistema supporta la connessione di più dispositivi che producono note, in questo mdo si può ottenere l’effetto di un orchestra dove gli strumenti sono dei device collegati alla rete locale.

(8)
(9)

Progetto Assegnato

Descrizione

Il progetto prevede la scrittura di un server che, ricevuti in ingresso alcuni parametri distintivi, permetta ai client connessi di richiedere l’esecuzione di note musicali specifiche.

I client potranno essere dispositivi mobili, tablet o altri oggetti in grado di connettersi al server usando il protocollo websocket e potranno usare tutti i sensori disponibili (prossimità, tocco, accelerometri...) per decidere quando effettuare le richieste.

Le note verranno eseguite, almeno in un primo momento, utilizzando le tecnologie web disponibili nei browser di ultima generazione (Web Audio)

Compiti

Lo studente dovrà occuparsi della scrittura del server, dell’esecutore vero e proprio di suoni e di almeno un client che faccia da strumento musicale.

La comunicazione sarà bidirezionale in modo da permettere l’esecuzione automatica in mo-dalità "rullo di piano", con dei feedback anche sul client (per esempio in caso di tastiera virtuale, i tasti dovranno apparire premuti nel momento dell’esecuzione)

Obbiettivi

Ottenere il sistema descritto in modo tale che la latenza sia tanto bassa in modo da permet-tere l’esecuzione di almeno un motivo elementare.

Il server dovrà inoltre permettere l’esecuzione contemporanea di più note e quindi di polifo-nia

(10)
(11)

Introduzione

Nel primo capitolo si fa un introduzione sulle tecnologie presenti al giorno d’oggi che pos-sono essere utilizzate all’interno del progetto. Si analizzano le applicazioni che riproducono canzoni e musica più in generale, si passa attraverso applicazioni web che permettono di comporre melodie nota per nota fino ad arrivare a parlare degli strumenti di più basso livello che permettono di riprodurre suoni. Infine viene definito il concetto di realtime che il progetto deve rispettare e vengono analizzate, anche in questo caso, le tecnologie già presenti e che probabilmente verranno utilizzate.

Nel secondo capitolo viene presentata la struttura generale del progetto, esso si svilup-perà diviso in tre componenti principali: un producer che, usato dall’utente, ha il compito di produrre note musicali e melodie. Un dispatcher che rappresenta l’elemento centrale dell’ar-chitettura, quello che mette in comunicazione producer e consumer. E infine un consumer che il componente che riproduce i suoni.

Nel terzo capitolo viene spiegata la metodologia scrum come metodologia per sviluppare il progetto. Si presentano le caratteristiche principali e i punti forte che fanno preferire questa ad altre metodologie.

Nel quarto capitolo si parla più nello specifico del componente producer. Si analizzano tutte le funzionalità implementate durante lo sviluppo del software in ordine cronologico e le classi, i metodi e i meccanismi creati per raggiungere tali funzionalità.

Nel quinto capitolo viene trattato lo sviluppo del componente dispatcher. Come nel caso del producer vengono analizzate le funzionalità raggiunte durante lo sviluppo software in ordine cronologico ponendo una particolare attenzione alle classi, ai metodi e ai meccanismi che sono stati adoperati per raggiungere tali funzionalità.

Nel sesto capitolo l’attenzione si sposta sull’ultimo componente rimasto, il consumer. Ana-logamente ai componenti precedenti si sono analizzate le funzionalità che sono state imple-mentate e le funzioni più importanti. Si spiega anche la struttura messa in piedi per gestire le dipendenze all’interno del componente.

Nell’ultimo capitolo vengono esposti i risultati ottenuti e vengono trattate le conclusioni riguardo al progetto.

(12)
(13)

Capitolo 1

Stato dell’arte

Al giorno d’oggi, nel web si trovano molte applicazioni che permettono di produrre e riprodur-re musica in generale, nel particolariprodur-re queste danno la possibilità all’utente sia di riprodurriprodur-re canzoni o melodie intere, sia di produrre una singola nota, sia di comporre una propria me-lodia nota per nota. Le maggiori applicazioni che danno la possibilità di ascoltare intere melodie (Spotify, Google Play Music e Youtube in primis) prima di riprodurle devono scari-care megabyte di dati. Approfondendo, invece, il tema di produrre note o melodie nel web si trovano diversi strumenti che permettono di comporre melodie e che mettono a disposizione strumenti ed effetti per ogni genere e gusto (Caustic 3 e GarageBand per citare i più famosi). Queste sì permettono di produrre musica ma ad alto livello, e si tratta di applicazioni che producono e riproducono note solo al proprio interno: cioè non usano un riproduttore ester-no e ester-non hanester-no bisogester-no del web per svolgere il proprio compito. Continuando la ricerca di strumenti che permettano di produrre note musicali si trovano le cosiddette "Web Audio API"; queste sono state concepite con lo scopo di colmare il vuoto che solo parzialmente ha riempito il tag HTML <audio> grazie al quale si ha la possibilità di riprodurre un audio in streaming o in locale. Le Web Audio API sono relativamente giovani nel web: la prima rac-comandazione a riguardo, scritta dal W3C (World Wide Web Consortium), è datata 20111 ma è solo dal 2015 che tutti i browser le supportano2. Nello specifico le Web Audio API sono delle Application Programming Interface JavaScript di basso livello per l’elaborazione e la sintetizzazione di file audio in applicazioni web. Queste API permettono di manipolare file audio esistenti o qualsiasi sorgente sonora e di produrre un suono da zero. Per fare ciò utilizzano degli oggetti detti "AudioNode" che sono utilizzati principalmente per tre scopi: produrre, filtrare o riprodurre un suono; questi AudioNode possono essere combinati in qual-siasi modo per ottenere il risultato desiderato. Nel caso del progetto "Websocket Symphony Orchestra" non è stato necessario utilizzare delle API di così basso livello ma si è optato per un framework come Tone.js3 che fornisce funzionalità di scheduling avanzato,

sintetiz-1

https://www.w3.org/TR/webaudio/

2https://caniuse.com/#search ¯web%20audio 3

(14)

8 Stato dell’arte

zatori e effetti, e astrazioni musicali intuitive basate sulle Web Audio API. Questa scelta è stata fatta anche perché la sua architettura è pensata per essere familiare sia ai musicisti che ai programmatori audio. Tone.js offre funzioni DAW (Digital Audio Workstation) comu-ni come un trasporto globale per lo scheduling di eventi, sintetizzatori e effetti predeficomu-niti; per i programmatori offre invece blocchi ad alte prestazioni e bassa latenza, e moduli DSP per la costruzione di propri sintetizzatori, effetti o segnali di controllo complessi. Oltre alla produzione di suoni il progetto prevede che un sistema distribuito lavori in realtime: cioè che un componente qualsiasi dell’applicazione risponda agli eventi creati dagli altri appena l’evento è scatenato. In questo caso quando l’utente produce una nota con un device la nota è riprodotta immediatamente dal riproduttore collegato. Qualcosa di simile è stato fatto in alcuni giochi per console come "Guitar Hero" in cui il giocatore utilizzava un joystick a forma di chitarra e il suo scopo era schiacciare il pulsante giusto nel momento giusto per suonare correttamente la canzone. La limitazione con questo strumento è lo scarso numero di note (massimo 5) e strumenti (è stata prodotta un’altra versione che permette di usare una batteria) a disposizione. Quindi per ottenere l’effetto di realtime nel nostro sistema si ha bisogno di mezzi di comunicazione che assicurino bassa latenza e alte prestazioni. In que-sta direzione si muove il protocollo di comunicazione Websocket, protocollo que-standardizzato dall’IETF come RFC64554 e le sua API sono standardizzate dal consorzio W3C. Questa tecnologia web fornisce canali di comunicazione full-duplex attraverso un singola connes-sione con protocollo TCP (Trasmission Control Protocol): ciò significa che posso stabilire tra due macchine una connessione tramite un singolo canale di comunicazione e che attraver-so quest’ultimo si può comunicare in entrambe le direzioni con la sicurezza che i dati inviati siano consegnati integralmente grazie al protocollo TCP. All’interno di questo contesto ho scelto "Websocket Symphony Orchestra" come lavoro di tesi perché si tratta di un progetto che parla di musica, argomento del quale sono molto appassionato e che vivo quotidiana-mente come semplice ascoltatore, e comunicazione realtime nel web, un argomento che associato alla musica mi ha affascinato per le potenzialità che potrebbero essere scoperte.

4

(15)

Capitolo 2

Struttura generale

Il progetto sin dall’inizio è stato suddiviso in tre parti indipendenti l’una dall’altra: così facen-do la progettazione è avanzata parallelamente su tutte e tre le parti. La struttura prende spunto dal Producer-Consumer pattern. Quest’ultimo descrive un’architettura nella quale troviamo a monte un "producer", il quale ha il compito di produrre dati, nel mezzo un buffer, nel quale vengono depositati i dati in attesa di essere processati, e a valle un "consumer", il quale deve processare i dati contenuti nel buffer. Come è facile intuire in questa architettura posso avere un numero indefinito di producer, con l’unica condizione di non produrre dati se il buffer è pieno, e un numero indefinito di consumer, con la condizione che non richiedano dati se il buffer è vuoto. Da questa struttura base si è costruita l’architettura del progetto: come producer si è creata un’applicazione android che fosse in grado di collegarsi ad un server e comunicare i dati che è capace di produrre; come consumer si è creata una pagina web in grado di processare i dati in arrivo. Al posto di avere un buffer che immagazzina dati troviamo un server che ha il solo compito di trasmettere i dati in arrivo dal/i producer verso il/i consumer collegati.

(16)

10 Struttura generale

2.1

Producer

Nello sviluppo del progetto è stato necessario sviluppare un’applicazione android che fun-gesse da producer. Ma andando con ordine elenchiamo le funzionalità che ha questa applicazione:

• stabilire un collegamento con il server/dispatcher

• produrre dei dati ricavandoli dai sensori presenti (touch, prossimità, accelerometro, ...)

• produrre un motivetto elementare come un "rullo di piano"

• inviare i dati secondo un protocollo leggibile al consumer

Ottenute queste funzionalità all’interno della architettura completa non c’è nessuna differen-za tra un’applicazione android o ios o di qualsiasi altro sistema operativo per device mobili. Si parla di device mobili e non solo di smartphone perché anche, per esempio, i tablet ri-spondono ai requisiti richiesti. Ho scelto di sviluppare su android per un semplice motivo, perché possiedo uno smartphone android e ho potuto utilizzarlo sin da subito, e sempre, per testare il funzionamento dell’applicazione descritta.

2.2

Dispatcher

Il dispatcher è l’elemento centrale in questa architettura e ad esso sono richieste queste funzionalità:

• supportare la connessione di più producer

• inoltrare i dati verso il/i consumer

• avere una bassa latenza nell’inoltrare i dati

• registrare le operazioni che svolge

Anche se mette in collegamento producer e consumer è indipendente dalle altre compo-nenti: i messaggi che arrivano dal producer sono mostrati all’utente nella pagina di log delle operazioni e inoltrati al consumer che esegue le operazioni scritte nel messaggio. Il dispat-cher in realtà non si interessa dei dati che riceve e inoltra, ma si interessa di garantire le massime performance nel sistema.

2.3

Consumer

Il consumer è la parte finale dell’architettura ed è quella che ci fa apprezzare il lavoro svolto dalle altre componenti; da esso ci si aspetta la seguente funzionalità:

(17)
(18)
(19)

Capitolo 3

Metodologia di sviluppo

Figura 3.1: Processo di sviluppo software SCRUM

Come metodologia di lavoro è stato usato il modello agileSCRUM. Questo permette di

ge-stire il ciclo di sviluppo del software in modo iterativo e incrementale. Così facendo un ciclo di lavoro dura un periodo di tempo fissato che può variare da 1 settimana fino a un mese. Questo ciclo è chiamato sprint. In questo progetto si è deciso che la durata di uno sprint

fosse di 2 settimane. Durante questo periodo gli sviluppatori implementano una o più fun-zionalità utente, o ancheuser story, in modo da ottenere entro fine sprint una versione del

software potenzialmente completa e funzionante. Ogni user story ha un valore definito in termini di story points che è deciso dal gruppo di lavoro, o team: gli story points

rappre-sentano la valutazione del tempo che, in teoria, uno sviluppatore impeiega per risolvere il problema in relazione alla sua complessità. Inizialmente è impossibile dare una valutazione precisa in questi termini perché ogni sviluppatore ha le proprie capacità, però già dopo i pri-mi sprint il processo di valutazione diventa più preciso. Tutte le user story individuate sono messe in un "contenitore", oproduct backlog, dal quale ad ogni sprint sono estratte e

(20)

mes-14 Metodologia di sviluppo

se nellosprint backlog. Qui le user story sono suddivise ulteriormente in feature, e queste

in semplicitoDo; questo modus operandi aiuta gli sviluppatori a realizzare (relativamente)

piccole porzioni di codice che messe insieme creano un software funzionate. Per mettere in pratica questa metodologia è stata utilizzata la piattaforma di TI-EDUSCM, o Source code

management, nella quale è possibile impostare le caratteristiche del modello agile come la durata degli sprint, definire il contenuto di product backlog e configurare il contenuto degli sprint.

(21)

Capitolo 4

Producer

Dopo aver introdotto la struttura del progetto in generale, ora si entra più nel dettaglio analizzando le singole componenti per funzionalità acquisite durante l’implementazione del componente.

Il compito principale di questo componente è quello di produrre delle note musicali, o me-glio è quello di produrre dei messaggi che interpretati dal consumer riproducano delle note musicali.

4.1

Connessione al dispatcher

La prima funzionalità implementata è stata quella di poter stabilire una connessione tra producer e dispatcher. Per fare ciò innanzitutto è stato necessario creare un’applicazione android basilare. Questa prima versione divideva lo schermo in due parti distinte:

• PARTE SUPERIORE, che è la parte interattiva dell’applicazione

• PARTE INFERIORE, che è la parte in cui visualizzare il log delle operazioni

Figura 4.1: Layout producer prodotto dall’IDE Android Studio

Per creare una connessione tra producer e dispather si è usata la classe "Socket", classe messa a disposizione da java nel package "java.net". Prima di parlare di implementazione

(22)

16 Producer

bisogna tenere conto che un socket è fondamentalmente una coppia di numeri che rap-presentano le porte attraverso le quali due computer, che hanno i ruoli di client e server (rispettivamente producer e dispatcher), comunicano. Per poter stabilire questa connes-sione è necessario che il client conosca l’indirizzo IP1 del server e la porta2. In questo momento dell’implementazione queste informazioni sono state scritte direttamente nel co-dice sorgente ma è una scelta non funzionale perché ogni volta che si cambia rete bisogna cambiare l’indirizzo IP. La porta del server che si è scelto usare per comunicare è la 9999, mentre la porta del producer è scelta in modo automatico dal sistema operativo tra quel-le disponibili al momento. Per utilizzare la connessione e contemporaneamente interagire con l’applicazione è necessario utilizzare una classe che ha il compito di far comunicare il client e il server e che non processi sul thread principale. Questa classe è stata chiama-taSocketClient ed estende AsyncTask<>. AsyncTask è un classe ausiliaria della classe

Thread che mette a disposizione android nel suo package android.os. Questa permette di eseguire operazioni in background, cioè non sul thread della User Interface (UI), e pubbli-care i propri risultati sulla UI senza doversi preoccupare di sincronizzazione o altri problemi di programmazione multithreading. Estendendo questa classe si deve sovrascrivere il me-todo doInBackground() nel quale si trova il codice che esegue il thread. L’altro metodo

sovrascritto è onProgressUpdate() il quale è l’unico modo per interagire con la UI, cioè

pubblicare i log delle operazioni svolte.

Figura 4.2: Panoramica SocketClient

A livello pratico il thread stabilisce la connessione (socket.connect()), iniziallizza i buffer

che il socket usa per leggere i messaggi dal server (BufferedReader in) e scrivere i messag-gi per il server (PrintWriter out). Dopodiché legge il messagmessag-gio di benvenuto del server (in questo modo si verifica che la connessione è avvenuta con successo) e inizia il ciclo while

1

Etichetta numerica che identifica univocamente un dispositivo collegato a una rete informatica

2

Numero da 0 a 65535 che identifica la porta su cui è in ascolto il server. I valori da 0 a 1024 sono riservati e non utilizzabili

(23)

di sospendere la connessione l’istanza comunica al server che ha terminato l’esecuzione (out.println(getTimeStamp() + "Background thread finished!")) e chiude il socket. In

questo momento dello sviluppo software la connessione viene aperta al momento dell’aper-tura dell’applicazione stessa. Questo modo di procedere non è ottimale ma verrà modificato nelle prossime iterazioni.

4.1.1

Gestione touch

Raggiunto questo punto è stata implementata la "callback"onTouchEvent, ovvero quel

me-todo che viene chiamato dal sistema operativo quando un determinato evento si è verificato. Questa callback accetta come parametro un oggetto di tipo "MotionEvent" il quale contiene tutte le caratteristiche del tocco sul schermo del device. Si deve tenere presente che ogni volta che si tocca lo schermo di un qualsiasi device android non vengono prodotti, come si potrebbe pensare semplicemente, solo gli eventi di inizio_tocco e fine_tocco, ma molti altri eventi che inconsapevolmente produce l’utente al momento del tocco. Bisogna tenere conto che lo schermo dei dispositivi odierni ha milioni di pixel e che nel momento in cui si tocca lo schermo stesso si stanno toccando molti più pixel che uno solo. Inoltre con un normale tocco vengono prodotti oltre agli eventi di inizio e fine, anche eventi che rappresen-tano lo scorrimento del dito su più pixel; questo è molto utile nel caso in cui l’applicazione deve saper riconoscere dei movimenti preimpostati (le cosiddette "gesture") per reagire di conseguenza, come nel caso di giochi che associano a dei movimenti delle determinate azioni. Ma questo non rappresenta la nostra situazione e quindi ci si è limitati a gestire gli eventi ACTION_DOWN eACTION_UP: questi rappresentano gli eventi che corrispondo al momento in cui comincio a toccare lo schermo (ACTION_DOWN) e al momento in cui smetto di toccarlo (ACTION_UP). A questo punto dell’implementazione, agli eventi appena descritti

l’applicazione eseguiva semplicemente dei log nella textview3 in modo da poter verificare che fossero gestiti correttamente. La textview occupa laPARTE INFERIOREdello schermo e si può riconoscerla sia dal titolo che dal colore di sfondo (giallo) che è stato utilizzato.

4.2

Produzione singola nota

Durante il secondo sprint è stato scelto di implementare la funzionalità di produrre una nota utilizzando il sensore touch del device. Per fare ciò la callback che gestisce il tocco sullo schermo è stata modificata per produrre il messaggio "L’utente ha toccato il dispositivo!!".

3

(24)

18 Producer

Questo messaggio viene settato in un campo dell’istanza diSocketClient che sta

eseguen-do in background e verrà inviato al server che a questo punto dello sviluppo interpreterà il messaggio. Infine è stato aggiunto che ogni messaggio di log prodotto dal producer con-tenesse il timestamp4 del momento in cui l’azione loggata fosse avvenuta: in questo modo si può verificare la latenza della rete confrontando questo valore con quello prodotto sul server, o sul consumer. La differenza tra i valori del timestamp è nell’ordine dei centesimi di secondo.

4.2.1

Stato della connessione con il server

Si è deciso inoltre che è opportuno che il producer non si collegasse la server automatica-mente all’avvio dell’applicazione ma che fosse l’utente a decidere quando connettersi e che il producer visualizzi lo stato della connessione con il server. In questo modo è possibile gestire più efficientemente la connessione e i problemi che possono crearsi. Per prima cosa è stato creato un bottone visualizzabile nella pagina principale dell’applicazione che contie-ne la stringa "OFF" ed è di colore rosso per comunicare all’utente che la concontie-nessiocontie-ne non è attiva. A questo bottone è stata aggiunta la callback che risponde al tocco sul bottone: questa funzione ha il compito di cercare di stabilire la connessione con il server. E inoltre è stato aggiunto un pezzo di codice che evita di bloccare l’applicazione nel caso in cui il server sia offline e l’applicazione cerchi continuamente di connettersi. Il codice riprotato in figura

Figura 4.3: Codice che gestisce i tentativi di connessione

4.3 è quello che gestisce i tentativi di connessione del producer al server. Viene sfruttato il metodoconnect() messo a disposizione dalla classe Socket che accetta come parametri

l’indirizzo del server, comeInetSocketAddress, e opzionalmente un tempo massimo entro

il quale connettersi. Scaduto tale tempo il metodo produce un’eccezione che viene gestita comunicando all’utente che è impossibile al momento connettersi e che verrà effettuato un altro tentativo più tardi. É stato deciso un numero massimo di tentativi, o meglio un tempo massimo entro il quale se la connessione non è stabilita si interrompono tutti i tentativi di connessione: questo tempo è di un minuto (MAXtimeout = 60) e si effettuano tentativi ogni 5 secondi (Thread.sleep(5000) ). In caso che la connessione sia stata stabilita con successo

4

(25)

4.3

Selezione nota musicale e strumento

Per il terzo sprint è stato deciso di poter dare all’utente la possibilità di selezionare quale nota produrre e con quale strumento riprodurla. Per fare ciò è stato aggiunto all’applicazione un menù che contenesse le azioni che un utente si aspetta di poter effettuare. Tali azioni sono:

• stabilire una nuova connessione

• selezionare la nota musicale

• selezionare lo strumento musicale

4.3.1

Stabilire connessione

Per poter stabilire una connessione con il server a livello di codice non si sono fatte grosse modifiche: il bottone con cui prima si faceva questa azione è stato deresponsabilizzato e ha solo la funzione di visualizzare lo stato della connessione attraverso i colori rosso e verde. La voce del menù invece crea un’istanza della classeSocketClient con la quale si stabilisce

la connessione con il server.

4.3.2

Selezionare nota

Questa voce del menù apre una lista di note dalla quale l’utente può selezionare la nota che desidera. Le note presentate sono scritte con la notazione anglosassone: le note classiche DO, RE, MI, FA SOL, LA e SI sono anche rappresentate dalle lettere, rispettivamente, C, D, E, F, G, A e B. Oltre alle note cosiddette "naturali", cioè senza alterazioni, è possibile sele-zionare le altre cinque note che completano un’ottava5, ovvero A#6, C#, D#, F# e G#. Infine ad ogni nota è associato un numero che indica in quale ottava riprodurre la stessa, e le ottave rappresentate sono 7 (0 - 6). A livello di codice questo è stato realizzato con l’aggiun-ta di tre classiNote, NoteFragment e MyNoteRecyclerViewAdapter, che rispettivamente

rappresentano una singola nota nell’elenco, l’elenco delle note in un nuovo fragment7 e ge-stiscono l’interazione tra il contenitore di elementi di una lista (NoteFragment) e gli elementi contenuti (Note). Queste tre classi sono state prodotte dall’IDE con File->New->Fragment->Fragment(List) con un esempio funzionante composto da DummyItem. Il codice che è

5

Intervallo di 8 note posizionate a frequenza diversa nella scala musicale

6

La diesis

7

(26)

20 Producer

stato prodotto automaticamente evidenzia le parti in cui dev’essere modificato a seconda delle esigenze degli oggetti da rappresentare. Dopo che è stata selezionata una nota viene prodotto il messaggio: timestamp/origine/NOTE:nota. Per origine si intende la classe che produce il timestamp, quindi la classe che che ha effettuato l’azione. Il messaggio prodotto è passato all’istanza della classeSocketClient che lo invierà via socket al server.

4.3.3

Selezionare strumento

Analogamente a quello che è stato fatto per le note, sono state create le stesse classi che gestiscono la selezione dello strumento; gli strumenti disponibili sono: Basso elettrico, Fagotto, Violoncello, Clarinetto, Contrabbasso, Flauto, Corno francese, Chitarra acustica, Chitarra elettrica, Chitarra acustica con corde di nylon, Armonio, Arpa, Organo, Piano, Sas-sofono, Trombone, Tromba, Tuba, Violino e Xilofono. Questa volta il messaggio prodotto contiene la strumento con questa notazione: "timestamp/origine/INSTRUMENT:strumento".

4.3.4

Controllo lunghezza nota con touch

Durante questa iterazione si è voluto anche raggiungere l’obiettivo di controllare la lunghez-za di riproduzione delle note come succede con uno strumento reale in cui il musicista decide per quanto tempo riprodurre la nota. L’idea di base è quella di utilizzare il touch come un tasto che finché è premuto la nota viene riprodotta, mentre quando viene rila-sciato la nota smette di essere riprodotta. Per fare ciò sono stati introdotti nuovi messaggi che identificano queste azioni: TOUCH_START eTOUCH_END. Questi messaggi, che sono prodotti rispettivamente quando inizio a toccare lo schermo e quando smetto di toccarlo, sono inviati al server tramite l’istanza della classe SocketClient sempre con il protocollo

"timestamp/origine/TOUCH_START/END".

4.3.5

Sensore di prossimità

L’ultima funzionalità che è stata introdotta in questo terzo sprint è stata l’utilizzo del sensore di prossimità per la produzione di note e per il controllo della lunghezza di riproduzione della nota stessa. Innanzitutto bisogna tenere presente che il sensore di prossimità non è pre-sente su tutti i dispositivi, anche perché è un componente hardware. Proprio perché si parla di un compoente hardaware le diverse versioni posso funzionare in modo differente a se-conda del tipo di tecnologie che sono state usate per implementarlo. Nella guida di Android Developers8 si avvertono i programmatori dei due tipi di sensori di prossimità esistenti:

• sensore continuo, ovvero restituisce la distanza dal sensore entro un range

• sensore discreto, ovvero restituisce due valori numerici che equivalgono a true o false

8

(27)

utilizzare il sensore, a livello di codice, bisogna che laMainActivity implementi l’interfaccia SensorEventListener per poter catturare gli eventi prodotti dai sensori. Questa interfaccia

è generica, nel senso che è possibile ricevere eventi da qualsiasi sensore presente sul devi-ce a patto che questi sensori siano ottenuti dalSensorManager e che vengano registrati per

ottenere gli eventi. Ciò a livello di codice si ottiene così: Nel metodoonSensorChanged si

Figura 4.4: Codice che gestisce gli eventi del sensore di prossimità

riceve l’evento che è prodotto dal sensore e da questo si può estrapolare che tipo di evento è. In questo specifico caso si controlla la distanza che il sensore calcola e se è minore della metà del massimo valore rilevabile (if (distance < (proximitySensor.getMaximumRange() / 2))) si produce il messaggio PROXIMITY_START, in caso contrario il messaggio prodotto è

PROXIMITY_STOP. Fare un controllo del genere evita di fare una distinzione tra i due tipi di sensori di prossimità che si possono avere, riducendoli essenzialmente ad un sensore discreto. I messaggi prodotti sono inviati come nel caso del touch.

(28)

22 Producer

4.4

Produzione di una melodia

Nel quarto sprint è stato deciso di implementare la funzionalità "carillon", cioè di produrre una melodia preimpostata. Prima di poter produrre una melodia preimpostata è necessario trovare una melodia e tradurla in un linguaggio compatibile con il consumer che deve ripro-durla. La melodia scelta è "Fra Martino" suonata con un pianoforte. Dallo spartito musicale

Figura 4.5: Spartito Fra Martino

si è passati al solfeggio delle note, cioè a leggere le note dallo spartito come DO-RE-MI etc, fino alla traduzione in note anglosassoni. Dopodiché ad ogni nota così tradotta è stata aggiunta l’ottava in cui riprodurre la nota sotto forma di numero da 0 a 6 (coerentemente alla lista da cui si può selezionare la nota singola). Infine ad ogni nota è stato associato per quanto tempo riprodurre la nota, anche questo parametro è espresso come numero se-guendo questa regola: DURATA [1 = 4/4; 2 = 2/4; 4 = 1/4; 8 = 1/8; 16 = 1/16; ...] Il numero prima dell’uguale è quello che identifica la durata all’interno dell’applicazione, il rapporto a destra dell’uguale indica il rapporto della durata della nota riferito al metro con cui è scritto lo spartito. Con questo modo di leggere uno spartito , lo spartito di "Fra Marino" è stato tradotto e salvato in un file:

INSTRUMENT:piano

MELODY:C3-4;D3-4;E3-4;C3-4;C3-4;D3-4;E3-4;C3-4;E3-4;F3-4;G3-2;E3-4;F3-4;G3-2;G3-8;A3-8;G3-8;F3-8;E3-4;C3-4;G3-8;A3-8;G3-8;F3-8;E3-4;C3-4;D3-4;G2-4;C3-2;D3-4;G2-4;C3-2 La prima riga di questo file riporta lo strumento con cui si vuole riprodurre la melodia, la seconda riga riporta la serie di note con tutte le informazioni necessarie alla riprodu-zione secondo il protocollo "NotaOttava-Durata" e sono separate le une dalle altre da un punto e virgola. Il file contenente queste informazioni per poter essere letto dall’applica-zione dev’essere messo in una cartella speciale chiamata "raw", all’interno della cartel-la che contiene tutte le risorse dell’applicazione chiamata "res". Se non fosse in questa posizione del fileSystem l’applicazione dovrebbe avere i permessi per poter leggere dalla memoria del dispositivo e inoltre l’applicazione dovrebbe sapere dove si trova il file all’in-terno della memoria. Invece, in questo modo, è possibili riferirsi al file attraverso il suo

(29)

tre campi:

• String fileName, in cui viene riportato il nome della melodia letta, è un parametro del metodo

• String instrument, in cui è riportato lo strumento con il quale riprodurre la melodia

• String[] melody, in cui un elemento è "NotaOttava-Durata"

Questa classe espone oltre ai relativi "getter" e "setter" un metodotoJsonString() che

scri-ve una stringa nel formato JSON9. Questo perché la stringa in formato JSON è il messaggio che viene inviato inviato al server sempre tramite l’istnaza della classeSocketClient.

Que-sta funzionalità è messa a disposizione dell’utente come nuova voce del menù: Produci Carillon.

4.4.1

IP server dinamico

Usare un indirizzo IP scritto manualmente nel codice per connettersi con il server non è mai stata un’idea funzionale, quindi è stato creato un meccanismo grazie al quale l’applicazio-ne producer possa ottel’applicazio-nere dinamicamente l’indirizzo IP del server. Questo "meccanismo" sfrutta un altro protocollo di comunicazione in rete, ovvero il protocollo UDP10. Attraverso questo l’applicazione chiede a tutti i dispositivi connessi alla rete (broadcasting), su una spe-cifica porta, se sono il server che sta cercando; quando il server riceve il messaggio questo risponde all’applicazione con un altro messaggio. Grazie a questo scambio di messaggi e al protocollo UDP è possibile ottenere l’indirizzo IP del server. Questo meccanismo, così co-me è stato spiegato, è co-messo in funzione dalla classeDiscoverServerIPThread.

Quest’ul-tima estende AsyncTask perché deve processare in background rispetto alla MainActivity, e quando ottiene la risposta dal server restituisce un valore di ritorno che è l’IP del server. Un’istanza di questa classe è creata quando viene selezionata la voce del menù "Nuova connessione" e viene immediatamente "messa al lavoro". Appena termina l’esecuzione e restituisce l’IP questo è utilizzato per stabilire la connessione con il server.

9

JSON, acronimo di JavaScript Object Notation, è un formato adatto all’interscambio di dati fra applicazioni client/server

10

(30)

24 Producer

4.4.2

Nuovo protocollo di comunicazione

Ricapitolando, i messaggi che vengono trasmessi al server sono di due tipi allo stato attuale di implementazione:

• CarillonMessage, per trasmettere una melodia intera da riprodurre

• stringa contente "TOUCH_START/STOP" per produrre e fermare una nota usando il touch

• stringa contente "PROXIMITY_START/STOP" per produrre e fermare una nota usan-do il sensore di prossimità

A livello di server questo implica che è il server stesso che deve capire di quale genere di messaggio si tratta e comportarsi di conseguenza. Quindi per facilitare il lavoro del server si è deciso di cambiare il modo di comunicare dal producer al server. Si è deciso di creare una struttura di comunicazione che fosse compatibile con i messaggi che attualmente vengono prodotti dal producer e che liberi il server dall’elaborazione di tali messaggi. Per ottenere ciò è stata introdotta una nuova classeMessage che contiene i seguenti campi:

• String note, cioè una stringa che contiene la nota selezionata

• String instrument, cioè una stringa contente lo strumento selezionato

• String id, cioè una stringa contente l’id univoco dell’istanza di Message

• String command, cioè una stringa che contiene o "PLAY" o "STOP"

Inoltre questa classe espone "getter" e "setter" per ogni campo e il metodotoJsonString,

già descritto per la classeCarillonMessage. Utilizzando questa classe i messaggi prodotti

utilizzando il touch o il sensore di prossimità diventano, a livello di codice, lo stesso tipo di messaggio. Nella pratica quando si seleziona una nota questa viene salvata nell’istanza della classeMessage, quando si seleziona uno strumento anch’esso è salvato nell’istanza

della classeMessage, quando si tocca il touch o ci si al sensore di prossimità viene settato

il campo command con "PLAY" e il campo con un id univoco creato dalla combinazione del MAC address11del device più il timestamp e il messaggio trascritto in forma JSON viene inviato al server. In modo analogo quando si smette di toccare il touch o ci si allontana dal sensore di prossimità il campo command è settato con il valore "STOP" e nuovamente inviato al server. Si noti che durante il ciclo di vita dell’applicazione quando si produce una singola nota viene utilizzata un’unica istanza della classeMessage, la quale è modificata

ogni volta che l’utente seleziona una nuova nota o un nuovo strumento, e ogni volta che produce la nota e la ferma.

11

Codice di 48 bit assegnato in modo univoco dal produttore ad ogni scheda di rete ethernet o wireless al mondo

(31)

della polifonia nel producer è necessario parlare della produzione di una singola melodia: così com’è l’implementazione il messaggio prodotto contiene l’intera melodia ed è preoccu-pazione del consumer riprodurla ma non è il risultato che si vuole ottenere. In realtà l’unico compito che dovrebbe eseguire il consumer è eseguire i comandi che vengono prodotti dal producer e gli unici comandi che il producer può produrre sono "PLAY" e "STOP".

4.5.1

Produzione melodia nota per nota

A questo punto la logica di produzione di una melodia viene completamente stravolta per-ché è il producer stesso che deve mandare i messaggi nel momento giusto seguendo la melodia scritta nel file, cioè deve produrre il messaggio di "PLAY" di una nota e dopo il tem-po che deve durare quella nota il messaggio di "STOP" e subito quello di "PLAY" della nota successiva, e così via. Per fare ciò è stata scritta una nuova classe,CarillonSocket, il cui

compito è quello di produrre i messaggi necessari per riprodurre la melodia correttamente. Questa estendeAsyncTask, quindi si tratta di un thread che esegue in background le

pro-prie operazioni. A livello pratico ogni istanza di questa classe apre un nuovo socket verso il server perché in questo modo si evita di dover dipendere da un solo socket per inviare tutti i messaggi (ipoteticamente molti anche nello stesso momento se si pensa ad un’orchestra) e quindi avere un traffico sulla rete distribuito su diversi canali di comunicazione. La scelta di aprire un nuovo socket per comunicare una melodia al server si è resa necessaria dopo aver testato empiricamente12i risultati ottenuti con l’implementazione che utilizzava solo un canale per inviare i messaggi; i test facevano notare come la soluzione con un solo canale aperto producesse degli errori nella riproduzione e tra i vari motivi c’era il fatto che i mes-saggi arrivassero nel momento sbagliato. Ciò era dovuto alla cooperazione di più thread che parallelamente settavano il messaggio da inviare al server e non c’era nessuna assi-curazione che il messaggio venisse mandato nel momento corretto. Con l’uso di un nuovo socket per ogni melodia questo problema non sorge e gli unici responsabili di errori nella produzione della melodia sono lo scheduler del sistema android che gestisce l’esecuzione dei thread e il traffico della rete su cui il sistema comunica. Dopo aver aperto la nuova con-nessione al server l’istanza di CarillonSocket all’interno di un ciclo estrae le informazioni

di ogni singola nota dall’oggettoCarillonMessage e crea un nuovo oggetto Message con

il comando "PLAY" per inviare le informazioni al server e riprodurre la nota. L’informazione riguardante la durata della nota è utilizzata per "mettere a dormire" il thread in modo da aspettare il tempo giusto prima di inviare lo stesso messaggio inviato precedentemente solo

12

(32)

26 Producer

con il comando "STOP". La durata non è utilizzata così come si trova scritta ma viene elabo-rata per trasformarla in un intervallo di tempo in secondi attraverso la funzionetoSecond().

Al termine del ciclo il lavoro del thread termina chiudendo il socket che ha utilizzato.

4.5.2

CarillonSocket con canone

Per raggiungere l’obiettivo di produrre una polifonia in questo sprint si è deciso di pro-durre "Fra Martino" con un canone: cioè di propro-durre due melodie identiche di cui la se-conda inizia dopo un certo numero di note della prima. Questo risultato è ottenuto con l’ultimo parametro che accetta il costruttore della classe CarillonSocket, ovvero il

para-metro che inizializza il campo canon e che rappresenta il numero di note dopo le quali

far partire la stessa melodia. Per produrre la seconda volta la stessa melodia viene det-to alla MainActivity di istanziare e far partire una nuova istanza di CarillonSocket (con

canon = -1 per non ripetere ulteriormente la melodia). Il passaggio attraverso l’activity principale dell’applicazione è necessario per il corretto funzionamento del sistema opera-tivo android, altrimenti l’applicazione si arresta producendo un errore interno. Infatti an-droid permette solo all’Activity principale di creare e far eseguire nuovi thread; inoltre sem-pre android obbliga gli sviluppatori ad utilizzare "Esecutore" per eseguire più di due th-read (uno è la MainActivity, l’altro è SocketClient) alla volta correttamente: carillonSoc-ket.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR). Nello specifico TH-READ_POOL_EXECUTOR è un contenitore di thread che permette l’esecuzione in

paral-lelo, al contrario diSERIAL_EXECUTOR che esegue in modo ordinato e seriale. L’utente

può utilizzare questa funzionalità selezionando dal menù la voce "Produci polifonia".

4.5.3

Costruzione di un ID univoco

Ogni nota prodotta per poter essere correttamente riprodotta dal consumer necessità di ave-re un ID che sia univoco all’interno del sistema: fino a questo momento l’ID consiste in una stringa che è costruita concatenando una stringa che contiene il MAC address del device ad una stringa che contiene il timestamp. Questo però non è più sufficiente per assicurare l’univocità dell’ID perché se si pensa che in questo momento ci sono due thread che stanno eseguendo (idealmente) in parallelo e che stanno producendo la stessa melodia potrebbe capitare di produrre due note nello stesso istante. Per evitare che si creino problemi nella riproduzione è stata modificata la costruzione dell’ID: si tratta sempre di una stringa otte-nuta concatenando clientNumber13, MAC address, timestamp e strumento con cui voglio riprodurre la nota. Questa costruzione assicura che anche se due o più thread producono la stessa nota nello stesso momento avranno comunque sia un ID diverso.

13

(33)

che arriva al consumer è Preloading terminato ed è il segnale che il sistema ha caricato tutte le risorse necessarie e aspetta messaggi dal producer. Il producer prima dell’arrivo di questo messaggio non è in grado di produrre alcuna nota per evitare errori nel consumer. L’utente può sapere quando può produrre le note guardando il colore del bottone in alto a sinistra dell’applicazione. Questo bottone prima aveva solo il compito di dare un feedback sullo stato della connessione tra producer e server, ora il colore verde significa sia che la connessione è stata stabilita, sia che il consumer è pronto per l’esecuzione.

4.6

Polifonia 2

Durante l’ultima iterazione si è deciso di introdurre una nuova melodia per verificare il cor-retto funzionamento della polifonia sia a livello di producer che a livello di consumer. La melodia scelta è "Aria sulla quarta corda", cioè la trasposizione del violinista tedesco Au-gust Wilhelmj dell’Aria della terza Suite per orchestra in re maggiore di Bach (BWV 1068). Come fatto per "Fra Martino" il primo passo è stato tradurre lo spartito in un file leggibile

Figura 4.6: Estratto dello spartito dell’Aria sulla quarta corda

e interpretabile dall’applicazione. Questa volta però le melodie sono 2 diverse e suonabili con due strumenti diversi: la prima riga è suonata solitamente con un violino, la seconda riportando note solitamente più basse è suonata con un violoncello. Inoltre in questo spar-tito compaiono le "legature di valore", ovvero quei tratti che uniscono alcuno note, come nel caso delle prime tre note nella prima riga dello spartito. Queste legature di valore significano

(34)

28 Producer

che le note dovrebbero essere suonate come un’unica nota, cioè non dovrebbe sentirsi lo stacco tra una e l’altra, ma all’interno del protocollo che si utilizza per tradurre lo spartito non esiste nessun modo per tradurre questo tipo di informazione. L’unica alternativa sa-rebbe quella di riprodurre un’unica nota con lunghezza uguale alla somma delle lunghezze delle note legate, ma questo non è possibile perché si stanno utilizzando numeri interi per rappresentare la lunghezza delle note. Quindi il file risulterà così:

INSTRUMENT1:violin MELODY1:F#5-1;F#5-1;F#5-4;B5-8;G5-8;E5-8;D5-8;C#5-8;D5-8;C#5-2;B4-8;A4-4;A4-8;A5- 1;A5-8;F#5-8;C5-8;B4-8;E5-8;D#5-8;A5-8;G5-8;G5-1;G5-8;E5-8;B4-8;A4-8;D5-8;C#5-8;G5- 8;F#5-8;F#5-2;F#5-4;G#5-8;A5-8;D5-4;D5-16;E5-16;F#5-8;F#5-8;E5-8;E5-8;D5-8;C#5-8;B4-8;B4-16;C#5-16;D5-8;D5-4;C#5-8;B4-8;A4-1 INSTRUMENT2:cello MELODY2:D3-4;D4-4;C#4-4;C#3-4;B2-4;B3-4;A3-4;A2-4;G2-4;G3-4;G#3-4;G#2-4;A2-4;A3- 4;G3-4;G2-4;F#2-4;F#3-4;E3-4;E2-4;D#2-4;D#3-4;B2-4;B3-4;E2-4;E3-4;D3-4;D2-4;C#2-4;C#3- 4;A2-4;A3-4;D3-4;D4-4;C#4-4;C#3-4;B2-4;B3-4;G#3-4;E3-4;A3-4;D3-4;E3-4;E2-4;A2-8;B2-8;C#3-8;D3-8;E3-8;G3-8;F#3-8;E3-8

Dopodiché è stata creata una nuova classe,PolyphonicFileReader, che ha il compito di

leggere il file ed estrarre le due melodie separate. Le singole melodie sono salvate in oggetti di tipoCarillonMessagge e ordinati all’interno di un array. A questo punto, coerentemente

con quanto fatto prima, ogni melodia deve essere prodotta in un thread ma così facendo sorge il problema che un thread comincia ad eseguire prima dell’altro. Per risolvere ciò è stato utilizzato lo strumento di sincronizzazione messo a disposizione da javaCyclicBarrier.

Quest’ultimo può essere paragonato all’arbitro della gara dei cento metri piani: mette ad aspettare i thread (che sono gli sprinter) finché non sono tutti arrivati allo stesso punto dell’esecuzione e questo punto li lascia eseguire. Il numero di thread che fa aspettare è un parametro che viene passato al costruttore e come dice il nome questo oggetto può essere riutilizzato più volte. I thread vengono fermati dopo aver concluso le operazioni preliminari come stabilire la connessione, inizializzare i buffer di input e output e prima di cominciare la produzione delle note. Tutto questo permette di produrre la melodia polifonica correttamente (a meno di latenze sulla rete) metricamente parlando.

(35)

Capitolo 5

Dispatcher

Il dispatcher in questa architettura è l’elemento centrale, quello che permette la comunica-zione tra il producer e il consumer. La sua funcomunica-zione principale è quella di inoltrare i messaggi prodotti da uno o più producer e indirizzarli verso il consumer. Visto le sua funzionalità all’in-terno del sistema questo componente deve assicurare alte performance in termini di velocità di inoltro dei messaggi per permettere la definizione di sistema realtime. Si è scelto di im-plementare questo componente con il linguaggio di programmazione Java principalmente perché possiede tutti gli strumenti necessari per costruire questo genere di applicazione, perché è stato il linguaggio di programmazione di riferimento durante il mio ciclo di studi e perché garantisce buone performance riguardo alla comunicazione via socket. Analo-gamente al producer, in questo capitolo si analizzeranno le funzionalità acquisite durante l’implementazione.

5.1

Connessione con producer(s)

La prima funzionalità implementata è stata stabilire una connessione con un producer e come naturale conseguenza la possibilità di supportare più connessioni con eventuali più producer. Per fare ciò sono state create due classi: ListenerServer e ListenerThread. La

prima è la classe principale, quella che inizializza tutti gli oggetti e che permette di mettere in funzione il server. Tra gli oggetti che inizializza il più importante è unServerSocket che

è un’implementazione java di un server socket, il quale aspetta delle richieste in arrivo sul-la rete sulsul-la porta specificata al momento delsul-la costruzione (ServerSocket listener = new ServerSocket(listenerPortNumber)). La porta specificata è la 9999 ed è la stessa che

il producer deve specificare quando vuole connettersi al server. Essenzialmente l’oggetto

listener ha solo il compito di aspettare richieste di connessione e lo fa attraverso il

meto-do accept(); questo metodo blocca l’esecuzione del programma finché non stabilisce una

connessione e restituisce il socket aperto attraverso il quale producer e dispatcher possono comunicare. Il socket che ha ritornato è poi utilizzato per creare un’istanza di

(36)

ListenerTh-30 Dispatcher

read, il quale come dice il nome è un thread che si mette in ascolto sul socket. Il ciclo di

vita di questo thread comprende una fase iniziale in cui inizializza i buffer di input e output (BufferedReader e PrintWriter) per comunicare attraverso il socket e invia un messaggio

di benvenuto al producer per comunicare due informazioni: il numero progressivo che iden-tifica il producer e che la connessione è stata stabilita con successo. Dopodiché il thread entra in un ciclo while che ha il compito di ascoltare i messaggi che produce e invia il pro-ducer. String input = in.readLine() è il modo in cui i messaggi vengono letti: se il valore

della stringa ottenuta ènull oppure "." il ciclo si interrompe e il thread chiude la connessione

socket e termina la sua esecuzione. Si svolge questa doppia verifica sulla stringa perché si vogliono intercettare il caso in cui la connessione è caduta (caso null) oppure il caso in cui il producer voglia interrompere la comunicazione e quindi invia un messaggio specifi-co (".") per specifi-comunicarlo al dispatcher. Con queste due classi è stato possibile creare un server multithread che supportasse la connessione di più producer contemporaneamente. Le modifiche al codice sono state minime: infatti è bastato introdurre un cicloWhile(true)

che aspetta nuove connessioni, e quando un nuovo producer si collega viene istanziato un nuovoListenerThread.

Figura 5.1: Prima implementazione della classe ListenerServer

5.1.1

DispatcherLogger

In un server multithread è assolutamente necessario avere un sistema per controllare le operazioni che sono state svolte: in questo modo si ha maggiormente sotto controllo il ser-ver stesso ed possibile scoprire e trovare più velocemente eventuali errori. Il sistema di controllo implementato è una pagina web statica in cui vengono stampate tutte le operazio-ni che il server compie. Quindi si è reso necessario introdurre un nuovo componente nel sistema che prende il nome di DispatcherLogger. Per fare ciò si è utilizzato il framework javascript Vue.js: questo è dedicato alla realizzazione di interfacce web reattive che

(37)

sfrutta-mette a disposizione del programmatore un modo molto semplice per aggiornare i dati e visualizzarli sulla pagina. Infatti grazie al data-binding, cioè al meccanismo che associa

Figura 5.2: Prima implementazione del DispatcherLogger

dati a elementi dell’interfaccia utente, è necessario solo aggiornare la variabilelogger per

mostrare all’utente i log che arrivano dal dispatcher. All’interno del tag <script> si trova la

proprietàsockets: questa è un’implementazione di Socket.IO per Vue che rende molto più

semplice gestire le connessioni tramite websocket. Andando con ordine, Socket.IO è una libreria javascript per applicazioni web realtime che permette una comunicazione bidirezio-nale tra web client e server. Per utilizzare questa libreria è stata prima di tutto installata nel progetto attraverso il package manager "npm"; a livello di codice bisogna inizializzare una variabile che si mette in ascolto su una determinata porta (SocketIstance = socke-tio(’http://localhost:5560’)). Infine è necessario dire a Vue di utilizzare quella variabile

(Vue.use(VueSocketIO, SocketIstance)) per permettere a tutti i componenti ".vue" di

rice-vere eventi tramite websocket. Socket.IO mette a disposizione del programmatore un siste-ma di multiplexing grazie ai "namespace"2: cioè sulla stessa connessione socket è possibile mettersi in ascolto solo di alcuni eventi che sono specificati da delle stringhe e questi eventi

1https://www.html.it/pag/63947/vue-js-unintroduzione/ 2

(38)

32 Dispatcher

sono chiamati, appunto, namespace. Con questo meccanismo al server basta specificare il namespace corretto per assicurarsi che i dati che vuole trasmettere sia correttamente instra-dati. Inoltre Socket.IO permette di inviare qualsiasi tipo di dato: dai tipi primitivi agli oggetti più complessi. Facendo riferimento alla figura 5.2 si è utilizzato il namespacedispatchLog

per ricevere e gestire gli eventi di log dal server: in questo caso il tipo di dato ricevuto è un stringa ed è mostrata sull’interfaccia utente così come è stata composta lato dispatcher.

5.1.2

Classe WebSocketServer

Il DispatcherLogger nell’architettura di progetto ha il ruolo di web client, mentre il ruolo di server lo ricopre la classeWebSocketServer all’interno del dispatcher. Questa classe ha

il compito di inviare i messaggi dal dispatcher ai web client che sono in ascolto tramite il meccanismo dei namespace. La classe WebSocketServer è un contenitore dell’oggetto

SocketIOServer, un’implementazione java del server Socket.IO basata su "Netty". Netty

è un framework per applicazioni di rete event-driven asincroni per un rapido sviluppo del protocollo server & client che mantiene delle performance elevate3. SocketIOServer neces-sita di conoscere l’indirizzo sul quale aprire una connessione websocket: questo indirizzo è costruito in un oggetto di tipoConfiguration come si può vedere nella figura 5.3. La porta

sulla quale viene aperta la connessione websocket tra dispatcher e dispatcherLogger è la 5560 ed è specificata nella classe principale all’interno della variabile dispatcherLogger-PortNumber. L’istanza della classe SocketIOServer così creata è passata come parametro

Figura 5.3: Costruzione del SocketIOServer

al costruttore della classe WebSocketServer; al momento della creazione, dopo aver ini-zializzato i propri campi il server è fatto partire con il metodostart(). L’istanza della classe WebSocketServer è passato come parametro al momento della costruzione di tutte le

istan-ze di ListenerThread perché è l’oggetto tramite il quale il dispatcher comunica con il proprio dispatcherLogger. Per comunicare è stato scritto il metodosendDispatchLog che accetta

Figura 5.4: Metodo in WebSocketServer che comunica con il dispatcherLogger come parametro una stringa. Questo metodo emette sul websocket un evento con il name-space "dispatchLog" e i dati trasmessi sono la stringa contenente il messaggio da loggare.

3

(39)

Nel secondo sprint si è voluta implementare la funzionalità di riprodurre una nota preimpo-stata: l’utente utilizzando il producer vuole poter ascoltare una nota musicale. Il dispatcher in questa funzionalità ha il compito di inoltrare il messaggio che riceve attraverso il socket aperto con il producer verso il consumer. Per comunicare con il consumer il dispatcher apre una nuova connessione websocket utilizzando un’altra istanza della classe WebSoc-ketServer: il numero di porta scelto per questo socket è 5570. Così come nel caso del

dispatcherLogger, questa istanza di WebSocketServer è passata come parametro al co-struttore di ListenerThread ed è l’unico mezzo di comunicazione verso il consumer.

Al-l’interno del ciclo while che "ascolta" i messaggi del producer è stato scritto un controllo:

if(input.contains("L’utente ha toccato il dispositivo!!")). Se si verifica questa

condizio-ne viecondizio-ne fatta la chiamataconsumerWSS.reproduceNote(). Questo metodo della classe

WebSocketServer emette un evento sulla connessione websocket verso il consumer usando il namespace "screenTouch" e i dati trasmessi sono la stringa "C6"4.

5.2.1

Tabella dei producer collegati

In un sistema che mette in collegamento uno o più producer con un server si rende neces-sario che il server sappia quali producer siano collegati e quali si siano scollegati. Per fare ciò nel dispatcher è stata creata una lista che contiene tutti le istanze di ListenerThread

create. Ma per mostrare all’utente quali producer siano o meno connessi è stata aggiunta nel dispatcherLogger una tabella che contiene il numero progressivo che identifica il pro-ducer e un valore booleano che rappresenta lo stato della connessione (true = connesso, false = disconnesso). La tabella è stata realizzata con Bootstrap-Vue. Bootstrap-Vue è un

Figura 5.5: Tabella che rappresenta i producer connessi

4

(40)

34 Dispatcher

progetto che permette di utilizzare Bootstrap con le funzionalità di data-binding che met-te a disposizione Vue per i propri componenti. Questo progetto fornisce dei tag speciali, molto simili a quelli che si è abituati a utilizzare con Bootstrap, che permettono, tra le al-tre componenti, di creare una tabella configurandone stile e contenuti. La linea di codice

<b-table striped hover :items="items" :field="fieldss"></b-table> è quella che

permet-te di creare la tabella: la proprietà :field è quella che permette di specificare i titoli delle

colonne nella tabella mentre la proprietà:item è quella che permette di aggiungere nuove

righe nella tabella. Entrambe queste proprietà sono collegate a delle variabili che rispet-tivamente contengono un array di stringhe che danno il titolo di ogni colonna nella tabella (fieldss) e un array inizialmente vuoto che verrà riempito da oggetti JSON in cui la chiave

è il titolo della colonna e il valore è ciò che viene mostrato nella tabella (items). Grazie

a Vue è possibile modificare il valore di queste due variabili per modificare i valori visua-lizzati sull’interfaccia utente. Nella classe ListenerServer quando un producer si collega

viene fatta la chiamata dispatcherWebSocketServer.addClient(clientNumber). Questo

metodo emette un evento sul websocket aperto con il dispatcherLogger con il namespa-ce "newClient" e i dati trasmessi sono il numero progressivo che identifica il produnamespa-cer. Il dispatcherLogger quando riceve questo messaggio aggiunge una nuova riga alla tabella:

this.items.push(ClientNumber: clientNumber, Connected: true). Quando un

produ-cer si disconnette è l’istanza diListenerThread associata che fa la chiamata dispatcher-WSS.sendToDispatcherClientDisconnected(clientNumber). Questo metodo emette un

evento sul websocket aperto con il dispatcherLogger con il namespace "clientDisconnected" e i dati trasmessi sono il numero che identifica il producer. La chiamata a questo metodo è l’ultima operazione che il listenerThread esegue prima di terminare l’esecuzione. Nel dispat-cherLogger il numero serve per trovare l’elemento all’interno dell’arrayitems e aggiornare

il valore associato alla chiave "Connected" in false.

5.3

Comunicazione nota, strumento o comando

Durante la terza iterazione è stata data la possibilità all’utente di poter selezionare quale nota riprodurre e con quale strumento riprodurla. Il dispatcher in questa funzionalità ha il compito di riconoscere quale genere di messaggio l’utente ha prodotto e inoltrare nel modo corretto le informazioni al consumer. Il dispatcher deve riconoscere se nel messaggio è contenuto la nota, lo strumento oppure un comando di riproduzione o di stop. Nella classeListenreThread, per non appesantire eccessivamente il codice all’interno del ciclo

while, è stato scritto un metodo che ha il compito di controllare il contenuto del messaggio. Nel seguente elenco si descrivono il controllo e il metodo invocato sull’istanza della classe

WebSocketServer che ha stabilito la connessione websocket con il consumer:

if (input.contains("INSTRUMENT:")), con il namespace "setInstrument" è inviata

(41)

if (input.contains("TOUCH_END") || input.contains("PROXIMITY_STOP")), con il

namespace "stopNote" è inviata la stringa "STOP"

5.4

Comunicazione melodia

Nel quarto sprint all’utente è stata aggiunta la funzionalità di riprodurre una melodia preim-postata. Il dispatcher in questa funzionalità ha il compito di comunicare al consumer i mes-saggi che riceve dal producer e di loggare questi mesmes-saggi sul dispatcherLogger. Rispetto all’iterazione precedente è stato introdotto un protocollo di comunicazione tra producer, di-spatcher e consumer che evitasse al didi-spatcher il compito di riconoscere se il messaggio contenesse una nota, uno strumento o un comando. Questo nuovo protocollo prevede che il producer produca due tipi di messaggi:

Message, contenente nota selezionata, strumento selezionato, ID che identifica la

nota riprodotta e comando "PLAY" o "STOP"

CarillonMessage, contenente nome della melodia, strumento con cui riprodurla,

ar-ray i cui elementi sono coppie "NotaOttava-Durata"

Entrambi questi tipi di messaggi sono inviati in una stringa scritta in formato JSON. All’inter-no del ciclo while che ascolta i messaggi del producer ogni messaggio prodotto dall’utente è loggato sul dispatcherLogger tramite la classe DispatcherEmitter, e tranne il

messag-gio iniziale prodotto dal producer (if (!input.contains("Connected!!"))) ogni messaggio è

inoltrato al consumer usando la classeConsumerEmitter.

5.4.1

DispatcherEmitter

DispatcherEmitter è una classe che implementa l’interfaccia Runnable, cioè è obbligata a

sovrascrivere il metodorun() che è il metodo eseguito all’interno di un thread. Al costruttore

sono passati come parametri una stringa contenente il messaggio da loggare e l’istanza di WebSocketServer con il websocket aperto verso il dispatcherLogger. Il thread creato

con questa classe effettua la chiamata dispatcherWSS.sendDispatchLog(data) dove la

variabiledata è un stringa ottenuta concatenando il timestamp e il messaggio passato nel

(42)

36 Dispatcher

5.4.2

ConsumerEmitter

ConsumerEmitter, come la precedente, implementa l’interfaccia Runnable. La differenza

sta nel metodorun(): il messaggio prima di essere inviato è utilizzato per creare un oggetto

di tipoJSONObject. A seconda se il messaggio da inviare contenga una singola nota

(Mes-sage) o una melodia (CarillonMes(Mes-sage) vengono fatte le chiamate rispettivametne ai meto-di consumerWSS.forwardObject(object) e consumerWSS.forwardCarillon(object). Il

primo metodo emette un evento sul websocket aperto verso il consumer con il namespa-ce "forward" mentre il secondo emette un evento con il namespanamespa-ce "carillon". Entrambi trasmettono l’oggetto di tipoJSONObject creato.

5.4.3

Comunicazione indirizzo IP al producer

Per implementare il meccanismo che comunica al producer che vuole collegarsi al dispat-cher l’indirizzo IP del dispatdispat-cher stesso è stata creata una nuova classe:IPCommunicator.

Questa classe implementa l’interfacciaRunnable e l’unica istanza creata è creata nella

clas-se principale per poter eclas-seguire il thread che si mette in ascolto su un socket aperto sulla porta 8500 con il protocollo UDP. Il protocollo UDP è necessario perché permette di riceve-re messaggi attraverso un socket che non specifica un IP: infatti si utilizza il meccanismo di broadcasting. Cioè il producer collegato alla rete invia un messaggio a tutti gli indirizzi IP sulla rete (broadcast) e l’istanza diIPCommunicator che resta in ascolto quando riceve un

messaggio controlla che arrivi da un producer e gli risponde. Grazie alla risposta del dispat-cher il producer può ottenere l’indirizzo IP con cui aprire un socket usando il protocollo TCP. A livello di codice l’esecuzione di questa classe consiste in un ciclo while che aspetta l’arrivo di messaggi: ciò avviene con la chiamata al metodosocket.receive(packetReceived). La

variabilesocket rappresenta il socket UDP che ascolta sulla porta 8500 e il metodo

invo-cato blocca l’esecuzione del programma finché un messaggio non è arrivato. Il messaggio arrivato è immagazzinato nella variabilepacketReceived di tipo DatagramPacket; questo

oggetto oltre a contenere un buffer da cui è possibile ottenere il messaggio contiene anche l’indirizzo IP della sorgente del messaggio. Il messaggio è convertito in una stringa e vie-ne fatto il controlloif (clientMessage.equals("SONO_UN_CLIENT")) per verificare che sia

stato un producer del progetto Websocket Symphony Orchestra e non un altro dispositivo connesso alla rete. A questo messaggio il dispatcher risponde con una stringa che contie-ne "SONO_IL_SERVER" ed è inviato al producer in un oggetto di tipoDatagramPacket in

modo che il producer possa ottenere l’indirizzo IP del dispatcher. Questo thread termina la sua esecuzione quando termina l’esecuzione del dispatcher.

Riferimenti

Documenti correlati

Valori assoluti a prezzi costanti (base 1995) in milioni di euro e variazioni percentuali su anno precedente, anni 2000-2003.. Agricoltura Industria in senso stretto Costruzioni

4.1 - Valore aggiunto per settore di attività economica nelle principali province italiane e in Italia, valori as- soluti a prezzi costanti (base 1995) in milioni di euro e

Valori assoluti a prezzi costanti (base 1995) in milioni di euro e variazioni percentuali su anno prece dente, anni 2000-2004. Agricoltura Industria in senso stretto Costruzioni

Galaxy Watch Active2 disponibile anche nel modello da 44 mm nel colore nero a 269.- anziché 369.-. 100.-

Wie seine Frau Alexandra Vavilina, die eine Flötenklasse am Leningrader Konservatorium leitete, es berichtet: „Seine geistige Spannweite, die Kenntnis der

Il Codice Sconto potrà essere utilizzato presso qualsiasi Punto Vendita A (mediante lettura del codice a barre) oppure sul Sito (inserendo nell’apposito spazio la versione

Per controllare lo stato del permesso di soggiorno dei cittadini extracomunitari che hanno già fatto il fotosegnalamento in questura (impronte e consegna fotografie nel

Il Progetto Interreg “TOP-Value Il valore aggiunto del Prodotto di montagna”, della durata di 3 anni, da gennaio 2017 a dicembre 2019, nasce dalla collaborazione tra la