Home » Patterns in software architecture: The Reactor pattern

Patterns in software architecture: The Reactor pattern

by admin
Patterns in software architecture: The Reactor pattern

Patterns are an important abstraction in modern software development and software architecture. They offer well-defined terminology, clean documentation, and learning from the best. Event-driven applications such as servers or GUIs often use the Reactor architecture pattern. This can accept multiple requests at the same time and distribute them to different handlers.

Rainer Grimm has been working as a software architect, team leader and training manager for many years. He likes to write articles on the programming languages ​​C++, Python and Haskell, but also likes to speak frequently at specialist conferences. On his blog Modernes C++ he deals intensively with his passion for C++.

The Reactor pattern is an event-driven framework to demultiplex and simultaneously distribute service requests to different service providers. The requests are processed synchronously.

Reactor

Also known as

Problem

A server should

  • answer several customer inquiries at the same time,
  • be high-performing, stable and scalable as well
  • be extensible to support new or enhanced services.

The application should be protected from multithreading and synchronization problems.

Solution

  • Each supported service is encapsulated in a handler.
  • The handlers are registered in the reactor.
  • The reactor uses an event demultiplexer to synchronously wait for all incoming events.
  • When the reactor is notified, it forwards the service request to the appropriate handler.

Structure

Handles

  • The handles identify various event sources such as network connections, opened files or GUI events.
  • The event source generates events such as connect, read, or write that are queued on the associated handle.

Synchonous Event Demultiplexer

  • The synchronous event demultiplexer waits for one or more indication events and blocks until the associated handle can process the event.
  • It can wait for indication events with the system calls select, poll, epoll, kqueue or WaitForMultipleObjects.

Event Handler

  • The event handler defines the interface for processing the indication events.
  • The event handler defines the supported services of the application.

Concrete Event Handler

  • The concrete event handler implements the application’s interface defined by the event handler.

Reactor

Der Reactor

  • supports an interface for registering and deregistering the concrete event handler using file descriptors,
  • uses a synchronous event demultiplexer to wait for indication events; an indication event can be a read, write or error event,
  • maps the events to their concrete event handlers and
  • manages the lifetime of the event loop.

The Reactor (and not the application) waits for the indication events to demultiplex and send the event. The concrete event handlers are registered in the reactor. The reactor reverses the flow of control. This reversal of control is often referred to as the Hollywood Principle.

The dynamic behavior of a reactor is quite interesting.

The following points illustrate the flow of control between the reactor and the event handler:

  • The application registers an event handler for specific events in the reactor.
  • Each event handler exposes its specific handler to the reactor.
  • The application starts the event loop. The event loop waits for indication events.
  • The event demultiplexer returns to the reactor when an event source becomes ready.
  • The reactor sends the handles to the appropriate event handler.
  • The event handler handles the event.

Let’s see the Reactor in action.

This example uses the POCO framework. “The POCO C++ Libraries are powerful cross-platform C++ libraries for building network- and internet-based applications that run on desktop, server, mobile, IoT, and embedded systems.

// 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) creates the TCP server. This leads the mainfunction (2) and is initialized in (3) and deinitialized in (4). The main-Function of TCP server creates two server sockets listening on port 4711 5) and port 4712 (6). In the (7) and (5) the server sockets with the EchoHandler and the DataHandler created. The SocketAcceptor models the Acceptor component of the Accepter-Connector design pattern. The reactor runs in a separate thread (9) until it receives its cancel request (10).

The EchoHandler registers its read handle in the constructor (11) and unregisters its read handle in the member function socketReadable on (12). It sends back the customer’s message (13). In contrast, the DataHandler a client to transmit data to the server. The Handler registers its action for read events (14) and shutdown events (line 15) in its constructor. Both Handler become in the destructor of DataHandler (16) unsubscribed again. The result of the data transfer is directly in the file handle outFile written (17).

The following output shows the server on the left and the two clients on the right. A telnet session serves as the client. The first client connects to port 4711: telnet 127.0.0.1 4711. This client connects to the echo server and therefore displays its request. The second client connects to port 4712: telnet 127.0.0.1 4712. The output from the server shows that the client’s data is being transferred to the server.

What are the pros and cons of the Reactor?

Advantages

  • A clear separation of framework and application logic.
  • The modularity of the various concrete event handlers.
  • The reactor can be ported to different platforms as the underlying functions for demultiplexing events like select, poll, epoll, kqueue or WaitForMultipleObjects are available on Unix (select, epoll) and Windows platforms (WaitForMultipleObjects).
  • The separation of interface and implementation allows for easy customization or extension of the services.
  • The overall architecture supports parallel execution.

Disadvantages

  • The reactor requires a system call to demultiplex events.
  • A long-running event handler can block the reactor.
  • Reversing control makes testing and debugging more difficult.

There are many proven patterns used in the field of concurrency. They deal with synchronization problems such as sharing and mutation, but also with concurrent architectures. In my next post, I’ll start with the patterns that focus on data sharing.


(rme)

To home page

See also  Isolated Web Apps: Will the web app gap be closed once and for all?

You may also like

Leave a Comment

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

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More

Privacy & Cookies Policy