mag
15

Dart, HTML5 e il frattale di Koch (2/2)

<– parte precedente

Conclusa la descrizione geometrica cominciamo a dare uno sguardo alle applicazioni Dart

Dart, struttura generale delle applicazioni

Un applicazione in Dart è concettualmente molto simile a un applicazione nei linguaggi visuali che usano elementi grafici ( un applet, un applicazione visual studio con le form etc etc) in particolare una cosa che salta all’occhio e la presenza di un entry point obbligatoria nella forma di un main() che viene eseguito al caricamento della pagina.
Tipicamente all’inizio dell’applicazione si trovano anche le import delle library e le risorse dichiarate per esempio la mia applicazione ha questa forma:

#library('koch');
#import('dart:html');
#resource('koch.css');

main() {
new Koch();
}

All’avvio mi limito ad allocare un oggetto Kock che materialmente gestirà il disegno, per quanto riguarda l’html la forma per collegarsi è la seguente:

&lt;!DOCTYPE html&gt;
&lt;html&gt;
[…]
&lt;canvas id="canvas" width="640" height="640" style="background-color: black"&gt;&lt;/canvas&gt;
[…]
&lt;div class="rangeOption"&gt;
&lt;input id="slider" type="range" max="10" value="2" /&gt;
&lt;/div&gt;
[…]
&lt;script type="application/dart" src="koch.dart"&gt;&lt;/script&gt;
&lt;script src="koch.dart.js"&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;

Lo script viene sempre importato due volte, una volta come file dart ( che viene eseguito nel caso sia presente la macchina virtuale) e una volta nella sua versione javascript, l’importazione avviene alla fine dell’html,

La gestione degli eventi

Una peculiarità del dart è che gli handler per gli eventi non devono ( e probabilmente neanche possono) essere dichiarati nell’html ad esempio la input con id slider nell’html precedente viene usata per controllare i livelli di profondità da disegnare ed ha quindi un metodo associato all’evento change. L’associazione avviene in questo modo:

InputElement slider = document.query("#slider");
slider.on.change.add((Event e) {
passi = Math.parseInt(slider.value);
drawFrame();
}, true);

su un elemento che supporta gli eventi col metodo on si accede alla lista degli eventi e col metodo add si aggiungono i listener, il prototipo della funzione è questo

EventListenerList add(void handler(Event event), [bool useCapture])

L’oggetto event in questo caso viene allocato come classe anonima, il flag finale (opzionale) ha un significato abbastanza oscuro:

Immaginate il DOM della pagina come un albero, gli eventi nel DOM vengono propagati prima dai livelli superiori verso il basso ( contenitore verso il contenuto ) e successivamente dal contenuto in alto verso il contenitore, è possibile associare l’handler a una delle due fasi per avere più controllo sull’ordine di esecuzione di eventuali handler multipli, il valore di default è false e quindi gli handler vengono invocati in fase di risalita. ( vi rimando a un ticket che ho aperto nel gruppo per ulteriori informazioni ).

Le Canvas

L’unico oggetto HTML5 che ho usato nel progetto sono le canvas. Le canvas sono degli ogetti al cui interno è possibile disegnare, i disegni sono persistenti finchè la pagina non viene chiusa e possono indirettamente essere salvati, dart ci permette di accedere alle canvas attraverso l’oggetto CanvasElement, e in particolare attraverso il CanvasRenderingContext2D ad esso associato

Il salvataggio di una canvas

La procedura per salvare una canvas è attualmente abbastanza macchinosa, si estrae l’immagine corrente dalla canvas ( come png, gif o jpeg) , la si setta come contenuto di un tag img e poi la si salva dal browser come un immagine normale.

CanvasElement canvas = document.query("#canvas");
var str=canvas.toDataURL("image/png");
ImageElement e=document.query("#canvasImg");
e.src=str;

Quindi torniamo al frattale di Koch

Lo script seguente disegna il frattale partendo da un triangolo ( 3 segmenti) creando una struttura simile a un fiocco di neve, ho fatto in modo che i colori fossero dipendenti dalla posizione e dalla distanza dal centro ( algoritmo assolutamente empirico, creato con i colori Y’UV per avere più controllo sulla luminanza, così empirico che non lo descrivo nemmeno xD ) uno slider permette di scegliere il livello di profondità con cui dovrà essere disegnato il frattale, la fase di plot viene comunque effettuata solo all’ultimo livello. Leggi tutto »

mag
14

Dart, HTML5 e il frattale di Koch (1/2)

Qualche giorno fa ho cominciato a giocherellare con Dart, questo è il frutto della mia insonnia:

Dart, l’ennesimo tentativo di uccidere javascript.

Dart è un interessante progetto per creare un linguaggio cross-platform e cross-browser funzionante sia lato server che lato client che permetta di sviluppare applicazioni complesse.
L’obiettivo immediato è quindi il sostituire Javascript ( che è un linguaggio oggettivamente impossibile da debuggare ) e in futuro anche i linguaggi server side ( il primo che vorrei eliminare a questo punto è il php) permettendo di utilizzare un solo linguaggio per l’intera applicazione.
Dart dispone sia di una virtual machine ( ancora in alpha ) che un ricompilatore che permette di compilare in javascript in modo da rendere dart avviabile da qualunque browser moderno e ottenendo in futuro, quando la virtual machine si sarà diffusa, prestazioni superiori allo stesso javascript.
Dart è un linguaggio OOP che supporta la programmazione funzionale e può essere usato sia in maniera tipizzata che in maniera non tipizzata (tipizzazione opzionale la chiamano).Supporta l’ereditarietà singola e l’uso delle interfacce, e anche la reflection e la programmazione concorrente.
E’ un progetto molto interessante, non vedo l’ora che entri quantomeno in beta per cominciare a lavorarci seriamente.

La curva di Koch

