Capitolo 3
Nei successivi paragrafi vedremo come sono state implementate le funzioni del controllore delle iscrizioni a parlare di cui abbiamo parlato alla fine del capitolo precedente.
Per prima cosa facciamo una distinzione tra il lato “moderatore”, cioè quando l'applicazione viene avviata sulla macchina il cui indirizzo IP corrisponde a quello del creatore della conferenza, e il lato “ascoltatore”, cioè il caso in cui l'applicazione sia in esecuzione sulla macchina di un generico hosts. La modalità “moderatore” prevede un set di funzioni che sono diverse da quelle dell' “ascoltatore”. Illustremo anche gli algoritmi che vengono seguiti alla ricezione di tali messaggi.
In secondo luogo distinguiamo i tipi di messaggi che gli ascoltatori (ossia i generici membri della conferenza) scambiano con il moderatore. Il primo tipo di messagi si basa su una comunicazione unicast dall'host verso il moderatore; tale comunicazione verrà denominata in seguito “colloquio diretto”. La seconda categoria invece interessa l'intero gruppo multicast e la chiameremo “colloquio di gruppo”.
3.1) Il Moderatore
Il creatore della conferenza avvia l'applicazione sulla macchina il cui indirizzo IP è stato reso noto tramite l'annuncio di sessione su SDR; quindi egli diventa il moderatore della conferenza. La sua applicazione deve gestire due tipi di messaggi:
quelli appartenenti al “colloquio diretto” e quelli del “colloquio di gruppo”.
I primi avvengono tramite l'impiego di una connessione TCP tra il generico membro e il moderatore; i secondi invece sono mandati in multicast e possono essere ricevuti da tutti.
All'avvio del programma il moderatore apre due sockets, uno gestito in modalità server (figura 1) su una determinata porta, l'altro invece è un socket multicast gestito in modalità sequenziale. L' apertura e la gestione dei due sockets è stata implementata in maniera differente; per quanto riguarda il primo ci siamo avvalsi di comandi definiti nel linguaggio Tcl/Tk che in modo abbastanza immediato permettono la creazione di tale socket, sia dalla parte del server (caso moderatore) che dalla parte del client (lato ascoltatore). Per quanto riguarda la crezione del socket multicast ci siamo avvalsi del linguaggio C. La gestione di tale interfaccia di rete è naturalmente asincrona, come del resto quella del server parallelo, ma dal momento che i socket multicast non erano ancora stati implementati in Tcl/Tk, abbiamo dapprima scritto il codice in linguaggio C e in seguito abbiamo arricchito l'interprete Tcl con dei nuovi comandi relativi all'invio e alla ricezione asincrona di questi ultimi: al socket è stato associato un filehandler Tcl che, ogni qual volta il socket è leggibile, invoca una procedura di callback per la gestione dei pacchetti immagazinatti nel buffer del socket stesso. Tale procedura è la seguente: Tcl_CreateFileHandler(fd, mask, proc, clientData). Il primo argomento è l'identificativo del socket, mentre il campo proc specifica la procedura di callback da invocareogni qualvolta si riceve un pacchetto sul socket.
Per la sua intima connessione con il C è infatti possibile scrivere delle procedure in C ed eseguirle come se fossero dei comandi Tcl. La caratteristica di Tcl consiste nel fatto che l'interprete può essere arricchito continuamente. Si riporta a titolo esplicativo il seguente comando che viene chiamato al momento dell'inizializzazione dell'interprete
Questa procedura crea dei veri e propri comandi Tcl la cui esecuzione prevede naturalmente l'esecuzione della procedura con cui abbiamo arricchito l'interprete.
Il primo argomento da passare a TclCreateCommand è il nome della procedura C che noi vogliamo venga eseguita. Il secondo campo invece è il nome del comando dell'interprete arricchito che vogliamo associare alla procedura.I messaggi del primo tipo sono scambiati su una connessione TCP fra il moderatore e l'ascoltatore. Il modello di comunicazione è quello client-server. Il moderatore si mette in ascolto su una determinata porta e aspetta le richieste di connessione da parte dei vari membri della conferenza. Abbiamo scelto questo tipo di comunicazione per le fasi di registrazione della domanda, di addio e di cancellazione in quanto è molto più affidabile di una comunicazione via UDP, la quale avrebbe dovuto prevedere anche una parte di avvenuta ricezione nei confronti di chi ha inoltrato la domanda al moderatore. Qui di seguito riportiamo uno schema del modello classico di client-server anche se non è illustrato il fatto che si possono gestire più connessioni contemporaneamente tramite la creazione di processi “child” che si occupano delle singole connessioni. Nella figura della pagina successiva
è mostrato un modelo clien-sever sequenziale
3.1.1) Messaggi del primo tipo
I messaggi del primo tipo che riceve il moderatore sono quelli di registrazione, di cancellazione e di addio alla conferenza. Un partecipante invia la propria domanda con un numero d'ordine progressivo (Ns_List) il quale permette al moderatore di distinguere fra le differenti domande di una stessa persona in caso quest'ultimo ne voglia cancellare qualcuna. Il formato dei messaggi è di tipo testuale. Per la registrazione il formato è quello di figura 2:
|
SUBMIT |
NAME |
NS_LIST |
QUESTION |
figura 2: formato del mesaggio di registrazione
La cancellazione di una domanda dalla lista di quelle ricevute dal moderatore è possibile, da parte di chi l'ha precedentemente posta, grazie al numero progressivo che le è stato associato; Ns_List permette infatti di distinguere le differenti domande di una stessa persona. Se la domanda non è stata ancora approvata viene proprio cancellata dalla lista; se invece è stata approvata e quindi già mandata in multicast al successivo aggiornamento assumerà lo stato di EVASA. Il formato della richiesta di cancellazione è illustrato qui di seguito.
|
EVASA |
NAME |
NS_LIST |
figura 3: formato del messaggio di evasa
L'ultimo messagio scambiato nel “colloquio diretto” è l'addio di un membro alla conferenza. Il fatto che se ne vada deve essere notificato al moderatore perché questi potrebbe già averne approvato delle domande e inviato al gruppo gli aggiornamenti. Le domande del membro che se ne va e che non sono state approvate vengono cancellate; quelle già approvate saranno riaggiornate con lo stato di EVASA. Il formato della notifica di addio è mostrato nella pagina seguente.
|
BYE |
NAME |
figura 4: formato del messaggio di addio
3.1.2) Messaggi del secondo tipo
I messaggi del secondo tipo sono ricevuti ed inviati in muticast all'intero gruppo di partecipanti. Il primo campo di questi messaggi specifica sempre il tipo del messaggio: per esempio una notifica di aggiornamento avrà nell'intestazione la parola di UPDATE. Il moderatore invia in multicast il messaggio di presenza, gli aggiornamenti delle domande via via approvate e la notifica di onAir. Riceve invece dal gruppo multicast richieste di ritrasmissione di domande già approvate.
Il messaggio di presenza è necessario per notificare ai membri che il moderatore è presente: se il moderatore non approvasse domande per un periodo di tempo abbastanza lungo gli ascoltatori potrebbero ritenerlo assente e lasciare la conferenza. Tale messagio di presenza non è altro che un pacchetto mandato in multicast alla cui ricezione comincia a lampeggiare una spia nell'interfaccia dell'ascoltatore.
Gli aggiornamenti verrano meglio esaminati in seguito quando verranno proposti gli algoritmi della sequenza di azioni che vengono svolte alla ricezione di un mesaggio. Per ora diciamo che negli aggiornamenti delle domande approvate sono contenuti i dati di chi ha posto la domanda, la domanda stessa e Ns_M, ovvero il numero di sequenza di approvazione. Ad ogni domanda che viene approvata viene associato un numero progressivo, Ns_M appunto, il quale permette di identificare in modo unico tale domanda l'interno della sessione. Conoscendo tale numero l'ascoltatore può richiedere la ritrasmissione di quella domanda oppure quando la domanda viene evasa il moderatore lo comunica al gruppo facendo riferimento proprio a Ns_M. Vi è inoltre un campo in cui si riporta il numero di sequenza dell'ascoltatore Ns_List; questo serve al generico partecipante per ricordarsi quali dei suoi interventi sono stati approvati. Anche se non ci sono nuove domande approvate il moderatore, in modo casuale emette, il segnale di presenza e contemporaneamente l'ultima domanda che è stata approvata. In questo modo si facilita il ricevitore che può immediatamente rendersi conto delle domande che sono andate perdute.
Qui di seguito riportiamo il formato dei messaggi di aggiornamento.
|
UPDATE |
NAME |
NS_LIST |
QUESTION |
NS_M |
figura 5: formato del messaggio di Update
Le notifiche di onAir sono anch'esse inviate in multicast e permettono ai partecipanti di sapere a chi è stata data la parola in quel momento. La ricezione di tale messagio fa lampeggiare sulla lista delle domande dell'ascoltatore la riga corrispondente alla domanda e alla persona a cui il moderatore sta in quel momento rispondendo. Si ricorda infatti che il moderatore da' la parola ad un generico membro tramite la connessione TCP fornita da Confcontrol e quindi il resto del gruppo non può sapere chi sta parlando se non riceve notifica da parte del moderatore. Formato del messagio di notifica di onAir qui di seguito.
|
ONAIR |
NAME |
NS_LIST |
figura 6: formato del messaggio di On_Air
Per quanto riguarda i messaggi del secondo tipo abbiamo già detto che non avvengono in modo sincrono ne tantomeno deterministico. Il fatto di casualizzare le azioni del moderatore e dei partecipanti è funzionale al fine di non creare situazioni di implosione di messaggi da o verso il moderatore. Ad esempio (lo vedremo meglio in seguito) i messaggi di presenza e gli aggiornamenti non sono continui e cadenzati, ma sono emessi ad ogni heartbeat, il quale non avviene sempre allo scadere di uno stesso intervallo temporale bensì in modo del tutto aleatorio. L'aleatorietà non è assoluta perchè la sequenza casuale si calcola a partire da un “seme”.
3.2) L'ascoltatore
Chi vuole partecipare alla conferenza avvia l'applicazione la quale, rendendosi conto che l'indirizzo IP della macchina su cui è in esecuzione differisce da quello del creatore della conferenza, parte in modalità “ascoltatore”.
Anche in questo caso abbiamo due tipi di messaggi a seconda di come vengono inoltrati. L'ascoltatore apre solo il socket multicast che gli serve per chiedere le ritrasmisioni e ricevere gli aggiornamenti, mentre per registrarsi presso il moderatore, per ritirare una domanda fatta o per notificare il suo addio alla conferenza si connette di volta in volta in modalità client con il moderatore.
Non analizzeremo i messaggi del primo tipo in quanto li abbiamo già illustrati nella sezione del moderatore; per i messaggi del secondo tipo il disorso è diverso perchè essi implicano una cooperazione frai vari membri della conferenza.
I messagi di aggiornamento che sono ricevuti possiedono un numero d'ordine Ns_M che permette di conoscere l'ordine di approvazione di una domanda ma anche di rendersi conto se ci sono state delle perdite di pacchetti di aggiornamento. Infatti se viene ricevuta una domanda con Ns_M pari a 4 e successivamente si riceve un'altra domanda con Ns_M pari a 7 è banale rendersi conto che ci siamo persi le domande 5 e 6. Bisogna quindi chiedere la ritrasmissione di tali domande; tale richiesta non avviene all'istante (vedi paragrafi successivi) ma si aspetta un certo intervallo di tempo prima d'inviarla. Infatti può accadere che qualcun altro voglia fare la mia stessa richiesta e sarebbe uno spreco inutile di banda se molti membri chiedessero le stesse cose; ecco quindi che, durante l'intervallo di attesa prima di inoltrare la richiesta, potrei accorgermi che un altro ha già fatto la mia stessa richiesta e a questo punto è inutile che io invii la mia. Il formato delle richieste di ritrasmissione è il seguente
|
REFRESH |
NAME |
NS_M |
figura 7: formato della riciesta di ritrasmissione
Il campo NAME contiene l'indirizzo di colui che ha emesso la richiesta mentre il numero successivo è Ns_M, ossia l'identificativo unico della domanda all'interno del gruppo.
Il campo IP si è reso necessario in quanto trattandosi di pacchetti multicast essi sono ricevuti anche da chi li ha mandati e quindi un partecipante considererebbe come altrui richieste di ritrasmissione che invece egli stesso ha inviato. Benchè tale algoritmo sarà analizzato più avanti diciamo subito che ci siamo ispirati alla filosofia SRM illustrata nel capitolo precedente.
3.3) Sequenza di operazioni
Illustreremo ora la sequenza di operazioni che si verificano alla ricezione dei messaggi.
Prima di fare ciò vorrei però ricapitolare i messaggi che vengono scambiati e vedere quali sono le strutture dati impiegate dal moderatore e dal'ascoltatore per l'immagazzinamento delle informazioni. Il moderatore tiene le informazioni relative alle domande ricevute in due listbox, che sono delle liste a scorrimento del linguaggio Tcl/Tk le quali sono accessibili tramite l'indice dela posizione in cui la stringa si trova. Il moderatore possiede due di queste listbox: una per le domande in attesa di approvazione, l'altra per le domande approvate.
Lo stato di una domanda può essere di tre tipi:
1) GIACENTE
2) APPROVATA
3) ON_AIR
4) EVASA
Lo stato di GIACENTE è individuato dalla appartenenza o meno alla prima listbox.
Lo stato di APPROVATA ce l'hanno le domande che si trovano nella seconda listbox e che, nell'ultimo campo della stringa che le individua all'interno della listbox, hanno la parola “ok”.
Lo stato di ON_AIR è proprio della domanda a cui stiamo rispondendo in quel preciso momento.
Lo stato di EVASA è sempre illustrato nell'ultimo campo della listbox in cui compare la parola “EVASA”
Ogni richiesta d'intervento viene associata ad un array Tcl: tale tipo di array è accessibile attraverso una o più chiavi. Abbiamo pensato che le chiavi potessero essere il nome del partecipante e il numero di sequenza che quest'ultimo dà alle proprie domande. Di conseguenza abbiamo ottenuto un array unico per il moderatore al quale accedere tramite queste due informazioni che sono reperibili andando a leggere la listbox in tale array abiamo inserito il testo delle domande che vengono poste. Facciamo un esempio: voglio sapere cosa ha chiesto il partecipante di nome “pippo” nella sua seconda richiesta d'intervento. Mi posiziono con il cursore sopra la riga relativa della listbox, clicco due volte con il mouse, viene eseguita la procedura visualizza crea una finestra di dialogo nella quale è mostrato il testo della domanda e il suo stato.
Per rendere più agevole la visualizzazione di questi pannelli in cui vengono visualizzate informazioni relative alla richiesta d'intervento abbiamo anche utilizzato delle liste, il cui nome richiama lo stato delle domande i cui numeri di sequenza sono contenuti nelle liste stesse. Per esempio per sapere immediatamente quante sono le domande evase non devo andare a leggere una per una tutte le righe della listbox, ma è sufficiente che guardi il contenuto di ListaEvase e automaticamente conosco i numeri di sequenza delle domande evase.
L'ascoltatore possiede una struttura dati molto simile ma ciò che lo caratterizza sono delle liste che contengono i numeri di sequenza delle domande approvate, perdute, evase e di cui bisogna richiedere la ritrasmissione. Il nome di tale liste richiama da vicino le domande a cui si riferiscono. Tali lista risultano molto utili perchè contengono l'identificativo unico della domanda: il numero di sequenza del moderatore.
3.3.1) Sequenza di operazioni del moderatore
Ricapitoliamo i messaggi che interessano il moderatore:
|
Messaggi ricevuti |
|
|
UDP |
TCP |
|
Refresh |
//// |
|
Evasa |
|
|
On_Air |
|
|
Blink |
|
figura 10
In trasmissione si utilizza esclusivamente il socket multicast. Non si hanno comunicazioni TCP.
|
Messaggi trasmessi |
|
|
UDP |
TCP |
|
Update |
Submit |
|
|
Cancel |
|
|
Bye |
figura 11
Fra i messaggi ricevuti prevalgono quelli ricevuti sulla connessione TCP; essi naturalmente provengono dall'ascoltatore.
Ma cosa succede alla ricezione di tali messaggi da parte del moderatore? Per rispondere a questa domanda dobbiamo prima vedere cos'è la procedura di heartbeat di cui più volte abbiamo parlato nel corso di questo lavoro.
L'heartbeat è il cuore delle azioni del moderatore: questa procedura viene eseguita ciclicamente (non in modo periodico!!!): si carica un timer, si aspetta che scada, si esegue la procedura, si ricarica il timer. Quando si verifica l'heartbeat il moderatore controlla se ha domande approvate da inviare, se deve inviare messaggi di OnAir o messaggi di presenza. Il timer, ricaricato ogni volta che viene eseguita la procedura di heartbeat, non assume un valore deterministicamente stabilito bensì è frutto di una sequenza casuale: per rendere la cosa ancora più aleatoria il seme da cui la macchina genera la sequenza è ottenuto da una combinazione di indirizzo IP e ora locale.
La gestione di tale timer è ottenuta tramite il comando after del Tcl/Tk il quale aspetta un numero di secondi passati come argomento prima di eseguire la procedura di heartbeat, anch'essa passata come argomento.
La sintassi di tale comando è la seguente: after timer ?procedura?
Nelle pagine successive vedremo i diversi algoritmi utilizzati per la risposta ai messaggi ricevuti. In tutti i diagrammi che seguiranno ci sarà una linea verticale tratteggiata sulla sinistra che rappresenta lo scorrere del timer. Alla sinistra del timer abbiamo le azioni che fanno scattare una sequenza di operazioni. Tali azioni possono essere sia dei messaggi ricevuti sul socket sia delle azioni intraprese dal moderatore stesso quale l'approvazione di una domanda.

