Nouvelles Du Monde

Modèles d’architecture logicielle : le modèle Reactor

Modèles d’architecture logicielle : le modèle Reactor

2023-04-24 12:04:00

Les modèles sont une abstraction importante dans le développement de logiciels modernes et l’architecture logicielle. Ils offrent une terminologie bien définie, une documentation propre et l’apprentissage des meilleurs. Les applications événementielles telles que les serveurs ou les interfaces graphiques utilisent souvent le modèle d’architecture Reactor. Cela peut accepter plusieurs demandes en même temps et les distribuer à différents gestionnaires.

Rainer Grimm travaille depuis de nombreuses années en tant qu’architecte logiciel, chef d’équipe et responsable de la formation. Il aime écrire des articles sur les langages de programmation C++, Python et Haskell, mais aime aussi intervenir fréquemment lors de conférences spécialisées. Sur son blog Modernes C++, il traite intensément de sa passion pour le C++.

Le modèle Reactor est une infrastructure pilotée par les événements pour démultiplexer et distribuer simultanément les demandes de service à différents fournisseurs de services. Les requêtes sont traitées de manière synchrone.

Réacteur

Aussi connu sous le nom

Problème

Un serveur doit

  • répondre à plusieurs demandes clients en même temps,
  • être performant, stable et évolutif
  • être extensible pour prendre en charge des services nouveaux ou améliorés.

L’application doit être protégée contre les problèmes de multithreading et de synchronisation.

Solution

  • Chaque service pris en charge est encapsulé dans un gestionnaire.
  • Les manutentionnaires sont inscrits dans le réacteur.
  • Le réacteur utilise un démultiplexeur d’événements pour attendre de manière synchrone tous les événements entrants.
  • Lorsque le réacteur est avisé, il transmet la demande de service au gestionnaire approprié.

Structure

Handles

  • Les poignées identifient diverses sources d’événements telles que les connexions réseau, les fichiers ouverts ou les événements de l’interface graphique.
  • La source d’événements génère des événements tels que la connexion, la lecture ou l’écriture qui sont mis en file d’attente sur le descripteur associé.

Synchonous Event Demultiplexer

  • Le démultiplexeur d’événements synchrones attend un ou plusieurs événements d’indication et se bloque jusqu’à ce que le descripteur associé puisse traiter l’événement.
  • Avec les appels système sélectionner, sondage, epoll, kqueue ou WaitForMultipleObjects il peut attendre des événements d’indication.

Event Handler

  • Le gestionnaire d’événements définit l’interface de traitement des événements d’indication.
  • Le gestionnaire d’événements définit les services pris en charge par l’application.

Concrete Event Handler

  • Le gestionnaire d’événements concret implémente l’interface de l’application définie par le gestionnaire d’événements.

Reactor

Le réacteur

  • prend en charge une interface pour enregistrer et désenregistrer le gestionnaire d’événements concrets à l’aide de descripteurs de fichiers,
  • utilise un démultiplexeur d’événements synchrones pour attendre des événements d’indication ; un événement d’indication peut être un événement de lecture, d’écriture ou d’erreur,
  • mappe les événements à leurs gestionnaires d’événements concrets et
  • gère la durée de vie de la boucle d’événements.

Le Reactor (et non l’application) attend les événements d’indication pour démultiplexer et envoyer l’événement. Les gestionnaires d’événements concrets sont enregistrés dans le réacteur. Le réacteur inverse le flux de contrôle. Ce renversement de contrôle est souvent appelé Hollywood-Prinzip désigné.

Le comportement dynamique d’un réacteur est assez intéressant.

Les points suivants illustrent le flux de contrôle entre le réacteur et le gestionnaire d’événements :

  • L’application enregistre un gestionnaire d’événements pour des événements spécifiques dans le réacteur.
  • Chaque gestionnaire d’événements expose son gestionnaire spécifique au réacteur.
  • L’application démarre la boucle d’événements. La boucle d’événements attend des événements d’indication.
  • Le démultiplexeur d’événements retourne au réacteur lorsqu’une source d’événements devient prête.
  • Le réacteur envoie les poignées au gestionnaire d’événements approprié.
  • Le gestionnaire d’événements gère l’événement.

Voyons le réacteur en action.

Dans cet exemple, il sera Cadre POCO utilisé. “Les bibliothèques POCO C++ sont de puissantes bibliothèques C++ multiplateformes pour créer des applications réseau et Internet qui s’exécutent sur des systèmes de bureau, de serveur, mobiles, IoT et embarqués.

// reactor.cpp

#include 
#include 

#include "Poco/Net/SocketReactor.h"
#include "Poco/Net/SocketAcceptor.h"
#include "Poco/Net/SocketNotification.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/ServerSocket.h"
#include "Poco/Observer.h"
#include "Poco/Thread.h"
#include "Poco/Util/ServerApplication.h"

using Poco::Observer;
using Poco::Thread;

using Poco::Net::ReadableNotification;
using Poco::Net::ServerSocket;
using Poco::Net::ShutdownNotification;
using Poco::Net::SocketAcceptor;
using Poco::Net::SocketReactor;
using Poco::Net::StreamSocket;

using Poco::Util::Application;

class EchoHandler {
 public:
  EchoHandler(const StreamSocket& s, 
              SocketReactor& r): socket(s), reactor(r) { // (11)
    reactor.addEventHandler(socket, 
      Observer
        (*this, &EchoHandler::socketReadable));
  }

  void socketReadable(ReadableNotification*) {
    char buffer[8];
    int n = socket.receiveBytes(buffer, sizeof(buffer));
    if (n > 0) {
      socket.sendBytes(buffer, n);                       // (13)                                                    
    }
    else {
      reactor.removeEventHandler(socket,                 // (12)
	    Observer
	      (*this, &EchoHandler::socketReadable));
      delete this;
    }
  }