Per il mio primo “toy program” in dart ho scelto di implementare una classe che disegni la curva di kock in una canvas HTML5.
Non era di geometria che volevo parlare nell’articolo ma descriverò anche matematicamente il modo in cui si può disegnare attraverso il calcolo vettoriale.
La curva di Koch è uno dei primi frattali conosciuti, la sua definizione è più vecchia della definizione formale dei frattali.
La curva si ottiene a partire da un segmento, il segmento viene diviso in 3 parti e nel sottosegmento centrale si ricava un triangolo equilatero, ottenendo quindi una figura formata da 4 segmenti.
La procedura si ripete per ogni segmento, prendo in prestito un immagine da Wikipedia

Il frattale di koch

Animazione della costruzione del frattale di koch

Descriverò la tecnica che ho utilizzato in modo matematico, la trasposizione in codice è abbastanza immediata. Per non perdere di generalità disegnerò una generica porzione del frattale che sia inclinata in modo non banale ( non orizzontale, non verticale e nemmeno a 45°) in modo da evitare “semplificazioni inconsce” nella procedura.

Costruzione di un livello del frattale a partire da un segmento

Costruzione di un livello del frattale a partire da un segmento

1. Si divide il segmento (P1-P5) in 3 parti ottenendo il P2 e P4

Nel caso non ve lo ricordaste la divisione del segmento in un numero arbitrario di parti si ottiene estraendo il vettore che applicato al punto iniziale porta a quello finale, dividendone il modulo per la porzione che si vuole ottenere e applicandolo quindi al punto iniziale del segmento per ottenerne il punto finale.

2. Troviamo P3 applicando al punto medio del segmento ( che si trova come nel passo 1) un vettore lungo quanto l’altezza del triangolo (√(3/4) della lunghezza dei segmenti precedenti) e ruotato di 90° rispetto al vettore di partenza

La rotazione di un vettore si ottiene moltiplicandolo per la matrice di rotazione dell’angolo, nel caso di un angolo di 90° si ha: 

Ogni moltiplicazione per un vettore esprime una rotazione per una fase e una moltiplicazione per un modulo, una matrice di rotazione per un angolo α è la trasposizione in forma matriciale del vettore 1∠ α che equivale alla forma rettangolare cos(α)+ i sin(α) che corrisponde per definizione alla matrice

La procedura che individua i 5 punti della curva partendo da un segmento è quindi:
  List trovaramo(num x1, num y1, num x2, num y2){
    var p2=[x1+(x2-x1)/3,y1+(y2-y1)/3]; // un terzo del segmento
    var p3=[x1+(x2-x1)/2,y1+(y2-y1)/2];//il centro del segmento
    var p4=[x1+(x2-x1)*2/3,y1+(y2-y1)*2/3]; // due terzi del segmento
    var v=[(p2[0]-x1)*r3_4,(p2[1]-y1)*r3_4];
//un vettore allineato al segmento e lungo quanto l'altezza del triangolo
    var vr=[v[1],-v[0]]; //ruoto il vettore di 90 gradi
    p3=[p3[0]+vr[0],p3[1]+vr[1]];
// applico il vettore al punto medio del segmento per ottenere la punta del triangolo
    return [[x1,y1],p2,p3,p4,[x2,y2]];
  }
ripetendo la sequenza nei singoli segmenti si va costruendo il vettore.
Nel prossimo post descriverò l’implementazione in dart del frattale di cui però vi linko il risultato http://www.thedarshan.com/koch/koch.html

apr
18

Multithreading e programmazione funzionale in Cpp0X – C++11. Tutorial in PDF

Ho tratto un tutorial dalla serie di articoli sulla programmazione in Cpp0X, non ho ancora concluso la trattazione del linguaggio ma gli argomenti presenti sono abbastanza completi ( tratto tutte le tecniche base anche se non tutte le api messe a disposizione dalla libreria), spero di poterlo ampliare presto con le altre innovazioni portate da questo interessantissimo nuovo standard di uno dei miei linguaggi preferiti

Gli argomenti trattati sono:

1 Introduzione: Il C++ non è ancora morto
1.1 Lo standard C++0X – C++11
2 il C++ e le lambda
2.1 Il caso pi` semplice, le funzioni void
2.2 Le funzioni che usano e restituiscono parametri
2.3 Le funzioni lambda e la visibilità
2.4 Funzioni che ricevono funzioni e funzioni anonime
2.5 Qualche esempio funzionale ( che funziona)
3 Il C++ e il Multithreading
3.1 Allochiamo il nostro primo thread
3.2 Thread e condivisione dei dati
3.3 Allocare un Thread senza creare un oggetto
4 Sincronizzazione dei Thread e gestione delle risorse con i Mutex
4.1 I Mutex
4.2 Mutex ed eccezioni
5 Uso avanzato dei Mutex
5.1 Timed mutex e la prenotazione con timeout massimo
5.2 Accesso alle funzioni interne del mutex
5.2.1 Prenotazione multipla
6 Il problema dei 5 filosofi
6.1 Vari approcci ingenui
6.2 Una soluzione funzionante
7 Le Condition, implementazione delle code di attesa
7.1 Le Condition
7.2 Ma le cose non sono così semplici…
8 Il problema del barbiere addormentato
8.1 Il barbiere
8.2 Il cliente
8.3 Codice completo
A Ambiente di sviluppo e problemi di
A.1 GNU Gcc – g++
A.1.1 MinGw
A.1.2 Multithreading
A.2 Microsoft Visual Studio

Il testo è rilasciato sotto licenza Creative Commons ed è scaricabile da Scribd

Multithreading e programmazione funzionale in Cpp0X -C++11

feb
13

Implementazione dinamica di una funzione definita ricorsivamente

Implementazione dinamica di una funzione definita ricorsivamente (Appello Aprile 2010)

Sia data la funzione F così definita :

Si richiede di implementare in linguaggio C:

  • Una funzione Fr che calcoli F (n, k);
  • Una funzione Frd che calcoli F (n, k) in O(n2 );

Implementazione ricorsiva

int F(int n, int k)
{
    if(n*k==0)return n+k;
    return F(n,k-1)+ F(n-1,k)+ F(n-1,k-1);
}

La fuzione viene implementata applicando semplicemente la formula di ricorsione

Implementazione iterativa

int Fi(int n, int k)
{
    int **v=malloc(sizeof(int)*(n+1)*(k+1));
    int x=Fi_implementation(n,k,n+1,k+1,v);
    free(v);
    return x;
}

int Fi_implementation(int n, int k, int a, int b,int v[a][b])
{
    int r,c,x;

    for(r=0; r<=n; r++)
    {
        for(c=0; c<=k; c++)
        {
            if(r*c==0)v[r]1=r+c;
            else
                v[r]1=v[r]1+v[r-1]1+v[r-1]1;
        }
    }
    x=v[n][k];
    return v[n][k];
}

Il calcolo di ogni elemento n,k richiede il calcolo di tutti gli elementi y<n , n<k e questi elementi vengono usati più di una da tutti gli elementi superiori e a destra, salvandoli in una matrice è possibile ridurre la complessità a O(n*k) occupando O(n*k) memoria.
per facilitare la leggibilità del codice si usa una funzione di appoggio che permette di definire il vettore con le sue dimensioni, altrimenti passando semplicemente un puntatore doppio si dovrebbero calcolare gli indirizzamenti esplicitamente.

feb
09

Contare le coppie adiacenti in una sequenza in tempo nLog(n)

Contare le coppie adiacenti in una sequenza in tempo nLog(n) (Appello Settembre 2010)

il testo recita:

Sia data una sequenza di interi a1 , a2 , . . . , an . Diciamo che la sequenza contiene una coppia di numeri consecutivi se esistono due interi ai e aj tali che ai = aj + 1. Implementare un algoritmo per contare il numero di coppie consecutive contenute nella sequenza, che impieghi una complessità di tempo complessiva di n log n e una quantità di memoria aggiuntiva di O(n).

Implementazione attraverso un albero binario

Il problema può essere risolto implementando una treesort e facendo in modo che in fase di inserimento si verifichi l’esistenza delle coppie adiacenti

#define N 10
#include "util.h"
#include "strutture.h"
#include <string.h>  // NULL è definito qui

int piazza(nodo *base, nodo *nodo, int coppie){
    int x=nodo->val-base->val;
    if(x==1 || x==-1){//ho trovato una coppia
        coppie++;
        printf("coppia %d <-> %d (%d) \n",nodo->val,base->val, coppie);
    }
    if(x>0){//maggiore, vado a destra
        if(base->r==NULL){//è vuoto, lo piazzo
            base->r=nodo;
        }else{//non è vuoto, richiamo
            coppie=piazza(base->r, nodo, coppie);
        }
    }else{//è minore, vado a sinistra
        if(base->l==NULL){//è vuoto, lo piazzo
            base->l=nodo;
        }else{//non è vuoto, richiamo
            coppie=piazza(base->l, nodo, coppie);
        }
    }
    return coppie;
}

void trovacoppie(int len, int v[len])
{
    int coppie=0,n;
    nodo *nodi=malloc(sizeof(nodo)*len);

    for(n=0; n<len; n++)
    {
        nodi[n].val=v[n];
        nodi[n].l=NULL;
        nodi[n].r=NULL;
        if(n>0)coppie=piazza(nodi, &nodi[n], coppie);
    }
    free(nodi);
    printf("Un totale di %d coppie trovate\n", coppie);
}

Il contatore delle coppie viene passato a ogni chiamata ricorsiva e viene ritornato in output dalla funzione di ordinamento, in questo modo l’output finale conterrà il conto totale, questo approccio ha complessità O(n*log(n)) , non possono esistere soluzioni con un ordine di complessità minore inquanto non esistono ordinamenti più veloci essendo n*log(n) la dimensione dell’albero generato dalle decisioni necessarie per un ordinamento.
L’albero viene allocato dentro un vettore per facilitarne la deallocazione.

feb
06

Determinare la lunghezza della più lunga sottostringa palindroma

Determinare la lunghezza della più lunga sottostringa palindroma ( Appello Aprile 2010)

Il testo recita:

Data una stringa S = a1 a2 . . . an di n caratteri, si propone il problema di determinare la lunghezza della più lunga palindroma sottostringa di S.

Suggerimento:

per la risoluzione: Sia l[i, j] la lunghezza della più lunga palindroma contenuta nella stringa S[i . . . j] = ai ai+1 . . . aj . Il caso base consiste in una stringa di 0 caratteri, cioè il caso in cui i > j. Altrimenti se i caratteri S[i] ed S[j] sono uguali allora posso controllare se la stringa S[i + 1 . . . j − 1] e palindroma ed eventualmente aggiungere i due caratteri S[i] ed S[j]. In tutti gli altri casi devo risolvere ricorsivamente i problemi l[i + 1, j] e l[i, j − 1] e prendere il massimo. La definizione ricorsiva di una soluzione ottima potrebbe essere così definita

La risoluzione del problema mediante l’utilizzo della programmazione dinamica prevede quindi la costruzione di una tabella di dimensione n × n.Si richiede di implementare in linguaggio C:

  1. a) Una funzione lr che calcoli l[1, n] ossia la lunghezza della piùlunga palindroma contenuta nell’intera stringa ;
  2. b) Una funzione lrd che calcoli l[n, k] in O(n2 );

Implementazione Ricorsiva

int m(char s[], int da, int a)
{
    int x;
    if(da>a)return 0;
    if(da==a)return 1;
    if (s[a]==s[da]){
        x=m(s,da+1, a-1);
        if(x==a-da-1)return x+2;
    }
    int r=m(s,da+1,a);
    int l=m(s,da,a-1);
    x=r-l;
    if(x>0)
        return r;
    else
        return l;
}

La fuzione viene implementata applicando semplicemente la formula di ricorsione

Implementazione dinamica

int m_d(char s[], int da, int a,int n,int k, int v[n][k])
{
    int x;
    if(da>a)return 0;
    if(da==a)return 1;
    if (s[a]==s[da]){
        x=v[da+1][a-1];
        if(x==a-da-1)return x+2;
    }
    int r=v[da+1][a];
    int l=v[da][a-1];
    x=r-l;
    if(x>0)
        return r;
    else
        return l;
}

int m_iterativa(char stringa[])
{
    int len=strlen(stringa);
    int **v=malloc(sizeof(int)*len*len);
    int i,j;
    for(i=0;i<len;i++){
        for(j=0;j<(len-i);j++){
            int pal=m_d(stringa,j,i+j,len,len,v);
            v[j*len+(i+j)]=pal;
        }
    }
    len=v[len-1];
    free(v);
    return len;
}

L’implementazione dinamica viene creata modificando la funzione ricorsiva in modo che i valori già calcolati vengano letti da una matrice invece che ricalcolati. I valori per cui c<r (quelli sopra la diagonale principale) sono necessariamente 0 e non vanno calcolati.

L’unico modo di percorrere la matrice senza creare dipendenze da valori non ancora calcolati è per diagonali.

Esempi di matrici dei valori parziali










[ 0 0 0 0 0 0 1 ]
[ 0 0 0 0 0 1 1 ]
[ 0 0 0 0 1 1 1 ]
[ 0 0 0 1 1 1 1 ]
[ 0 0 1 1 3 3 3 ]
[ 0 1 1 1 3 5 5 ]
[ 1 1 1 1 3 5 5 ]
pabcbaq -> 5

[ 0 0 0 0 0 0 0 0 0 0 0 0 1 ]
[ 0 0 0 0 0 0 0 0 0 0 0 1 1 ]
[ 0 0 0 0 0 0 0 0 0 0 1 1 1 ]
[ 0 0 0 0 0 0 0 0 0 1 1 3 3 ]
[ 0 0 0 0 0 0 0 0 1 1 3 3 3 ]
[ 0 0 0 0 0 0 0 1 1 1 3 3 3 ]
[ 0 0 0 0 0 0 1 1 3 3 3 3 3 ]
[ 0 0 0 0 0 1 1 1 3 3 3 3 3 ]
[ 0 0 0 0 1 2 2 2 3 3 3 3 3 ]
[ 0 0 0 1 1 2 4 4 4 4 4 4 4 ]
[ 0 0 1 1 1 2 4 4 4 4 4 4 4 ]
[ 0 1 2 2 2 2 4 4 4 4 4 4 4 ]
[ 1 1 2 4 4 4 4 4 4 4 4 4 4 ]
ammaccabanane -> 4

feb
04

Ricerca del minimo numero di caratteri da aggiungere a una stringa per renderla palindroma

Ricerca del minimo numero di caratteri da aggiungere a una stringa per renderla palindroma ( Compito del 28 Settembre 2011)

Il testo recita:

Una stringa e palindroma se non cambia leggendola da destra a sinistra e viceversa. Ad esempio anna e osso sono due palindromi.

Risolvere il seguente problema: data una stringa S = {a 1 , a2 , . . . , an } di n caratteri, calcolare il minimo numero di caratteri che occorre inserire per renderla un palindromo.

Suggerimento per la risoluzione:

Sia P [i, j] il numero di caratteri necessari per trasformare la stringa S[i, j] in una stringa palindroma. Il caso base consiste in una stringa di 1 (o meno) caratteri, cioè il caso in cui i = j.

In tal caso ovviamente non serve nessun carattere per rendere la stringa palindroma perch lo gi. Altrimenti se i caratteri S[i] ed S[j] sono uguali allora posso semplicemente risolvere il sottoproblema P [i + 1; j − 1]. Viceversa dovrò inserire almeno un carattere per rendere la stringa palindroma e calcolare il minimo tra i sottoproblemi P [i + 1; j] e P [i; j + 1], risolti ricorsivamente. La definizione ricorsiva di una soluzione ottima potrebbe essere così definita

La risoluzione del problema mediante l’utilizzo della programmazione dinamica prevede quindi la costruzione di una tabella di dimensione n × n.

Implementazione ricorsiva

int p(char s[], int da, int a){
  if(da>=a)return 0;
  if (s[a]==s[da])return p(s, da+1, a-1);
  int r=p(s,da+1,a);
  int l=p(s,da,a-1);
  int x=r-l;
  if(x>0)
    return l+1;
  else
    return r+1;
}

int palindromizza(char stringa[]){
  return p(stringa, 0, strlen(stringa)-1);
}

La fuzione viene implementata applicando semplicemente la formula di ricorsione

Implementazione Dinamica

int p_d(char s[], int da, int a, int r, int c, int v[ r ][ c ] )
{
    if (da >= a)
    {
        return 0;
    }
    if (s[a] == s[da])
    {
        return v[da + 1][a - 1];
    }
    int destra = v[da + 1][a];
    int sinistra = v[da][a - 1];
    int delta = destra - sinistra;
    if (delta > 0)
    {
        return sinistra + 1;
    }
    else
    {
        return destra + 1;
    }
}

int p_iterativa(char stringa[])
{
    int len=strlen(stringa);
    int **v=malloc(sizeof(int)*len*len);
    int i,j;
    for(i=0;i<len;i++){
        for(j=0;j<(len-i);j++){
            int pal=p_d(stringa,j,i+j,len,len,v);
            v[j*len+(i+j)]=pal;
        }
    }
    len=v[len-1];
    free(v);
    return len;
}

L’implementazione dinamica viene creata modificando la funzione ricorsiva in modo che i valori già calcolati vengano letti da una matrice invece che ricalcolati. I valori per cui c>r sono necessariamente 0 (quelli sopra la diagonale principale) e non vanno calcolati.

L’unico modo di percorrere la matrice senza creare dipendenze da valori non ancora calcolati è per diagonali.

Esempi di matrici dei valori parziali

[ 0 0 0 0 0 ]
[ 0 0 0 0 1 ]
[ 0 0 0 1 2 ]
[ 0 0 1 0 1 ]
[ 0 1 2 1 0 ]ABCBA -> 0
[ 0 0 0 0 0 ]
[ 0 0 0 0 1 ]
[ 0 0 0 0 1 ]
[ 0 0 1 1 0 ]
[ 0 1 0 1 1 ]pappa -> 1
[ 0 0 0 0 0 ]
[ 0 0 0 0 1 ]
[ 0 0 0 1 2 ]
[ 0 0 1 2 3 ]
[ 0 1 2 3 4 ]abcde -> 4

gen
05

Come creare una sottorete sottoposta a sniffing utilizzando un router supplementare

In questo post descriverò la procedura che ho utilizzato per creare una sottorete all’interno della mia rete domestica da utilizzare per eseguire analisi delle cominicazioni di rete ( per motivi di sviluppo o di sicurezza) o per creare Honeypot. L’idea era quella di creare una seconda rete collegata alla rete domestica e quindi a internet attraverso un host che fa da proxy ( nel senso etimologico del termine) che chiameremo Routing server e logga tutte le conversazioni verso l’esterno per poterle analizzare in seguito.

I requisiti sono quindi:

  • La sottorete creata deve aver accesso diretto e trasparente ad internet
  • Tutto il traffico che attraversa il gateway della sottorete deve essere loggato ( quindi sia quello che esce che quello che entra)
  • Il traffico della sottorrete esistente ( incluso quello generato dal server di routing su eth1 che non è di forwarding) non deve essere loggato
  • Il traffico strettamente interno alla sottorete è irrilevante
  • L’interazione della sottorete con la rete interna preesistente deve essere minimo

La topologia di rete utilizzata è

Descrivendo la topologia dal punto di vista del Routing Server si ha la configurazione seguente:

  • All’interfaccia Eth1 è collegato il router che ha l’accesso ad internet, ad esso è collegata anche la sottorete preesistente
  • L’indirizzo del Routing server in Eth0 è 192.168.1.64
  • L’indirizzo del Routing server in Eth0 è 192.168.0.5, dovrà fare da gateway per il router interno
  • L’indirizzo (interno) del router connesso ad internet è 192.168.1.254
  • L’indirizzo del router interno è 192.168.0.1
  • All’interfaccia Eth0 è collegato un secondo router ( in particolare un router wireless visto che la funzione principale del progetto è studiare applicazioni Android)
  • Il router connesso ad internet ha una sottorrete definita dalla netmask 192.168.1.0/24
  • il router interno una sottorete definita dalla netmask 192.168.0.0/24

Routing Server collega le due sottoreti attraverso funzioni di forwarding implementate attraverso iptables, è quindi un host Linux

Configurazione del Routing server

Come dicevo il routing server è un host linux con due schede di rete che collega le due sottoreti forwardando i pacchetti con iptables. Per rendere possibile questo dobbiamo innanzitutto terminare il servizio network manager ( /etc/init.d/network-manager stop )

La configurazione viene eseguita attraverso uno script e viene quindi annullata al riavvio. lo script è il seguente:

#parte1
pkill dhclient
ifconfig eth0 192.168.0.5
route add -net 192.168.0.0 netmask 255.255.255.0 dev eth0 gw 192.168.1.64
route del default
route add default gw 192.168.1.254

#parte2
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -F
iptables -t nat -P PREROUTING ACCEPT
iptables -t nat -P POSTROUTING ACCEPT
iptables -t nat -P OUTPUT ACCEPT
iptables -t mangle -P PREROUTING ACCEPT
iptables -t mangle -P OUTPUT ACCEPT
iptables -A POSTROUTING -t nat -o eth1 -j MASQUERADE
iptables -A FORWARD -i eth0 -o eth1 -j ACCEPT
iptables -A FORWARD -o eth1 -i eth0 -j ACCEPT

#parte3
iptables -A FORWARD -p tcp -s 192.168.0.5/24 --sport 135 -j DROP #microsoft epmap
iptables -A FORWARD -p udp -s 192.168.0.5/24 --sport 137 -j DROP #netbios ns
iptables -A FORWARD -p tcp -s 192.168.0.5/24 --sport 138 -j DROP #netbios dgm
iptables -A FORWARD -p udp -s 192.168.0.5/24 --sport 1900 -j DROP #SSDP
iptables -A FORWARD -p tcp -s 192.168.0.5/24 --sport 445 -j DROP #microsoft-ds

vediamo di descrivere lo script:

la #parte1

termina i servizi di dhcp ancora in vita e poi setta in maniera statica l’indirizzo di Eth0 e setta il gateway di Eth0 a 192.168.1.64 e il gateway di default a 192.168.1.254 (il router connesso)

la #parte2

attiva e configura il forwarding tra le due interfacce, la prima riga lo attiva nel caso fosse disattivato

la #parte3

filtra i pacchetti provenienti da protocolli tipici di windows e provenienti dalla rete preesistente . questi pacchetti gonfierebbero inutilmente la dimensione dei file di log

Configurazione del router interno

Il router interno dovrà utilizzare 192.168.0.5 come gateway ( indirizzo di Router Server su Eth0) ma utilizzare un dns esterno ( nel mio caso opendns)

L’indirizzamento dhcp deve essere configurato facendo in modo che siano dinamici solo gli indirizzi superiori a 192.168.0.5

Sniffing!

Una volta configurato il tutto lo sniffing è molto semplice, basta avviare uno sniffer qualunque ( tcpdump, wireshark) e metterlo in ascolto su Eth0

In questo modo possiamo anche continuare a navigare senza che il nostro traffico compaia nei log

nov
25

le Condition, implementazione delle code di attesa. il problema del barbiere addormentato (C++0x – C++11)

Le Condition

Le Condition sono un meccanismo di sincronizzazione che permette ai thread di sospendersi in attesa che avvenga un evento e di essere risvegliati quando l’evento accade, permettono quindi di creare delle code di attesa.

Nelle STL le condition sono oggetti della classe std::condition_variable , i metodi fondamentali per usarle sono tre:

  • wait() permette di accodarsi alla condition
  • notify_one() notify_all() permettono di risvegliare uno o tutti i thread in attesa sulla coda (nell’implementazione tradizionale dei pthread si chiamavano signal e broadcast)

A una Condition è sempre associato un mutex per evitare race condition che potrebbe creare un thread che si sta preparando a una wait mentre un altro thread esegue una signal, la signal potrebbe infatti avvenire contemporaneamente alla wait e venire quindi persa creando un deadlock, può essere usato un mutex qualunque o si può creare un mutex proprio della condition

Vediamone il funzionamento con un semplice codice: 10 thread si mettono in coda su una condition e un altro thread li sveglia uno per volta

#include <iostream>
#include <thread>
#include <cstdlib>
#include <mutex>
#include <condition_variable>

using namespace std;

class Worker
{
public :

    int numero;
    std::condition_variable *coda;
    mutex *m;
    Worker(int n, std::condition_variable *cond, mutex *mtx)
    {
        coda=cond;
        numero=n;
        m=mtx;
    }
    void operator()()
    {
        cout << "Thread"<< numero << " allocato, mi metto in coda" << endl;
        std::unique_lock<std::mutex> lk(*m);
        coda->wait(lk);
        cout << "Thread"<< numero << " risvegliato dalla coda" << endl;
    }
};

int main()
{
    std::condition_variable *coda= new condition_variable();
    mutex *m= new mutex();
    int a;

    std::thread* threads[10];

    for(a=0;a<10;a++){//creo
        threads[a]= new std::thread(Worker(a+1,coda,m));
        usleep( 100000 );
    }
    for(a=0;a<10;a++){//sveglio
        usleep( 1000 * ( rand() % 900));
        coda->notify_one();
    }
    for(a=0;a<10;a++)//aspetto termine
    {
        threads[a]->join();
        delete(threads[a]);
    }
};

Ma le cose non sono così semplici…

  • Malgrado di solito non succeda le specifiche dei pthread non garantiscono che l’ordine in cui i thread si sveglino sia uguale a quello in cui si addormentano, quindi in caso di applicazioni dove l’ordine degli accessi è importante questo va verificato esplicitamente.
  • Un altra cosa che potrebbe succedere ( e questa è più grave ) è che più di un thread venga liberato dalla coda, quindi i thread appena svegliati devono di nuovo verificare che la condizione che li ha fatti accodare sia stata soddisfatta ed eventualmente rimettersi in coda.

Il problema del barbiere addormentato

Un barbiere di Lisbona...

Un barbiere di Lisbona...

Vediamo un applicazione delle condition a un problema più complesso, il problema del barbiere addormentato di Dijkstra. il problema è definito in questo modo:

Un negozio di barbiere è costituito da una sala con una sola poltrona sulla quale si accomoda il cliente che viene servito e da una sala d’attesa con n sedie. Se non ci sono clienti da servire il bar­biere va a dormire. Se arriva un cliente e tutte le sedie della sala d’attesa sono occupate, il clien­te va via, mentre, se c’è una sedia libera e il barbiere è occupato, il cliente attenderà, seduto, il proprio turno. Se il barbiere sta dormendo (cioè è libero, senza clienti in attesa), il cliente lo sve­glia e viene servito subito.

La soluzione è più prolissa di quello che potrebbe sembrare a prima vista, tralaltro nell’implementarlo ho istintivamente usato un monitor anche se non ho accennato a questo tipo di costrutti, ne do soltanto una breve definizione OOP

Un monitor è un oggetto che incapsula la risorsa ed espone i metodi per accedervi in maniera sicura

In questo caso, come si vede nel sorgente sottostante, il contatore delle sedie occupate è nascosto dentro la classe Stanza che mette a disposizione i metodi per occupare e liberare una sedia mantenendoli sincronizzati attraverso un semaforo proprio della classe.

#include <iostream>
#include <thread>
#include <cstdlib>
#include <mutex>
#include <condition_variable>

using namespace std;

int dormi(float base, float variabile)
{
    int k=1000*1000;
    int base_i=(int)base*k;
    int variabile_i=0;
    if(variabile>0)variabile_i=rand() % (int)(variabile*k);
    usleep(base_i+variabile_i);
    return base_i+variabile_i;
};

class Stanza
{
private:
    int occupate;
    std::mutex monitor_m;

public:

    std::condition_variable attesa;

    std::mutex attesa_m;
    std::condition_variable poltrona;
    std::condition_variable letto;
    std::mutex barbiere;
    int sedie;

    int prenota()
    {
        std::unique_lock<std::mutex> lk(monitor_m);
        if(occupate>=sedie)return -1;
        occupate++;
        cout << occupate << " sedie occupate"<<endl;

        return occupate;
    }

    int guarda()
    {
        std::unique_lock<std::mutex> lk(monitor_m);
        return occupate;
    }

    int libera()
    {
        std::unique_lock<std::mutex> lk(monitor_m);
        occupate--;
        cout << occupate << " sedie occupate"<<endl;
        return occupate;
    }

    Stanza(int n_sedie)
    {
        this->sedie=n_sedie;
        occupate=0;
    }
};

class Barbiere
{
public :

    int numero;
    int azioni;
    Stanza *stanza;
    std::unique_lock<std::mutex> *barbiere;
    Barbiere(Stanza *st)
    {
        azioni=0;
        this->stanza=st;
        barbiere= new std::unique_lock<std::mutex>(stanza->barbiere,std::defer_lock_t());

    }
    void operator()()
    {
        while(true)
        {
            cout << "Barbiere controlla se ci sono clienti in attesa"<<endl;

            if(stanza->guarda()==0)
            {
                cout << "Barbiere si addormenta"<<endl;
                std::unique_lock<std::mutex> lk(stanza->barbiere);
                stanza->letto.wait(lk);
            }

            cout << ++azioni <<" Barbiere serve un cliente"<<endl;
            stanza->attesa.notify_one();
            dormi( 2 , 2 );
            stanza->poltrona.notify_one();
        }
    }
};

class Cliente
{
public :

    int numero;
    Stanza *stanza;
    std::unique_lock<std::mutex> *barbiere;
    Cliente(Stanza *st, int n)
    {
        stanza=st;
        numero=n;
        barbiere= new std::unique_lock<std::mutex>(stanza->barbiere,std::defer_lock_t());

    }
    void work()
    {
        cout << "Thread"<< numero << " allocato, entra dal barbiere" << endl;
        int s=stanza->prenota();

        if(s<0)
        {
            cout << "Thread"<< numero << " tutte le sedie sono occupate, esce" << endl;
            return;
        }
        else
        {
            if(s>0)
            {
                cout << "Thread"<< numero << " si siede e aspetta" << endl;
                if (s==1)
                {
                    cout << "Thread"<< numero << " sveglia il barbiere" << endl;
                    dormi(1,0);
                    stanza->letto.notify_one();
                }
                std::unique_lock<std::mutex> lk(stanza->attesa_m);
                stanza->attesa.wait(lk);
                cout << "Thread"<< numero << " si alza" << endl;
                stanza->libera();
            }

            barbiere->lock();
            cout << "Thread"<< numero << " si siete e si fa rasare" << endl;
            stanza->poltrona.wait(*barbiere);
            barbiere->unlock();
            cout << "Thread"<< numero << " esce" << endl;

        }

    }
    void operator()()
    {
        for(int a=0; a<2; a++)
        {
            work();
            cout << "Thread"<< numero << " va in giro a bighellonare" << endl;
            dormi(30,30);
        }
        cout << "Thread"<< numero << " muore" << endl;
    }
};

int main(int argc, char** argv)
{
    Stanza stanza(5);
    int a;

    std::thread* threads[15];

    std::thread* barbiere=new std::thread(Barbiere(&stanza));
    for(a=0; a<15; a++)   //creo
    {
        dormi(0,a);
        threads[a]= new std::thread(Cliente(&stanza,a+1));
    }
    for(a=0; a<15; a++)   //aspetto termine
    {
        threads[a]->join();
        delete(threads[a]);
    }
};

Il problema è risolto nel modo seguente:

Il barbiere:

  1. Controlla se ci sono clienti in attesa (attraverso i metodi del Monitor)
  2. se non ce ne sono si addormenta ( svegliandosi al punto 3)
  3. se ce ne sono li rade e torna al punto 1

la rasatura consiste in:

  • liberare un Cliente dalla coda della sala di attesa (attesa.notify_one) , il cliente liberato occupa la coda della poltrona
  • radere il Cliente, liberarlo dalla coda della poltrona (poltrona.notify_one())

Il Cliente

  1. Entra nella sala, cerca di sedersi
    1. Se può sedersi:
      1. Se è l’unica persona seduta sveglia il barbiere (letto.notify_one())
      2. va al punto 2
    2. Se non può sedersi esce dalla sala e va al punto 5
  2. Si mette in attesa nella coda della sala (attesa.wait(lk))
  3. una volta svegliato occupa il barbiere (barbiere->lock()) e si mette in attesa sulla sedia della poltrona (poltrona.wait(*barbiere)).
    il mutex sul barbiere evita il problema di una possibile signal non voluta ( vedi sopra il paragrafo “ma le cose non sono così semplici”)
  4. una volta svegliato libera il barbiere ed esce dalla sala
  5. va in giro a bighellonare e poi torna al punto 1

In questa implementazione sono possibili delle signal superflue su un barbiere già sveglio, ma sono irrilevanti

Con questo articolo ho concluso la serie sulla programmazione concorrente in Cpp0X Leggi tutto »

nov
09

Uso avanzato dei Mutex, i 5 Filosofi (C++0x – C++11)

Nell’articolo precedente ho parlato del mutex e del suo uso semplice con la lock_guard. in questo tratterò gli altri tipi di guard e l’uso avanzato dei mutex

unique_lock

Con unique_lock un mutex può fare molto di più di quello che permette la lock_guard :

  • Si può cercare di prenotarlo in maniera non bloccante ( se è libero lo si occupa, se non è libero non si attende)
  • Si può cercare di prenotarlo fornendo un timeout massimo di attesa ( se si tratta di un timed mutex ovviamente)
  • Si può accedere direttamente alle funzioni del mutex

e un sacco di altre cose per le quali vi rimando a una reference ( lo so, non è ufficiale… ma non ho trovato di meglio )

Timed mutex e la prenotazione con timeout massimo

Disponendo di un timed mutex potremmo per esempio eseguire questo:

std::timed_mutex m;
std::unique_lock<std::timed_mutex>
 lk(m,std::chrono::milliseconds(5));
if(lk)work()
}

Questa funzione prova ad accedere al mutex aspettando massimo 5 millisecondi, il valore della lock si usa per determinare se l’accesso è avvenuto o no

Accesso alle funzioni interne del mutex

Attraverso la unique_lock possiamo accedere alle funzioni del mutex, ma questa volta le eccezioni sono gestite.

std::mutex m;
void zona_critica()
{
 std::unique_lock<std::mutex> lk(m);
 // in questo momento il mutex è occupato
 lk.unlock();
 // in questo momento il mutex è libero
 lk.lock();
 //in questo momento il mutex è di nuovo occupato
}

Possiamo anche evitare che il mutex venga occupato all’allocazione della unique_lock utilizzando un costruttore diverso:

std::mutex m;
void zona_critica()
{
 std::unique_lock<std::mutex> lk(m,std::defer_lock_t());
 // in questo momento il mutex è libero
 lk.lock();
 // in questo momento il mutex è occupato
 lk.unlock();
 //in questo momento il mutex è di nuovo libero
}

Prenotazione multipla

Un problema molto comune nella programmazione concorrente è la necessita da parte di un processo di accedere in maniera esclusiva a più risorse, è un problema che se gestito male può causare facilmente deadlock o starvation in quanto se un processo che ha acquisito parte delle risorse necessarie attende altre risorse per continuare e queste risorse sono bloccate da altri processi anche loro in attesa tutti i processi restano bloccati in attesa l’uno dell’altro, in questo caso le stl ci vengono in aiuto dandoci un costrutto che permette di bloccare n mutex restando in attesa ma non bloccandoli

void prenota(std::mutex *a ,std::mutex *a)
 {
 std::unique_lock<std::mutex> lock_a(*a, std::defer_lock);
 std::unique_lock<std::mutex> lock_b(*b, std::defer_lock);
 std::lock(lock_a,lock_b);

 // fai qualcosa di critico
 }

Il problema dei 5 filosofi

Illustrazione originale dal testo di Dijkstra

Un problema molto interessante che illustra la potenzialità di questo costrutto è il problema dei 5 filosofi a cena, esso è stato formulato da Dijkstra intorno al 1965, per mettere alla prova le primitive di sincronizzazione dell’epoca ( i semafori fondamentalmente) il problema è questo (cito dal testo di Silberschatz) :

Si considerino cinque filosofi che passano la vita pensando e mangiando. I filosofi con­dividono un tavolo rotondo circondato da cinque sedie, una per ciascun filosofo. Al cen­tro del tavolo si trova una zuppiera colma di riso, e la tavola è apparecchiata con cinque bacchette. Quando un filosofo pensa, non interagisce con i colleghi; quan­do gli viene fame, tenta di prendere le bacchette più vicine: quelle che si trovano tra lui e i commensali alla sua destra e alla sua sinistra. Un filosofo può prendere una bacchetta alla volta e non può prendere una bacchetta che si trova già nelle mani di un suo vicino. Quando un filosofo affamato tiene in mano due bacchette contemporaneamente, man­gia senza lasciare le bacchette. Terminato il pasto, le posa e riprende a pensare.

“Il problema dei cinque filosofi è considerato un classico problema di sincronizzazione, non certo per la sua importanza pratica, e neanche per antipatia verso i filosofi da parte degli informatici, ma perché rappresenta una vasta classe di problemi di controllo della concorrenza, in particolare i problemi caratterizzati dalla necessità di assegnare varie risorse a diversi processi evitando situazioni di stallo e d’attesa indefinita.” la parola starvation deriva proprio da questo problema… se non si riesce a gestire la cosa i filosofi muoiono di fame ( to starve in inglese)

Una semplice soluzione consiste nel rappresentare ogni bacchetta con un mutex: un filosofo tenta di afferrare ciascuna bacchetta eseguendo un’operazione lock su quel mutex e la posa eseguendo una unlock.

Partendo da questo modello possiamo cominciare a cercare una soluzione, quella più immediata potrebbe esere:

un filosofo prende la bacchetta a destra, poi quella a sinistra e poi mangia

ma cosa succede se tutti prendono contemporaneamente la forchetta di destra? un deadlock, muoiono di fame aspettando che qualcuno liberi qualcosa ma nessuno libererà mai niente.

un’altra soluzione potrebbe essere:

Un filosofo di numero pari prende prima la forchetta di destra e poi quella di sinistra, un filosofo dispari il contrario

Ma questo è barare xD scartiamola

Ci rendiamo conto che un filosofo deve rilasciare la bacchetta se non può prendere la seconda, qualcosa del tipo

Un filosofo prende la bacchetta di destra, poi cerca di prendere quella di sinistra (trylock) e se non può le posa entrambe e riprova in seguito

ma che succede se i filosofi partono insieme? l’algoritmo va in loop: tutti prendono quella di destra, nessuno può prendere quella a sinistra, tutti posano tutto e si ricomincia, le operazioni necessitano di essere eseguite in maniera atomica:

Un filosofo cerca di prendere entrambe le bacchette contemporaneamente, se non può aspetta

Questo comportamento non può essere costruito direttamente usando i singoli mutex, ma possiamo farlo con il nostro std::lock(lock_a,lock_b)

La soluzione tradizionale necessita della costruzione di un Monitor, ma non me ne occuperò qui, sfruttiamo a pieno le stl e riduciamo il problema a una cosa banale, con buona pace di Dijkstra

...
class Filosofo {
public :

    int id;
    mutex *destra;
    mutex *sinistra;
    Filosofo(int n, mutex *d, mutex *s) {
        id=n;
        destra=d;
        sinistra=s;
    }

    void pensa() {
        string argomenti[5]= {
            "Alla fame nel mondo",
            "Al punto G",
            "All origine dell Universo",
            "Al senso della vita",
            "Alla domanda la cui risposta e' 42"
        };
        cout << id << ": Sto pensando... " << argomenti[id] << endl;
        usleep( 10000 * ( rand() % 90 + 10 ));
    };

    void mangia() {
        printf("%d: Cerco le bacchette...\n",id);
        std::unique_lock<std::mutex> lock_a(*destra, std::defer_lock);
        std::unique_lock<std::mutex> lock_b(*sinistra, std::defer_lock);
        std::lock(lock_a,lock_b);
        printf("%d: Sto mangiando...\n",id);
        usleep( 30000 * ( rand() % 90 + 10 ));
        printf("%d: Poso le bacchette, sazio\n",id);
    };

    void operator()() {
        printf("%d: Penso quindi sono \n",id);
        usleep( 30000 * ( rand() % 90 + 10 ));
        for(int a=0; a<5; a++) {
            pensa();
            mangia();
        }
    }
};
...

La versione completa del codice si trova qui

per questo articolo è meglio fermarmi, sta diventando troppo lungo, le condition le tratto nel prossimo

References

Se siete interessati alla versione originale del problema di Dijkstra ,risolta interamente con i semafori, potete dare un occhiata a questo testo su google docs, se poi avete anche interessi archeologici c’è la scan della versione originale qui (vi avviso, Dijkstra coda in Agol xD)

Che Dio benedica l’università del texas e google docs, visto che la versione ufficiale nel sito della Springer costa 35€

Il testo di Silberschatz che citavo è “Sistemi operativi. Concetti ed esempi” di Abraham Silberschratz, Peter Baer Galvin, Greg Gagne. potete dargli un occhiata qui , è finora il miglior libro tradotto in italiano sui sistemi operativi in cui mi sia imbattuto

L’ambiente utilizzato

Man mano che ci inoltriamo nella parte più raffinata della libreria abbiamo bisogno di versioni sempre più recenti, stavolta ci serve la 4.6 di gcc oltre a tutte le lib descritte nell’articolo iniziale sui thread

Leggi tutto »

Post precedenti «