Vediamo
ora le azioni che portano all'emissione dei pacchetti di Update
dopo
figura 12 : procedura di heartbeat
che una domanda è stata approvata. Si ricorda che la linea tratteggiata sula sinistra rapresente il timer allo scadere del quale si inviano gli aggiornamenti:

figura 13: procedura di aggiornamento
Come detto in precedenza il generico membro della conferenza può ritirare le proprie domande; il ririro di tale domanda avviene tramite l'apertura di un canale TCP tra il membro e il moderatore. Alla ricezione di un messaggio di cancellazione il moderatore ricerca la domanda che il partecipante vuole cancellare e la individua grazie al numero d'ordine con cui era stata inviata: ad esempio il membro di nome “pippo” vuole ritirare la sua terza domanda; i moderatore riceve tale comunicazione e ricerca con le due chiavi, nome e Ns_List, la domanda da cancellare all'interno delle sue strutture dati.
La cancellazione vera e propria come illustrato nel grafico sottostante avvienese la domanda non è ancora stata approvata; in caso contrario si cambia lo stato della domanda in EVASA e si invia il relativo aggiornamento

figura 14:
cancellazione di un intervento
Dopo l'evasione di una domanda non si aspetta il successivo heartbeat per inviare la domanda con lo stato di EVASA.
L'ultima procedura di cui analizzeremo l'algoritmo è quella di On_Air, ovvero l'aggiornamento all'interno del gruppo su chi sta parlando in quel momento. Alla procedura di On_Air segue sempre quella di evasione, in quanto una volta che il partecipante ha terminato il suo intervento bisogna notificare agli altri partecipanti che chi stava parlando ha terminato. Il messaggio di On_Air continua ad andare in onda finché la variabile OnAir continua ad avere un valore diverso da -1. Ad ogni damanda approvata è associata una finestra di dialogo, mostrata nelle pagine precedenti, la quale ci permette di dare la parola ad un partecipante; questo scopo viene raggiunto tramite l'impiego di Confcontrol, il tool di cui abbiamo parlato nel primo capitolo. Cliccando sul bottone che permette di dare la parola, si genera uno script Tcl che, tramite l'ausilio del comando send, viene inviato a Confcontrol (deve essere in esecuzione in quel momento).

