Alcuni giorni orsono ho postato un articolo che descriveva le problematiche riguardanti la creazione di un Web Server completamente realizzato in C# con gli strumenti messi a disposizione dal framework .NET. In questa puntata inizieremo ad affrontare i primi due punti esposti in tale articolo per dare loro una soluzione che consenta al nostro Web Server di accettare richieste in ingresso senza per questo impedire che esso possa essere avviato o fermato a piacimento. Come primo passo iniziamo ad analizzare quali sono gli strumenti di cui il framework dispone per metterci in ascolto su una porta tcp/ip pronti ad evadere le richieste dei browser.

Il metodo di gran lunga più semplice per attivare un socket in ascolto su una porta è quello di fare uso della classe TcpListener. Tale classe, ha la capacità di ricevere in ingresso un "EndPoint" (accoppiata IP-porta) e di operare in due modi, dopo che sia stato avviato il binding con il metodo Start(). Il primo metodo richiede che vengano chiamati AcceptSocket() o AcceptTcpClient() provocando il blocco del thread corrente finchè non arrivi una richiesta che ci verrà restituita sotto forma di Socket nel primo caso o di TcpClient nel secondo. Questa modalità "bloccante" non è adeguata a quello che dobbiamo realizzare. Infatti ci troveremmo nella condizione per cui non saremmo in grado di uscire dal thread principale dell'applicazione finchè non giunga una chiamata che sblocchi il metodo. Per fortuna la classe TcpListener dispone di un metodo Pending() che ci informa della presenza di richieste in attesa di essere evase, che poi dovranno essere accettate con i metodi suddetti. Questa modalità di lavoro richiede quindi che si verifichi lo stato di Pending() ad intervalli regolari e solo nel momento in cui esso restituisca true chiamare il metodo più adeguato alle nostre esigenze, che nel nostro caso sarà AcceptTcpClient().

Comincia quindi a prendere forma quello che dovrà essere il corpo principale del nostro main thread. Rimane solo da definire in che modo comuicare a questo ciclo potenzialente infinito che è giunto il momento di uscire. A questo scopo useremo uno degli strumenti che il sistema operativo stesso espone, in particolare un AutoResetEvent, cioè in breve un flag che solleveremo nel momento in cui desideriamo interrompere il servizio http. Su tale evento è possibile usare la funzione di sincronizzazione WaitHandle.WaitAny che verifica lo stato dell'evento per il periodo specificato ed esce indicando il motivo dell'interruzione. Ecco riportato uno spezzone di codice:

/// 
/// Initializes a new instance of the  
class.
/// 

/// The ip address.

/// 
The listen port.

public HttpServer(IPAddress ipAddress, int 
listenPort)
{
    
this
.Address = ipAddress;
    
this
.Port = listenPort;

    
this.StopEvent = new AutoResetEvent(false
);
    
this.Listener = new TcpListener(ipAddress, this
.Port);
}

/// 
/// 
Starts this instance.
/// 

public virtual void Start()
{
    ThreadStart start = 
new 
ThreadStart(ListenThread);
    Thread listenThread = 
new 
Thread(start);
    listenThread.Start();
}

/// 
/// 
Stops this instance.
/// 

public virtual void Stop()
{
    
this
.StopEvent.Set();


/// 
/// 
Listens the thread.
/// 

protected virtual void ListenThread()
{
    
this
.Listener.Start();

    WaitHandle[] waitHandles = 
new WaitHandle[] { this
.StopEvent };

    
while 
(
        WaitHandle.WaitAny(
            waitHandles, 
            100, 
            
false
) == WaitHandle.WaitTimeout)
        
if 
(Listener.Pending())
            EnqueueRequest(
this
.Listener.AcceptTcpClient());

    
this
.Listener.Stop();
}

Questo codice riporta la parte di inizializzazione, i metodi per l'avvio e l'interruzione del web server e ovviamente il main loop. Nel costruttore, uno dei molti che coprono buona parte delle possibili combinazioni di parametri, vengono semplicemente assegnati i valori delle proprietà e creati l'evento e il TcpListener. I metodi Start() e Stop() comandano il thread che esegue il main loop il primo avviandolo e il secondo sollevando l'evento AutoResetEvent che come vedremo fra poco obbliga il main loop ad uscire. Infine il main loop utilizza il metodo statico WaitAny dell'oggetto WaitHandle. Tale metodo è in grado di attendere che un oggetto di un array di WaitHandle cambi di stato e in questo caso esce ritornando l'indice del'oggetto nell'array. Inoltre consente di specificare un timeout oltre il quale ritorna il controllo al chiamante con un valore WaitHandle.WaitTimeout. Ecco quindi che il mainloop non fa altro che dedicare 100 millisecondi all'attesa dell'evento di uscita e ad ogni ciclo verifica lo stato di Pending() che non essendo un WaitHandle non è controllabile da WaitAny. Qualora Pending() sia settato a true il codice si occupa di gestire la richiesta intervenuta.

Il metodo che implementa il mainloop tutto sommato è molto semplice ma è evidente che deve funzionare con puntualità e velocita proprio per evitare che il client riceva delle risposte errate o dei timeout. La scelta del valore di 100 millisecondi è arbitraria. A me è parso un valore ideale abbastanza alto per non avere impatto sulle prestazioni delle macchina dato che comunque in tale periodo il thread appare a tutti gli effetti sospeso come se avesse invocato la Thread.Sleep(), e quindi rilascia le risorse ad altri processi ma anche abbastanza basso perchè un client non debba attendere troppo l'evasione della sua richiesta.

Nela prossima puntata affronteremo finalmente proprio questo punto. A partire dal metodo EnqueueRequest()  parte il codice che accoda la richiesta in arrivo e in seguito la gestisce. Per questo compto la mia scelta è stat quella di preferire l'uso di eventi sulla creazione esplicita di nuovi thread. Ma come ho già detto lo vedremo alla prossima.

powered by IMHO 1.3


Commenti (3) -

# | Radicalmente | 13.12.2005 - 23.42

# | Di .NET e di altre amenita' | 13.12.2005 - 23.49

# | Armando | 06.11.2006 - 20.17

Attendo con ansia il seguito dell'articolo. Sono nuovo di C# e questo genere di articoli mi aiutano molto nell'apprendere la filosofia del linguaggio.

Thanks Smile

Aggiungi Commento