 private:
  StreamSocket socket;
  SocketReactor& reactor;
};

class DataHandler {
 public:

  DataHandler(StreamSocket& s, 
              SocketReactor& r):
    socket(s), reactor(r), outFile("reactorOutput.txt") {
    reactor.addEventHandler(socket,                      // (14) 
      Observer
        (*this, &DataHandler::socketReadable));
    reactor.addEventHandler(socket,                      // (15)
      Observer
         (*this, &DataHandler::socketShutdown));
    socket.setBlocking(false);
  }

  ~DataHandler() {                                       // (16)
    reactor.removeEventHandler(socket, 
      Observer
         (*this, &DataHandler::socketReadable));
    reactor.removeEventHandler(socket, 
      Observer
        (*this, &DataHandler::socketShutdown));
  }

  void socketReadable(ReadableNotification*) {
    char buffer[64];
    int n = 0;
    do {
      n = socket.receiveBytes(&buffer[0], sizeof(buffer));
      if (n > 0) {
        std::string s(buffer, n);
        outFile << s << std::flush;                     // (17)
      }
      else break;
    } while (true);
  }

  void socketShutdown(ShutdownNotification*) {
    delete this;
  }

 private:
  StreamSocket socket;
  SocketReactor& reactor;
  std::ofstream outFile;
};

class Server: public Poco::Util::ServerApplication {

 protected:
  void initialize(Application& self) {                    // (3)
    ServerApplication::initialize(self);
  }
		
  void uninitialize() {                                   // (4)
    ServerApplication::uninitialize();
  }

  int main(const std::vector&) {             // (2)
		
    ServerSocket serverSocketEcho(4711);                  // (5)
    ServerSocket serverSocketData(4712);                  // (6)
    SocketReactor reactor;
    SocketAcceptor 
      acceptorEcho(serverSocketEcho, reactor);            // (7)
    SocketAcceptor 
      acceptorData(serverSocketData, reactor);            // (8)
    Thread thread;
    thread.start(reactor);                                // (9)
    waitForTerminationRequest();
    reactor.stop();                                       // (10)
    thread.join();
        
    return Application::EXIT_OK;

  }

};

int main(int argc, char** argv) {

  Server app;                                             // (1)
  return app.run(argc, argv);

}

(1) crée le serveur TCP. Cela conduit le mainfonction (2) et est initialisée en (3) et désinitialisée en (4). Le main-La fonction de serveur TCP crée deux sockets serveur écoutant sur le port 4711 5) et le port 4712 (6). Dans les (7) et (5) les sockets serveur avec le EchoHandler et le DataHandler créé. Le SocketAcceptor modélise le composant accepteur du Accepter-Connector-Designmusters. Le réacteur s’exécute dans un thread séparé (9) jusqu’à ce qu’il reçoive sa demande d’annulation (10).

Le EchoHandler enregistre son descripteur de lecture dans le constructeur (11) et désenregistre son descripteur de lecture dans la fonction membre socketReadable sur (12). Il renvoie le message du client (13). En revanche, le DataHandler un client pour transmettre des données au serveur. Le Handler enregistre son action pour les événements de lecture (14) et les événements d’arrêt (ligne 15) dans son constructeur. Les deux Handler devenir dans le destructeur de DataHandler (16) désabonné à nouveau. Le résultat du transfert de données est directement dans le descripteur de fichier outFile écrit (17).

La sortie suivante montre le serveur à gauche et les deux clients à droite. Une session telnet sert de client. Le premier client se connecte au port 4711: telnet 127.0.0.1 4711. Ce client se connecte au serveur echo et affiche donc sa requête. Le deuxième client se connecte au port 4712 : telnet 127.0.0.1 4712. La sortie du serveur montre que les données du client sont en cours de transfert vers le serveur.

Quels sont les avantages et les inconvénients du Reactor ?

Avantages

  • Une séparation claire du cadre et de la logique d’application.
  • La modularité des différents gestionnaires d’événements concrets.
  • Le réacteur peut être porté sur différentes plates-formes car les fonctions de démultiplexage d’événements sous-jacentes telles que sélectionner, sondage, epoll, kqueue ou WaitForMultipleObjects sont disponibles sur les plateformes Unix (select, epoll) et Windows (WaitForMultipleObjects).
  • La séparation de l’interface et de l’implémentation permet une personnalisation ou une extension facile des services.
  • L’architecture globale prend en charge l’exécution parallèle.

Désavantages

  • Le réacteur nécessite un appel système pour démultiplexer les événements.
  • Un gestionnaire d’événements de longue durée peut bloquer le réacteur.
  • L’inversion du contrôle rend les tests et le débogage plus difficiles.

Il existe de nombreux modèles éprouvés utilisés dans le domaine de la concurrence. Ils traitent des problèmes de synchronisation comme le partage et la mutation, mais aussi des architectures concurrentes. Dans mon prochain article, je commencerai par les modèles qui se concentrent sur le partage de données.


(rme)

Vers la page d’accueil



#Modèles #darchitecture #logicielle #modèle #Reactor
1682506798

Facebook
Twitter
LinkedIn
Pinterest

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Votre navigateur Web n’est pas pris en charge

Votre navigateur Web n’est pas pris en charge

Votre navigateur Web n’est pas pris en charge – CNN CNNEflèche vers le basfermerglobeplaylistrecherchesocial-facebooksocial-googleplussocial-instagramliens sociauxcourrier socialsocial-plussocial-twittersocial-whatsapp-01social-whatsapphorodatagetapez-audiogalerie de

ADVERTISEMENT