1. Définition de l'interface du gestionnaire d'événements
class GestionnaireEvenement {
public:
virtual void traiterLecture() = 0; // Callback pour les événements de lecture
virtual void traiterEcriture() = 0; // Callback pour les événements d'écriture
virtual int obtenirDescripteur() const = 0; // Obtenir le descripteur de fichier associé
virtual ~GestionnaireEvenement() = default;
};
2. Création d'une instance Epoll
int descripteurEpoll = epoll_create1(0);
if (descripteurEpoll < 0) {
// Gestion des erreurs
}
3. Implémentation de l'enregistrement/désenregistrement des événements
class Reacteur {
private:
int descripteurEpoll;
std::unordered_map<int, GestionnaireEvenement*> gestionnaires; // mapping fd -> gestionnaire
public:
void enregistrerGestionnaire(GestionnaireEvenement* gestionnaire, uint32_t evenements) {
int fd = gestionnaire->obtenirDescripteur();
gestionnaires[fd] = gestionnaire;
struct epoll_event ev;
ev.events = evenements; // EPOLLIN | EPOLLET etc.
ev.data.fd = fd; // association avec fd
epoll_ctl(descripteurEpoll, EPOLL_CTL_ADD, fd, &ev);
}
void supprimerGestionnaire(int fd) {
epoll_ctl(descripteurEpoll, EPOLL_CTL_DEL, fd, nullptr);
gestionnaires.erase(fd);
}
};
4. Implémentation de la boucle d'événements principale
void Reacteur::executer() {
const int MAX_EVENEMENTS = 64;
epoll_event evenements[MAX_EVENEMENTS];
while (true) {
int n = epoll_wait(descripteurEpoll, evenements, MAX_EVENEMENTS, -1);
for (int i = 0; i < n; ++i) {
int fd = evenements[i].data.fd;
auto it = gestionnaires.find(fd);
if (it == gestionnaires.end()) continue;
// Distribution des événements
if (evenements[i].events & EPOLLIN) {
it->second->traiterLecture(); // Déclencher le callback de lecture
}
if (evenements[i].events & EPOLLOUT) {
it->second->traiterEcriture(); // Déclencher le callback d'écriture
}
// Traitement des événements d'erreur/fermeture (EPOLLERR | EPOLLHUP)
}
}
}
5. Gestion du mode déclenchement par front
class GestionnaireSocket : public GestionnaireEvenement {
void traiterLecture() override {
while (true) { // Le mode ET nécessite de vider complètement le tampon
ssize_t n = read(descripteur, tampon, sizeof(tampon));
if (n <= 0) break; // EAGAIN ou erreur
// Traitement des données...
}
}
};
6. Flux de travail complet
- Initialisation : Créer une instance de
Reacteur - Enregistrement de la socket d'écoute : ```
GestionnaireEvenement* ecouteur = new Accepteur(descripteurServeur);
reacteur.enregistrerGestionnaire(ecouteur, EPOLLIN | EPOLLET);
- Démarrage de la boucle d'événements : ```
reacteur.executer(); // Entrer dans la boucle de distribution d'événements
- Établissemnet de connexion (dans
Accepteur::traiterLecture()) : ``` int descripteurClient = accept(descripteurServeur, ...); GestionnaireEvenement* client = new GestionnaireClient(descripteurClient); reacteur.enregistrerGestionnaire(client, EPOLLIN | EPOLLET | EPOLLRDHUP); - Lecture/écriture de données : Traitement de la logique métier dans
GestionnaireClient::traiterLecture()
Points clés de conception
- Pilotage par événements : Toutes les opérations sont déclenchées par des callbacks d'événements
- E/S non bloquantes : Tous les descripteurs de fichier doivent être configurés en mode non bloquant
- Gestion des ressources : ```
~Reacteur() {
for (auto& [fd, gestionnaire] : gestionnaires) {
close(fd); // Fermer le descripteur
delete gestionnaire; // Libérer le gestionnaire
}
close(descripteurEpoll); // Fermer l'instance epoll
}
- Optimisation des perofrmances :
- Utiliser le mode déclenchement par front (EPOLLET) pour réduire les appels système
- Adopter un pool d'objets pour réutiliser les GestionnaireEvenement
- Traitement par lot des événements prêts
Exemple complet : Le projet muduo sur GitHub constitue une référence d'implémentation Reacteur de qualité indsutrielle