Implémentation du mécanisme Epoll dans le framework Reactor pour serveurs haute concurrence

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

  1. Initialisation : Créer une instance de Reacteur
  2. Enregistrement de la socket d'écoute : ``` GestionnaireEvenement* ecouteur = new Accepteur(descripteurServeur); reacteur.enregistrerGestionnaire(ecouteur, EPOLLIN | EPOLLET);
  3. Démarrage de la boucle d'événements : ``` reacteur.executer(); // Entrer dans la boucle de distribution d'événements
  4. Établissemnet de connexion (dans Accepteur::traiterLecture()) : ``` int descripteurClient = accept(descripteurServeur, ...); GestionnaireEvenement* client = new GestionnaireClient(descripteurClient); reacteur.enregistrerGestionnaire(client, EPOLLIN | EPOLLET | EPOLLRDHUP);
  5. Lecture/écriture de données : Traitement de la logique métier dans GestionnaireClient::traiterLecture()

Points clés de conception

  1. Pilotage par événements : Toutes les opérations sont déclenchées par des callbacks d'événements
  2. E/S non bloquantes : Tous les descripteurs de fichier doivent être configurés en mode non bloquant
  3. 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 }
  4. 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

Étiquettes: epoll reacteur C++ Concurrence Linux

Publié le 2 septembre à 20h54