figura 15: procedura di On_Air
3.3.2) Sequenza di operazioni dell'ascoltatore
Anche qui come all'inizio del paragrafo precedente ricapitoliamo i tipi di messaggi che interessano l'applicazione avviata in modalità ascoltatore
|
Messaggi trasmessi |
|
|
UDP |
TCP |
|
Refresh |
Submit |
|
|
Cancel |
|
|
Bye |
figura 16
|
Messaggi ricevuti |
|
|
UDP |
TCP |
|
Update |
//// |
|
Refresh |
|
|
Evasa |
|
|
OnAir |
|
|
Blink |
|
figura 17
Si vede dalle tabelle qui sopra riportate che il generico ascoltatore emette e riceve messagi di Refresh; il socket per la connessione TCP è esclusivamente adoperato per la comunicazione con il modereatore per la registrazione, la cancellazione di una domanda o per notificare l'addio alla conferenza. Abbiamo scelto questo protocollo per le fasi suddette in quanto è un protocollo affidabile; in un primo tempo avevamo pensato di usare UDP, ma renderlo affidabile, in modo da notificare al partecipante l' avvenuta ricezione della sua registrazione, è risultato alquanto complesso.

figura 18: procedura di
refresh
Nelle pagine seguenti mostreremo la sequenza di azioni che vengono intraprese alla ricezione di tali messaggi e chiariremo il procedimento di soppressione della richiesta; avevamo parlato nei capitoli precedenti del fatto che, all'interno di un gruppo multicast è inutile chiedere una ritrasmissione se l'ha già fatto qualcun altro.
Qualche commento merita la procedura di verifica alla ricezione di un aggiornamento da parte dell'ascoltatore. Si possono verificare due situazioni:
ricezione di un aggiornamento di una domanda già ricevuta;
ricezione di una domanda con numero di sequenza superiore all'ultima ricevuta, ma non immediatamente successiva: ad esempio l'ultima domanda ricevuta è la numero 3 e al successivo aggiornamento ricevo la numero 6.
L'ascoltatore possiede una lista contenente i numeri delle domande ricevute e lo stato di tali domande, quindi alla ricezione di un aggiornamento controlla se la domanda ivi contenuta è già stata ricevuta; l'aggiornamento avviene esclusivamente nel caso in cui la domanda non sia ancora stata ricevuta con lo stato di EVASA.
Per quanto riguarda il numero di sequenza, se questo non è consecutivo all'ultimo ricevuto si mettono nel buffer delle richieste di ritrasmissione tutti i numeri di sequenza mancanti.
3.4) Interfaccia socket
In quest'ultimo paragrafo illustreremo come abbiamo implementato l'interfaccia socket sulla quale si inviano e ricevono messaggi e come è avvenuto il dumping dei messaggi ricevuti e trasmessi.
Per l'apertura del socket multicast abbiamo dovuto arricchire l'interprete Tcl con delle procedure in linguaggio C. Tali procedure di apertura e gestione del socket hanno seguito le linee guida contenute in . Il socket viene aperto all'avvio del programma sulla porta 1500, ma volendo si possono cambiare i parametri per ricevere e trasmettere messaggi su di un altra porta. Al socket come abbiamo detto è associato un filehandler Tcl il quale chiama l'applicazione di callback ogni qualvolta arrivano pacchetti sul socket stesso. Il trattamento delle informazioni contenute nel payload del protocollo UDP viene trattato da quella che possiamo definire l'unità di controllo. Quest'ultima è una procedura in linguaggio C che estrae dal buffer di ricezione il primo campo del payload UDP, quello che contiene il tipo di messaggio (vedi figura 19) e lo invia all'interprete Tcl: il tipo di messaggio ha un nome identico alla procedura che deve essere eseguita alla sua ricezione e quindi inviando all'interprete Tcl il tipo di messaggio ricevuto più i restanti campi come argomenti si ottiene l'esecuzione immediata della procedura relativa. Ad esempio alla ricezione di un messaggio di Update viene invocata con le modalità sopra descritte la procedura di Update.
|
TIPO-MESSAGGIO |
ARG1 |
ARG2 |
.... |
ARGN |
figura 19: formato del generico messaggio
Qui di seguito riportiamo cinque listati: il primo si riferisce all'apertura dell'interfaccia socket, sia in ricezione che in trasmissione.
Il secondo illustra l'associazione del filehandler Tcl al socket stesso;
il terzo si riferisce all'unità di controllo sopra descritta: si noterà che in quest'ultima la gestione di tali messaggi dipende dal ruolo dell'applicazione sul cui socket arriva il messaggio stesso: è per questo che il comando da invocare o meno dipende anche dalla seconda condizione imposta all'inizio di ogni ciclo if, essendo la variabile moderator pari a uno quando trattasi del moderatore, pari a zero altrimenti;
il quarto listato contiene un esempio di procedura per linvio del messaggio di Update,presenza (Blink) e OnAir;
il quinto listato contiene il main del programma C nel quale viene inizializzato l'interprete con le nuove funzioni implementate in linguaggio C.
Listato n. 1
.......................................................................
#include "Prova.h"
#include <tk.h>
#define SERVER_PORT 1500
#define MAX_MSG 500
extern Tcl_Interp *myinterp;
extern void linksocket(int fd, int mask, Tcl_FileProc *callback);
int rsock,ssock;
struct sockaddr_in sin;
/* la seguente funzione chiamata da Tcl apre il socket multicast su
cui si ricevono i messaggi di aggiornamento della lista della
domande approvate dal moderatore e i messaggi di refresh*/
int Make_Socket(ClientData data,Tcl_Interp *interp)
{
int n,cliLen;
struct sockaddr_in cliAddr;
cliLen=sizeof(cliAddr);
rsock = make_receiver();
if(rsock>0)
ssock = make_sender();
return TCL_OK;
}
int make_receiver()
{
int s;
int on=1;
struct in_addr mcastAddr;
struct ip_mreq mr;
h=gethostbyname(indirizzo);
memcpy(&mcastAddr, h->h_addr_list[0],h->h_length);
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0)
{
printf ("confcntlr: socket failed for confbus receiver socket\n");
return s;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))
< 0)
{
printf ("confcntlr: SO_REUSEADDR failed for confbus receiver socket\n");
close(s);
return (-1);
}
memset((char *)&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERVER_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
printf("confcntlr: bind failed for confbus receiver socket\n");
close(s);
return (-1);
}
nonblock(s);
if (s == -1)
{ printf("chiudo tutto");
close(s);
return s;
}
mr.imr_multiaddr.s_addr = mcastAddr.s_addr;
mr.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(s, IPPROTO_IP,IP_ADD_MEMBERSHIP,
(void *)&mr,sizeof(mr)) < 0)
{
printf("confcntlr: setsockopt (confbus): IP_ADD_MEMBERSHIP\n");
close(s);
return (-1);
}
linksocket(s, 1, dispatch); /*vedi commento successivo*/
return s;
}
/*la funzione linksocket, che sta in iohandler, contiene un file handler
Tcl che invoca dispatch non appena ci sono dati daa leggere sul
socket*/
int make_sender()
{
int s;
long addr;
struct sockaddr_in McAddr;
memset((char *)&McAddr, 0, sizeof(McAddr));
h=gethostbyname(indirizzo);
memcpy((char *) &McAddr.sin_addr,h->h_addr_list[0] ,h->h_length);
McAddr.sin_family =h->h_addrtype;
McAddr.sin_port = htons(SERVER_PORT);
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0)
{
printf("confcntlr: confbus sender socket failed");
return (-1);
}
sin.sin_port = 0;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
printf("confcntlr: bind failed for confbus sender socket");
close(s);
return -1;
}
sin.sin_port = htons(SERVER_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if (connect(s, (struct sockaddr*)&McAddr, sizeof(McAddr)) < 0)
{
printf("confcntlr: connect failed for confbus sender socket");
close(s);
exit(-1);
}
if (setsockopt(s, IPPROTO_IP,
IP_MULTICAST_TTL,(char*)&ttl,sizeof(ttl)) < 0)
{
perror("confcntlr: IP_MULTICAST_TTL failed for confbus sender");
close(s);
exit (-1);
}
return s;
}
/*la seguente funzione setta il socket multicast in maniera non
bloccante*/
void nonblock(int s)
{
u_long flag = 1;
int flags;
flags = fcntl(s, F_GETFL, 0);
flags |= O_NONBLOCK|O_NDELAY;
if (fcntl(s, F_SETFL, flags) < 0)
{
perror("fcntl for confbus");
rsock = -1;
}
}
/* la seguente funzione determina l'indirizzo IP e il nome della
macchina su cui l'applicazione è in esecuzione*/
void getlocalname(char *host)
{
char hostname[256];
gethostname(hostname, 256);
strcpy(host, hostname);
}
.......................................................................
fine listato n.1
Listato n .2
.......................................................................
/*la seguente funzione legge i messaggi che arrivano sul socket
multicast e li passa a Unità _di_controllo ne identifica il tipo
e invoca le procedure erelative*/
void dispatch(ClientData fd, int mask)
{
int cc;
char buff[100];
cc = recv((int)fd, buff, sizeof(buff), 0);
if (cc < 0)
{
perror("confbus recv");
exit(-1);
}
Unita_di_controllo((char *)buff);
}
int Unita_di_controllo(char *buff)
{
char cmd[10];
int i, j,n;
for (i=0,j=0; buff[i] != ' '; i++,j++)
cmd[j] = buff[i];
cmd[j] = '\0';
if (!(strcmp(cmd, "Blink")) && !(moderatore)) {
/* questo refresh è di
competenza del listener */
Tcl_VarEval(myinterp,"Blink ",(char *)NULL,(char *)NULL);
}
if (!(strcmp(cmd, "Update")) && !(moderatore)) {
Tcl_VarEval(myinterp,"Update ",(buff+strlen(cmd)+1),(char *)NULL);
}
if (!(strcmp(cmd, "Evasa")) && !(moderatore)) {
Tcl_VarEval(myinterp,"Evasa ",(buff+strlen(cmd)+1),(char *)NULL);
}
if (!(strcmp(cmd, "Refresh"))) {
/* questo refresh è di competenza
del moderatore */
Tcl_VarEval(myinterp,"Refresh ",(buff+strlen(cmd)+1),
(char *)NULL);
}
if (!(strcmp(cmd, "ON_AIR")) && !(moderatore)) {
/* questo refresh è di competenza
del moderatore */
Tcl_VarEval(myinterp,"ON_AIR ",(buff+strlen(cmd)+1),
(char *)NULL);
}
return TCL_OK;
}
/* la seguente funzione chiamata da Tcl crea setta le variabili chair
(indirizzo del moderatore), Mioaddress ( l'indirizzo della macchina
su cui è in esecuzione il programma), e Myname (nome della macchina
su cui è in escuzione il prog)*/
int chisono (Tcl_Interp *interp)
{
struct in_addr myaddr;
char host[256];
extern struct hostent *h;
char* var;
char var2;
getlocalname(host);
h = gethostbyname(host);
memcpy(&myaddr, h->h_addr_list[0],h->h_length);
strcpy(mioaddress,inet_ntoa(myaddr));
strcpy(myname,h->h_name);
var = Tcl_SetVar(interp, "Mioaddress", (char*)
&mioaddress,TCL_GLOBAL_ONLY);
var = Tcl_SetVar(interp, "Myname", (char*) &myname,TCL_GLOBAL_ONLY);
var = Tcl_SetVar(interp, "floorholder", (char*)
&chair,TCL_GLOBAL_ONLY);
}
.......................................................................
fine listato n.2
Listato n.3
......................................................................
/*La seguente funzione (chiamata da Tcl) è utilizzata dal Moderatore
gli argomenti che le vengono passati vanno a comporre il messaggio
che è inviato. Formato deel messaggio:
nome Ns_List question Ns_Mod stato */
int update_tx (ClientData data,Tcl_Interp *interp,int argc,char** argv)
{
char messaggio[100];
char msg[ ] = "Update";
int i;
sprintf(messaggio,"%s %s %s %s %s %s",msg,argv[1],
argv[2],argv[3],argv[4],argv[5]);
send(ssock,(char *)messaggio, sizeof(messaggio), 0);
return TCL_OK;
}
int blink_tx (ClientData data,Tcl_Interp *interp,int argc,char** argv)
{
char msg[ ] = "Blink";
char messaggio[100];
sprintf(messaggio,"%s %s",msg,argv[1]);
send(ssock,(char *)messaggio, sizeof(messaggio), 0);
return TCL_OK;
}
int onair_tx (ClientData data,Tcl_Interp *interp,int argc,char** argv)
{
char msg[ ] = "ON_AIR";
char messaggio[100];
sprintf(messaggio,"%s %s %s",msg, argv[1],argv[2]);
send(ssock,(char *)messaggio, sizeof(messaggio), 0);
return TCL_OK;
}
.......................................................................
fine listato n.3
Listato n.4
.......................................................................
void linksocket(int fd, int mask, Tcl_FileProc* callback)
{
switch (mask)
{
case 8:
case 16:
case 1: mask = TK_READABLE;
beak;
case 2:
mask = TK_WRITABLE;
break;
}
/* Line below only works for Tcl7.5, 7.6 */
/* Tcl_CreateFileHandler(Tcl_GetFile((ClientData)fd, TCL_UNIX_FD),
mask, callback, (ClientData)fd); */
Tcl_CreateFileHandler(fd, mask, callback, (ClientData)fd);
}
......................................................................
fine listato n.3
Listato n. 5
.......................................................................
#include "Prova.h"
#include <math.h>
#include <tcl.h>
#include <tk.h>
#include <tclExtend.h>
int Make_Socket(ClientData data,Tcl_Interp *interp);
int make_receiver();
int make_sender();
void nonblock(int s);
void getlocalname(char *host);
void dispatch(ClientData fd, int mask);
int Unita_di_controllo(char *buff);
int chisono (Tcl_Interp *interp);
int refresh_tx (ClientData data, Tcl_Interp *interp, int argc,
char** argv); /*Funzioni del listener*/
int update_tx (ClientData data, Tcl_Interp *interp, int argc,
char** argv);
int blink_tx (ClientData data, Tcl_Interp *interp, int argc,
char **argv);
int onair_tx (ClientData data, Tcl_Interp *interp, int argc,
char **argv);
extern int TkPlatformInit(Tcl_Interp *interp);
static Tk_Window mainWindow;
Tcl_Interp *myinterp;
int
main(argc, argv)
int argc; /* Numero di argomenti da linea di comando */
char **argv;
{
if (argc != 4)
{
fprintf(stderr,
"deve essere %s <mcastgroup> <ttl> <moderatore>\n",argv[0]);
exit(-1);
}
strcpy(prog,argv[0]);
h = gethostbyname(argv[1]);
if (h==NULL) {
printf("Server : gruppo sconosciuto'%s'\n",argv[1]);
exit(-1);
}
strcpy(indirizzo,argv[1]); /* canale degli annunci e
dell'heartbeat */
/* ttl = argv[2];*/
sscanf (argv[2], "%u", &ttl);
strcpy(chair,argv[3]); /* IP del moderatore */
myinterp = Tcl_CreateInterp();
/* Aggiunge all'interprete i comandi makesocket, refresh_tx,
updadte_tx; li primo apre il socket multicast,
il secondo invia un messaggio di refresh del listener in
multicast, il terzo, usato dal moderatore, manda in multicast
un messasggio di aggiornamento */
Tcl_AppInit(myinterp);
Tk_MainLoop();
return 0;
}
int
Tcl_AppInit(interp)
Tcl_Interp *interp;
{
int n,b;
n = Tcl_Init(interp);
b = Tclx_Init(interp);
if (Tk_Init(interp) != TCL_OK) {
fprintf(stderr, "Tk_Init failed: %s\n", interp->result);
exit(1);
}
mainWindow = Tk_MainWindow(interp);
Tcl_CreateCommand(interp,"makesocket",Make_Socket,
(ClientData)NULL,(Tcl_CmdDeleteProc *)NULL);
Tcl_CreateCommand(interp,"Refresh_Tx",refresh_tx,
(ClientData)NULL,(Tcl_CmdDeleteProc *)NULL);
Tcl_CreateCommand(interp,"Update_Tx",update_tx,
(ClientData)NULL,(Tcl_CmdDeleteProc *)NULL);
Tcl_CreateCommand(interp,"Blink_Tx",blink_tx,
(ClientData)NULL,(Tcl_CmdDeleteProc *)NULL);
Tcl_CreateCommand(interp,"Onair_Tx",onair_tx,
(ClientData)NULL,(Tcl_CmdDeleteProc *)NULL);
chisono (interp);
if (!(strcmp(mioaddress,chair))) {
moderatore = 1;}
else
{moderatore = 0; }
Tcl_EvalFile(interp, "libs.tcl");
if (moderatore == 1) {
Tcl_EvalFile(interp, "ctrlmenu_Mod.tcl");
} else
{Tcl_EvalFile(interp, "ctrlmenu_Lis.tcl");}
return TCL_OK;
}
.......................................................................
fine listato n.5
3.5) Dumping dei messaggi e simulazione di “packet loss”
3.5.1) Ethereal
Un altro aspetto importante del lavoro consiste nel dumping dei messaggi che vengono inviati e ricevuti all'interno del gruppo multicast. Per fare ciò abbiamo utilizzato Etherreal, ovvero uno sniffer che permette di visualizzare il tipo di traffico con i vincoli imposti dai filtri configurati dall'utente.
Lo sniffer consente di leggere i campi contenuti in ogni pacchetto; si possono visualizzare i campi relativi ai vari strati: in questo modo oltre a controllare se ad esempio il TTL è stato configurato in modo corretto (strato IP) si possono leggere le porte sulle quali il pacchetto viene ricevuto (strato del protocollo UDP) e i vari campi del payload UDP che contengono le informazioni di controllo e gestione per l'applicazione descritta in questo lavoro.

