Yazılım mimarisinde model: Reactor modeli
Modeller, modern yazılım geliştirme ve yazılım mimarisinde önemli bir soyutlamadır. İyi tanımlanmış terminoloji, açık belgeler sunar ve en iyisinden öğrenirler. Sunucular veya GUI’ler gibi olay odaklı uygulamalar genellikle Reactor mimari modelini kullanır. Bu, aynı anda birden çok isteği kabul edebilir ve bunları farklı işleyicilere dağıtabilir.
Rainer Grimm, uzun yıllardır yazılım mimarı, ekip lideri ve eğitim yöneticisi olarak çalışmaktadır. C++, Python ve Haskell programlama dilleri üzerine makaleler yazmaktan hoşlanır, aynı zamanda sık sık uzmanlık konferanslarında konuşmaktan da keyif alır. Modernes C++ blogunda yoğun bir şekilde C++ tutkusundan bahsediyor.
Reactor modeli, çoğullamayı çözmek ve hizmet isteklerini aynı anda birden çok hizmet sağlayıcıya dağıtmak için olay güdümlü bir çerçevedir. İstekler senkronize olarak işlenir.
reaktör
Ayrıca şöyle bilinir
sorun
Bir sunucu
Çözüm
Handles
Reaktör
Bir reaktörün dinamik davranışı oldukça ilginçtir.
dinamik davranış
Aşağıdaki noktalar, reaktör ile olay işleyici arasındaki kontrol akışını göstermektedir:
Örnek
Bu örnek, POCO çerçevesini kullanır. “POCO C++ Kitaplıkları, masaüstü bilgisayarlar, sunucular, mobil cihazlar, Nesnelerin İnterneti ve gömülü sistemler üzerinde çalışan ağ ve İnternet tabanlı uygulamalar oluşturmaya yönelik güçlü platformlar arası C++ kitaplıklarıdır.“
// reactor.cpp
#include <fstream>
#include <string>
#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<EchoHandler, ReadableNotification>
(*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<EchoHandler,
ReadableNotification>
(*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<DataHandler,
ReadableNotification>
(*this, &DataHandler::socketReadable));
reactor.addEventHandler(socket, // (15)
Observer<DataHandler,
ShutdownNotification>
(*this, &DataHandler::socketShutdown));
socket.setBlocking(false);
}
~DataHandler() { // (16)
reactor.removeEventHandler(socket,
Observer<DataHandler,
ReadableNotification>
(*this, &DataHandler::socketReadable));
reactor.removeEventHandler(socket,
Observer<DataHandler,
ShutdownNotification>
(*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:
fstream 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<std::string>&) { // (2)
ServerSocket serverSocketEcho(4711); // (5)
ServerSocket serverSocketData(4712); // (6)
SocketReactor reactor;
SocketAcceptor<EchoHandler>
acceptorEcho(serverSocketEcho, reactor); // (7)
SocketAcceptor<DataHandler>
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) TCP sunucusunu oluşturun. Bu getiriyor mainişlev (2) ve (3)’te başlatılır ve (4)’te sıfırlanır. ONLAR main-TCP sunucu işlevi, 4711 5) ve 4712 (6) bağlantı noktalarını dinleyen iki sunucu soketi oluşturur. (7) ve (5)’te sunucu, dosyayla yuva yapar EchoHandler ve DataHandler oluşturuldu. ONLAR SocketAcceptor Alıcı-Bağlayıcı tasarım modelinin Alıcı bileşenini modeller. Reaktör, iptal talebini (10) alana kadar ayrı bir iş parçacığında (9) çalışır.
ONLAR EchoHandler yapıcıya (11) okuma tanıtıcısını kaydedin ve üye işlevindeki okuma tanıtıcısının kaydını kaldırın socketReadable (12) üzerinde. Müşteriden gelen mesajı döndürür (13). Tersine, DataHandler sunucuya veri iletmek için bir istemci. ONLAR Handler yapıcısında okuma olayları (14) ve kapatma olayları (satır 15) için eylemini kaydeder. İkisi birden Handler yok edici duruma gelmek DataHandler (16) tekrar abonelikten çıktı. Veri aktarımının sonucu doğrudan dosya tanıtıcısındadır outFile yazılı (17).
Aşağıdaki çıktı soldaki sunucuyu ve sağdaki iki istemciyi gösterir. Bir telnet oturumu istemci görevi görür. İlk istemci bağlantı noktasına bağlanır 4711: telnet 127.0.0.1 4711. Bu istemci yankı sunucusuna bağlanır ve ardından isteğini görüntüler. İkinci istemci 4712 numaralı bağlantı noktasına bağlanır: telnet 127.0.0.1 4712. Sunucudan gelen çıktı, istemci verilerinin sunucuya aktarıldığını gösterir.
Reaktörün artıları ve eksileri nelerdir?
Avantajlar ve dezavantajlar
Avantajlar
Rekabet alanında kullanılan birçok kanıtlanmış model vardır. Paylaşım ve mutasyon gibi senkronizasyon sorunlarının yanı sıra rakip mimarilerle de ilgilenirler. Bir sonraki yazımda veri paylaşımına odaklanan şablonlarla başlayacağım.
(rm)
Haberin Sonu
Yazılım mimarisinde model: Reactor modeli
Modeller, modern yazılım geliştirme ve yazılım mimarisinde önemli bir soyutlamadır. İyi tanımlanmış terminoloji, açık belgeler sunar ve en iyisinden öğrenirler. Sunucular veya GUI’ler gibi olay odaklı uygulamalar genellikle Reactor mimari modelini kullanır. Bu, aynı anda birden çok isteği kabul edebilir ve bunları farklı işleyicilere dağıtabilir.

Rainer Grimm, uzun yıllardır yazılım mimarı, ekip lideri ve eğitim yöneticisi olarak çalışmaktadır. C++, Python ve Haskell programlama dilleri üzerine makaleler yazmaktan hoşlanır, aynı zamanda sık sık uzmanlık konferanslarında konuşmaktan da keyif alır. Modernes C++ blogunda yoğun bir şekilde C++ tutkusundan bahsediyor.

Reactor modeli, çoğullamayı çözmek ve hizmet isteklerini aynı anda birden çok hizmet sağlayıcıya dağıtmak için olay güdümlü bir çerçevedir. İstekler senkronize olarak işlenir.
reaktör
Ayrıca şöyle bilinir
sorun
Bir sunucu
- Aynı anda birden fazla müşteri talebine cevap vermek,
- performanslı, istikrarlı ve ölçeklenebilir olun
- yeni veya iyileştirilmiş hizmetleri desteklemek için genişletilebilir olmalıdır.
Çözüm
- Desteklenen her hizmet bir yönetici içinde kapsüllenir.
- İşleyiciler reaktörde kayıtlıdır.
- Reaktör, gelen tüm olayları eşzamanlı olarak beklemek için bir olay çoğullama çözücü kullanır.
- Reaktör bilgilendirildiğinde servis talebini ilgili yöneticiye iletir.


Handles
- Tutamaçlar, ağ bağlantıları, açık dosyalar veya GUI olayları gibi çeşitli olay kaynaklarını tanımlar.
- Olay kaynağı, ilişkili tanıtıcıda kuyruğa alınan bağlanma, okuma veya yazma gibi olaylar oluşturur.
- Eşzamanlı olay çoğullama çözücü, bir veya daha fazla bayrak olayını bekler ve ilgili tanıtıcı olayı işleyebilene kadar bloke eder.
- Select, poll, epoll, kqueue veya WaitForMultipleObjects sistem çağrılarıyla gösterge olaylarını bekleyebilir.
- Olay işleyici, gösterge olaylarını işlemek için arabirimi tanımlar.
- Olay işleyici, uygulamanın desteklenen hizmetlerini tanımlar.
- Somut olay işleyici, olay işleyici tarafından tanımlanan uygulama arayüzünü uygular.
Reaktör
- dosya tanımlayıcıları kullanarak somut olay işleyicisini kaydetmek ve kaydını silmek için bir arabirimi destekler,
- gösterge olaylarını beklemek için bir senkronize olay çoğullama çözücü kullanır; bir gösterge olayı bir okuma, yazma veya hata olayı olabilir,
- olayları somut olay işleyicilerine eşler e
- olay döngüsünün süresini yönetir.
Bir reaktörün dinamik davranışı oldukça ilginçtir.
dinamik davranış
Aşağıdaki noktalar, reaktör ile olay işleyici arasındaki kontrol akışını göstermektedir:
- Uygulama, reaktördeki belirli olaylar için bir olay işleyicisi kaydeder.
- Her olay işleyici, kendi özel işleyicisini reaktöre gösterir.
- Uygulama olaylar döngüsünü başlatır. Olay döngüsü gösterge olaylarını bekler.
- Bir olay kaynağı hazır olduğunda, olay çoğullama çözücüsü reaktöre geri döner.
- Reaktör, tanıtıcıları uygun olay işleyiciye gönderir.
- Olay işleyicisi olayı işler.
Örnek
Bu örnek, POCO çerçevesini kullanır. “POCO C++ Kitaplıkları, masaüstü bilgisayarlar, sunucular, mobil cihazlar, Nesnelerin İnterneti ve gömülü sistemler üzerinde çalışan ağ ve İnternet tabanlı uygulamalar oluşturmaya yönelik güçlü platformlar arası C++ kitaplıklarıdır.“
// reactor.cpp
#include <fstream>
#include <string>
#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<EchoHandler, ReadableNotification>
(*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<EchoHandler,
ReadableNotification>
(*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<DataHandler,
ReadableNotification>
(*this, &DataHandler::socketReadable));
reactor.addEventHandler(socket, // (15)
Observer<DataHandler,
ShutdownNotification>
(*this, &DataHandler::socketShutdown));
socket.setBlocking(false);
}
~DataHandler() { // (16)
reactor.removeEventHandler(socket,
Observer<DataHandler,
ReadableNotification>
(*this, &DataHandler::socketReadable));
reactor.removeEventHandler(socket,
Observer<DataHandler,
ShutdownNotification>
(*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:
};
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<std::string>&) { // (2)
ServerSocket serverSocketEcho(4711); // (5)
ServerSocket serverSocketData(4712); // (6)
SocketReactor reactor;
SocketAcceptor<EchoHandler>
acceptorEcho(serverSocketEcho, reactor); // (7)
SocketAcceptor<DataHandler>
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) TCP sunucusunu oluşturun. Bu getiriyor mainişlev (2) ve (3)’te başlatılır ve (4)’te sıfırlanır. ONLAR main-TCP sunucu işlevi, 4711 5) ve 4712 (6) bağlantı noktalarını dinleyen iki sunucu soketi oluşturur. (7) ve (5)’te sunucu, dosyayla yuva yapar EchoHandler ve DataHandler oluşturuldu. ONLAR SocketAcceptor Alıcı-Bağlayıcı tasarım modelinin Alıcı bileşenini modeller. Reaktör, iptal talebini (10) alana kadar ayrı bir iş parçacığında (9) çalışır.
ONLAR EchoHandler yapıcıya (11) okuma tanıtıcısını kaydedin ve üye işlevindeki okuma tanıtıcısının kaydını kaldırın socketReadable (12) üzerinde. Müşteriden gelen mesajı döndürür (13). Tersine, DataHandler sunucuya veri iletmek için bir istemci. ONLAR Handler yapıcısında okuma olayları (14) ve kapatma olayları (satır 15) için eylemini kaydeder. İkisi birden Handler yok edici duruma gelmek DataHandler (16) tekrar abonelikten çıktı. Veri aktarımının sonucu doğrudan dosya tanıtıcısındadır outFile yazılı (17).
Aşağıdaki çıktı soldaki sunucuyu ve sağdaki iki istemciyi gösterir. Bir telnet oturumu istemci görevi görür. İlk istemci bağlantı noktasına bağlanır 4711: telnet 127.0.0.1 4711. Bu istemci yankı sunucusuna bağlanır ve ardından isteğini görüntüler. İkinci istemci 4712 numaralı bağlantı noktasına bağlanır: telnet 127.0.0.1 4712. Sunucudan gelen çıktı, istemci verilerinin sunucuya aktarıldığını gösterir.

Reaktörün artıları ve eksileri nelerdir?
Avantajlar ve dezavantajlar
Avantajlar
- Çerçeve ve uygulama mantığı arasında açık bir ayrım.
- Çeşitli somut olay yöneticilerinin modülerliği.
- Select, poll, epoll, kqueue veya WaitForMultipleObjects gibi çoğullama çözme olaylarının altında yatan işlevler Unix (select, epoll) ve Windows (WaitForMultipleObjects) platformlarında mevcut olduğundan, reaktör farklı platformlara taşınabilir.
- Arayüz ve uygulamanın ayrılması, hizmetlerin kolayca özelleştirilmesine veya genişletilmesine olanak tanır.
- Genel mimari paralel yürütmeyi destekler.
- Reaktör, olayların çoğullamasını çözmek için bir sistem çağrısı gerektirir.
- Uzun süre çalışan bir olay işleyici, reaktörü engelleyebilir.
- Kontrolün tersine çevrilmesi, test etmeyi ve hata ayıklamayı daha zor hale getirir.
Rekabet alanında kullanılan birçok kanıtlanmış model vardır. Paylaşım ve mutasyon gibi senkronizasyon sorunlarının yanı sıra rakip mimarilerle de ilgilenirler. Bir sonraki yazımda veri paylaşımına odaklanan şablonlarla başlayacağım.
(rm)
Haberin Sonu