Approfondita comprensione dei loop di eventi e dei timer [chiuso]

0

Ho una conoscenza di base dell'accettazione dei client senza creare un thread per ogni connessione utilizzando strumenti come select e equivalenti moderni (kqueue, epoll).

Ma dove vado da qui? ad esempio, I / O asincrono e timer (periodici). Potrei aprire la fonte alla libev e agli amici, ma per me è un po 'travolgente per non dire altro.

Ho cercato libri di testo su questo argomento ma non ho trovato nulla di utile.

L'obiettivo di base è scrivere una piccola implementazione giocattolo di una libreria con timer e I / O asincroni. Solo per avere una migliore comprensione dei soggetti.

Non sto cercando di reinventare la ruota. Questo è solo per scopi di apprendimento / educativi. (neanche i compiti a casa)

    
posta mds 29.08.2014 - 12:44
fonte

2 risposte

2

In breve, puoi trovare una sorta di discussione in Architettura software orientata ai modelli vol.2 - stai chiedendo (e sto descrivendo approssimativamente in basso) il pattern Reactor.

Puoi anche leggere un'implementazione di questo nella libreria ACE di Doug Schmidt, ma come la maggior parte delle librerie del mondo reale, è coperta da verruche per la portabilità ed è comunque un codice piuttosto vecchio stile.

Quindi, hai un semplice ciclo di selezione / sondaggio / evento, e vuoi implementare callback I / O, timer periodici e I / O asincroni.

1: I / O di base

Il modo generico (ovvero riutilizzabile in senso bibliografico) per inviare gli eventi è fornire un'interfaccia astratta che un client possa implementare. Se guardi all'interfaccia select / poll / any, vedrai gli eventi associati a un descrittore di file che ti diranno esattamente cosa ti serve:

class IOHandler {
public:
  virtual ~IOHandler();

  // lib needs fd for syscall
  virtual int getFD() const = 0;

  // lib needs to know what flags to set
  virtual bool readFlag() const = 0;
  virtual bool writeFlag() const = 0;
  virtual bool errorFlag() const = 0;

  // lib needs to dispatch callbacks
  virtual void onReadable() = 0;
  virtual void onWriteable() = 0;
  virtual void onError() = 0;
};

Ora, se hai un vettore di IOHandler* o qualsiasi altra cosa, dovresti riuscire a scrivere il ciclo.

Tieni presente che la gestione degli errori, consentendo ai gestori di rimuovere se stessi durante il ciclo ecc. richiederà uno sforzo leggermente maggiore.

2: Timer

Noterai che il tuo syscall di scelta ha un parametro di timeout. Forniremo un'altra interfaccia per i timer:

class Timer {
public:
  virtual ~Timer() {}

  // some kind of period
  virtual unsigned period() const = 0;

  // some kind of callback at the end of each period
  virtual void fire() = 0;
};

Ora, la tua biblioteca ha bisogno di un'interfaccia per aggiungere & rimuovere quei timer. Una volta che li hai, puoi mantenere un contenitore di Timer* , ordinato da il prossimo tempo di sparo assoluto (un heap è tradizionale).

Quindi, ogni volta che chiami select / poll / whatever, ottieni la prossima scadenza dalla parte anteriore dell'heap e la usi per il tuo timeout. Se ottieni ETIMEDOUT, invia i timer dalla parte anteriore del tuo heap finché non ne raggiungi uno in futuro.

Qui stiamo parlando solo di timer periodici, quindi ogni volta che ne estrai uno in avanti, devi aggiungere l'ora corrente al suo periodo e reinserirla in coda.

Anche in questo caso, la gestione di una singola o di una frequenza variabile, che consente ai timer di rimuovere se stessi ecc., viene lasciata come esercizio. Se i normali callback I / O potrebbero essere lenti (rispetto al periodo dei timer), è possibile eseguire un ciclo di invio del timer dopo aver gestito anche questi callback.

3: I / O asincrono

Il solito meccanismo POSIX per questo è rendere i tuoi socket non bloccanti (questo in genere non funziona per i file). Dopo averlo fatto, devi anche aggiungere molto più gestione dello stato per gestire i casi in cui

  • non è stato possibile completare la scrittura di un buffer, quindi è necessario tenerlo da qualche parte (e assicurarsi di controllare che il file fd sia nuovamente scrivibile)
    • eventuali future scritture dovranno ora essere accodate o aggiunte a quel buffer fino a quando il socket non ha raggiunto
  • non puoi leggere quanto vuoi, quindi devi tenere un messaggio parziale da qualche parte (fino a quando il file fd diventa leggibile) e puoi riassicurare il messaggio

Probabilmente questo può essere gestito all'interno del framework I / O sopra disegnato, ma non ho intenzione di implementarlo qui.

    
risposta data 29.08.2014 - 17:41
fonte
1

Un'applicazione linux userebbe select () o sondaggio () in un ciclo, quindi inoltrare al gestore corretto per ogni coppia descrittore di file + evento. Un costrutto simile con la funzione GetMessage () è usato in windows.

Boost contiene una versione stand-alone di IO asincrono con timer di eventi (sebbene nel classico modulo Boost sia un po 'complicato).

    
risposta data 29.08.2014 - 14:22
fonte

Leggi altre domande sui tag