figura n.20: display di visualizazione dei pacchetti
catturati dallo sniffer
I filtri dello sniffer da noi utilizzati prevedevano la cattura dei pacchetti multicast in arrivo e in partenza da un determinato host oppure i pacchetti multicast emessi e ricevuti da due host. Nelle figure successive riportiamo per completezza la maschera del filtro di cattura e la lista che mostra il risultato di un dumping effettuato su traffico multicast; nella parte inferiore della lista di cattura sono visualizzati i campi del pacchetto selezionato nella lista sovrastante. Come si vede nel payload UDP è contenuto il messaggio di Update in formato testuale. Un esame più attento del payload del pacchetto UDP rivela come gran parte dello spazio presente non venga utilizzato; il messaggio occupa poche righe mentre quelle inutilizzate sono molte. Una successiva miglioria tecnica potrebbe quindi riguardare la gestione più efficiente del numero di byte che vanno a comporre il campo dati del pachetto UDP.
figura 21: interfaccia per la definizione del filtro di cattura; come si può vedere dall'immagine si possono combinare logicamente diversi scenari di cattura. Nell'esempio mostrato abiamo messo in “and” logico l'host sulla cui interfaccia compieremo il dumping e il tipo di pacchetti che vogliamo catturare
Come illustrato nella figura n. 20 oltre al tipo di pacchetto che si vuole catturare è possibile visualizzare i pacchetti catturati con diverse modalità: queste sono visibili nella parte inferiore della figura
3.5.2) Simulazione di videoconferenza con perdita di pacchetti (packet loss)
Per testare l'efficienza degli algoritmi implementati per l'aggiornamento e il recupero dei pacchetti eventualmente persi durante una sessione MBone abbiamo incontrato diverse difficoltà. Essendo praticamente impossibile testare l'applicazione in un contesto reale, che prevedeva la presenza di numerosi membri sparsi per il globo i quali avrebbero dovuto riferire le statistiche dei pacchetti persi e ritrasmessi, abbiamo optato per una simulazione assistita in laboratorio. Il problema relativo al controllo dei pacchetti perduti e ritrasmessi è stato effettuato con l'ausilio dello sniffer descritto nel paragrafo precedente; si è presentato però un problema ben più radicale: come simulare la perdita dei pacchetti stessi. I computer a nostra disposizione e su cui aboiamo svolto i test si trovano sulla rete locale del dipartimento di Infocom e le probabilità di perdita di un pacchetto sono trascurabili. L'idea quindi è stata quella di simulare le perdite sia in trasmissione che in ricezione: abbiamo quindi simulato le perdite sia per quanto riguarda il moderatore (quindi la perdita interessa tuti i ricevitori) sia per il ricevitore (la perdita è relativa esclusivamente a quella istanza). Gli eventi di perdita devono naturalmente avere natura aleatoria sia da parte del moderatore sia per quanto riguarda il generico ascoltatore. Questi eventi di perdita sono associati, rispettivamente dal lato moderatore e ricevitore, a due variabili globali denominate Pe_Tx e Pe_Rx (ovvero perdita in trasmissione e perdita in ricezione).
In questo modo si è voluto simulare differenti scenari di perdita: quella che si verifica vicino al moderatore in trasmissione e che interressa tutti gli ascoltatori; quella che si verifica al moderatore in ricezione e che quindi riguarda le richieste di ritrasmissione;
quelle che si verificano vicino al generico ascoltatore e che quindi interessano, sia in ricezione e in trasmissione, le richieste di ritrasmissione e gli aggiornamenti da parte del ricevitore per quanto riguarda gli aggiornamenti.
Le due variabili globali che governano la perdita casuale dei pacchetti , prima di ogni evento di heartbeat per quanto concerne il moderatore, o in modo periodico per quanto riguarda l'ascoltatore, assumono dei valori estratti da una sequenza casuale originata da un “seme”, di volta in volta diverso per ogni macchina su cui l'applicazione è in esecuzione. I numeri estratti dalla sequenza casuale hanno un valore compreso tra zero ed uno; vengono quindi confrontati con i valori di perdita impostati dall'utente in un file di configurazione: la trasmissione o la ricezione corretta di un pacchetto si verifica esclusivamente se la variabili Pe_Tx o Pe_RX hanno un valore maggiore dei valori di perdita in trasmissione presenti nel file di configurazione.
Ad esempio se imposto i parametri nel file di configurazione in modo che Pe_Tx sia 0.1 e Pe_RX 0.2 allora, ad ogni numero casuale che viene generato all'atto della ricezione o della trasmissione, se ad una estrazione, prima di inviare un aggiornamento, risulta pari 0.3 ( > 0.1 ), non si verifica alcuna perdita.
Esaminando con attenzione la procedura che determina quali siano le unità informative da perdere o gestire correttamente, si nota che imporre ad sempio un valore di 0.1 per Pe_Tx significa perdere il 10% dei pacchetti. Nel caso in cui non si vuole alcun tipo di perdita è sufficiente imporre Pe_Tx e Re_tx pari a zero.