Reactor-Entwurfsmuster (Reactor Pattern)

Aus dev.kaibel.net
Zur Navigation springen Zur Suche springen

Reactor-Entwurfsmuster (Reactor Pattern)

Das Reactor-Entwurfsmuster ist ein Entwurfsmuster aus der Kategorie der Verhaltensmuster, das häufig in ereignisgesteuerten Systemen verwendet wird – insbesondere in Netzwerkservern und asynchronen Anwendungen.

Es dient dazu, mehrere gleichzeitige Ereignisse (z. B. eingehende Verbindungen, eingehende Daten oder Timer) effizient zu verarbeiten, ohne für jedes Ereignis einen eigenen Thread zu starten.

Grundidee

Der Reactor ist eine zentrale Ereignisverteiler-Komponente (Event Demultiplexer), die auf verschiedene Eingabekanäle (z. B. Sockets) wartet. Wenn ein Ereignis eintritt, delegiert der Reactor die Bearbeitung an die zuständige Event-Handler-Komponente.

Das Muster trennt also:

  • die Ereigniserkennung (I/O oder Signale),
  • die Ereignisverteilung, und
  • die Ereignisverarbeitung.

Struktur

Typische Komponenten des Reactor-Musters:

Komponente Aufgabe
Handle Ein Deskriptor (z. B. Socket oder File-Handle), der Ereignisse erzeugen kann
Event Demultiplexer Wartet auf Ereignisse mehrerer Handles (z. B. mit select(), poll() oder epoll())
Reactor Registriert Event-Handler und leitet eintreffende Ereignisse weiter
Event Handler Implementiert die Logik, die bei einem bestimmten Ereignis ausgeführt werden soll
Concrete Event Handler Konkrete Implementierung (z. B. ClientHandler, FileHandler)

Ablaufdiagramm

Typischer Kontrollfluss des Reactor-Musters:

+---------------------+
|      Reactor        |
|---------------------|
| - Event-Loop        |
| - Demultiplexer     |
+---------+-----------+
          |
          v
  [Event tritt auf]
          |
          v
+---------------------+
|   Event Handler     |
|---------------------|
|  onRead(), onWrite()|
+---------------------+

Beispiel (C++)

Ein einfaches Beispiel, wie ein Reactor mit select() mehrere Sockets überwacht:

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <iostream>
#include <vector>
#include <algorithm>

class EventHandler {
public:
    virtual int getHandle() const = 0;
    virtual void handleEvent() = 0;
    virtual ~EventHandler() = default;
};

class ClientHandler : public EventHandler {
    int sockfd;
public:
    explicit ClientHandler(int fd) : sockfd(fd) {}
    int getHandle() const override { return sockfd; }
    void handleEvent() override {
        char buffer[256];
        int n = read(sockfd, buffer, sizeof(buffer) - 1);
        if (n > 0) {
            buffer[n] = '\0';
            std::cout << "Nachricht: " << buffer << std::endl;
        }
        else {
            std::cout << "Client getrennt.\n";
            close(sockfd);
        }
    }
};

class Reactor {
    std::vector<EventHandler*> handlers;
public:
    void registerHandler(EventHandler* handler) {
        handlers.push_back(handler);
    }

    void handleEvents() {
        while (true) {
            fd_set readfds;
            FD_ZERO(&readfds);
            int maxfd = 0;

            for (auto* h : handlers) {
                FD_SET(h->getHandle(), &readfds);
                maxfd = std::max(maxfd, h->getHandle());
            }

            int ready = select(maxfd + 1, &readfds, nullptr, nullptr, nullptr);
            if (ready > 0) {
                for (auto* h : handlers)
                    if (FD_ISSET(h->getHandle(), &readfds))
                        h->handleEvent();
            }
        }
    }
};

Dieses Beispiel zeigt den Kernmechanismus: Der Reactor wartet mit select() auf Ereignisse und ruft dann den passenden Handler auf.

Vorteile

  • Hohe Effizienz bei vielen gleichzeitigen Verbindungen (non-blocking I/O)
  • Klare Trennung von Ereigniserkennung und -verarbeitung
  • Erweiterbar um neue Ereignistypen durch neue Handler

Nachteile

  • Erhöhter Programmieraufwand
  • Komplexere Fehlerbehandlung
  • Nicht ideal für CPU-lastige Aufgaben (dafür eignet sich das Proactor-Entwurfsmuster)

Vergleich: Reactor vs. Proactor

Merkmal Reactor Proactor
Auslöseart Ereignis (z. B. "Daten sind verfügbar") Abschluss (z. B. "Lesevorgang ist beendet")
Typischer Einsatz Non-blocking I/O Asynchronous I/O
Steuerung Anwendung reagiert aktiv auf Ereignisse Betriebssystem meldet fertige Operationen

Siehe auch

Quellen

  • Douglas C. Schmidt: *Reactor: An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events*
  • POSA2 – *Pattern-Oriented Software Architecture: Patterns for Concurrent and Networked Objects*
  • Wikipedia: Reactor Pattern
  • select(2) Manpage