вівторок, 26 липня 2011 р.

Розширення Boost.Asio

/*стаття не відредагована*/
Я вже досить давно використовую бібліотеку Boost.Asio у своєму проекті, в основному для організації асинхронного введення виведення і не раз ловив себе на думці, що хотів би використовувати її не тільки для цього. Насправді це не складно виправити, asio це не стільки мережева бібліотека, скільки дуже зручний фреймверк для вашого коду =) Отже, трохи теорії. Бібліотека asio дозволяє ефективно відокремити реалізацію від інтерфейсу використовуючи принцип dependency injection, для цього існують три сутності: io_object - інтерфейс надається користувачеві; service (не плутати з io_service-ом) - об'єкт реалізує взаємодію з ОС, наприклад з системою введення-виведення; implementation - зберігає стан об'єкта (наприклад хендл сокета). Все разом це виглядає так: io_object - клас успадкований від boost:: asio:: basic_io_object, Об'єкти цього класу створюються користувачем для виконання будь-яких операцій, приклади: boost:: asio:: deadline_timer; boost:: asio:: ip:: tcp:: socket; boost:: asio:: windows:: random_access_handle. Конструктор класу basic_io_object приймає покажчик на io_service. Кожному типу io_object-а відповідає свій service. Сервіс, це об'єкт безпосередньо виконує операції, програма користувача повинна взаємодіяти з сервісом не безпосередньо а через io_object відповідного типу, який виконує роль інтерфейсу. Сервіс автоматично створюється до створення першого примірника відповідного io_object-a, наприклад, для всіх сокетів створюється один сервіс (під windows це буде win_iocp_socket_service за назвою зрозуміло, що він використовує порти завершення:), для таймерів - інший (deadline_timer_service, котрий під windows так- ж використовує IOCP). io_service містить список сервісів для всіх об'єктів (io_object) які були на ньому створені (при створенні отримали посилання на цей io_service як параметр конструктора). basic_io_objectмістить два члени класу, з допомогою яких програміст може отримати доступ до сервісу та реалізації самого об'єкта - service і implementation. service - посилання на сервіс для даного об'єкта, який має тип basic_io_object:: Service_type, implementation - об'єкт типу basic_io_object:: Implementation_type - який є членом класу basic_io_object і представляє з себе реалізацію io_object-a, часом життя цього об'єкта управляє сервіс (волаючи методи construct і destroy). Допустимо у нас є такий код: boost:: asio:: io_service io; boost:: asio:: deadline_timer timer (io); У першому рядку буде створено io_service, він не міститиме жодного сервісу і якщо ми викличемо метод io.run (), він поверне управління відразу, тому що роботи в нього немає. У другому рядку все трохи цікавіше, спочатку буде створений io_object, в даному випадку це deadline_timer, в конструкторі basic_io_object-a (базового класу) буде викликана ф-я use_service яка спробує знайти відповідний сервіс (для таймерів він буде мати тип deadline_timer_service <... >) якщо для даного io_service-a такий сервіс ще не створено, то він бует створений, далі буде викликаний метод construct тільки-що створеного сервісу, завдання якого - ініціалізація implementation-a (що має тип deadline_timer_service:: implementation_type), який є членом класу basic_io_object і містить деталі реалізації таймера на даній платформі. Але це ще не все:), після виходу з scope-a буде викликаний деструктор таймера, який викличе метод destroy сервісу, як параметр в нього буде переданий timer.implementation. Далі в деструкції io_service-a буде викликаний метод shutdown_service сервісу дедлайн таймерів, а потім видалений екземпляр класу deadline_timer_service. Як я вже згадував, все це заради того, що-б розділити реалізацію та інтерфейс. Як приклад я реалізую клас (хоча насправді їх буде три:), для роботи з повідомленнями windows, який можна буде використовувати разом в бібліотекою boost:: asio. Для початку нам потрібно клас реалізує обробку повідомлень - implementation. Він повинен дозволяти встановлювати обробники повідомлень, видаляти їх, а так-же містити метод для обробки конкретного повідомлення.
Using Namespace Boost:: tuples;
Using Boost:: function;
Class messageloop_impl {
typedef Boost:: function < void ( Const MSG &, boost:: system:: error_code)> handler_type;
typedef Boost:: unordered_map < DWORD , handler_type> table_type ; table_type table_; boost:: asio:: io_service & io_; boost:: asio:: io_service:: work work_; boost:: mutex mutex_;
public : messageloop_impl (boost:: asio:: io_service & io_service): io_ (io_service), work_ (io_service) {}
Void set_handler ( DWORD ID, handler_type handler) {boost:: mutex:: scoped_lock lock (mutex_); table_type:: iterator I;
BOOL contain_handler; tie (i, contain_handler) = find_ (id);
if (contain_handler) runhandler_ (i, MSG (), boost:: asio:: error:: eof); table_.insert (std:: make_pair (id, handler));}
Void process_message ( Const MSG & msg, int & / * out * / processed) {boost:: mutex:: scoped_lock lock (mutex_); table_type:: iterator I;
BOOL contain_handler; tie (i, contain_handler) = find_ (msg.message);
if (contain_handler) {+ + processed ; runhandler_ (i, msg, boost:: system:: error_code ());}}
Void remove_handler ( DWORD ID) {boost:: mutex:: scoped_lock lock (mutex_); table_type:: iterator I;
BOOL contain_handler; tie ( i, contain_handler) = find_ (id);
if (contain_handler) {runhandler_ (i, MSG (), boost:: asio:: error:: eof); table_.erase (i);}}
Void Clear () {boost :: mutex:: scoped_lock lock (mutex_);
for (table_type:: iterator I = table_.begin (); i! = table_.end (); + + i) runhandler_ (i, MSG (), boost:: asio :: error:: eof); table_.clear ();}
private : tuple <table_type:: iterator , bool > find_ ( DWORD ID) {table_type:: iterator I = table_. find (id);
Return tuple <table_type: : iterator , bool > (i, i! = table_.end ());}
Void runhandler_ (table_type:: iterator I, Const MSG & m, boost:: system:: error_code e) {handler_type h = i-> second ; io_.post (boost:: bind (h, m, e));}};
Конструктор messageloop_impl - приймає посилання на io_service і зберігає її всередині об'єкта класу. Обробники повідомлень реалізовані на основі boost:: function, мають сигнатуру void (const MSG &, boost:: system:: error_code). Для зберігання обробників повідомлень використовується хеш таблиця table_. Ф-я set_handler додає обробник в хеш таблицю, ф-я remove_handler відповідно видаляє, метод clear видаляє всі обробники. Метод process_message обробляє повідомлення, у випадку, якщо для даного повідомлення знайдений обробник, значення змінної processed збільшується на одиницю, а обробник передається в io_service за допомогою методу post, далі він викликається методом run, poll або run_one io_service-a. Це потрібно для того, що-б наш обробник повідомлень слідував правилу, згідно з яким усі обробники можуть викликатися тільки в тих потоках, в яких був викликаний метод run (poll або run_one) відповідного io_service-a, або під час дзвінка деструктора io_service-a. Так-же даний клас містить член work_ має тип boost:: asio:: io_service:: work. io_service містить лічильник, який инкрементируется кожен раз, коли починається будь-яка операція, і декрементируется щоразу після завершення чергової операції. Метод run io_service-a не завершується доти, поки цей лічильник не буде дорівнює нулю. Зробивши об'єкт класу io_service:: work членом класу messageloop_impl, ми гарантуємо, що цей лічильник не обнулиться до тих пір, поки хоч один примірник messageloop_impl існує. Перед видаленням обробника, він викликається за другим параметром рівним boost:: asio:: error:: eof. Тепер напишемо код нашого сервісу для обробки повідомлень.
Class basic_messageloop_service: Public Boost:: asio:: io_service:: service {
public :
Static Boost:: asio:: io_service:: id id;
typedef Boost:: shared_ptr <messageloop_impl> implementation_type;
private : boost:: mutex mutex_; std: : Set <implementation_type> processors_; std:: set < DWORD > threads_;
Static Void send_WM_QUIT_to ( DWORD Thread) {
BOOL Result =:: PostThreadMessage (thread, WM_QUIT , 0, 0);
if (! result) {boost:: system :: error_code e = boost:: system:: error_code (:: GetLastError (), boost:: system:: system_category);
Throw Boost:: system:: system_error (e);}}
public :
Explicit basic_messageloop_service (boost:: asio:: io_service & io_service): boost:: asio:: io_service:: service (io_service) {} ~ basic_messageloop_service () {}
Void CONSTRUCT (implementation_type & impl) {impl.reset ( New messageloop_impl (get_io_service ())); boost: : mutex:: scoped_lock lock (mutex_); processors_.insert (impl);}
Void Destroy (implementation_type & impl) {boost:: mutex:: scoped_lock lock (mutex_); processors_.erase (impl); impl.reset (); }
Void shutdown_service () {boost:: mutex:: scoped_lock lock (mutex_); std:: for_each (threads_.begin (), threads_.end (), & basic_messageloop_service:: send_WM_QUIT_to); threads_.clear ();}
template < Class Handler >
Void set_handler (implementation_type & impl, DWORD ID, Handler Handler) {impl-> set_handler (id, handler);}
Void remove_handler (implementation_type & impl, DWORD ID) {impl-> remove_handler (id);}
Void Clear (implementation_type & impl) {impl-> clear ();}
Void Loop () {
MSG MSG;
DWORD thread_id =:: GetCurrentThreadId (); {boost:: mutex:: scoped_lock lock (mutex_); threads_.insert (thread_id);}
while (:: GetMessage (& msg, NULL , 0, 0)) {boost:: mutex:: scoped_lock lock (mutex_);
INT proc_cnt = 0; std:: for_each (processors_.begin (), processors_.end (), boost :: bind (& messageloop_impl:: process_message, _1, msg, boost:: ref (proc_cnt)));
if (proc_cnt == 0):: DispatchMessage (& msg);} {boost:: mutex:: scoped_lock lock (mutex_) ; threads_.erase (thread_id);}}}; boost:: asio:: io_service:: id basic_messageloop_service:: id;
Отже, наш клас повинен бути спадкоємцем класу boost:: asio:: io_service:: service, а так-же мати статичний член id має тип boost:: asio:: io_service:: id, який є унікальним ідентифікатором сервісу. Так-же клас повинен визначати тип implementation_type, в даному випадку це boost:: shared_ptr, Від якого залежить тип змінної класу basic_io_object:: Implementation, тобто реалізації об'єкта обробника повідомлень. Сервіс повинен вміти ініціалізувати об'єкти мають тип implementation_type за допомогою методу construct і деініціалізіровать їх методом destroy. Так-же він містить ряд методів для управління екземплярами класу implementation_type а так-же метод loop, в якому реалізований цикл обробки повідомлень. Наприклад, метод set_handler, він приймає два параметри, перший (impl) має тип implementation_type, другий (id) - ід-р повідомлення і третій (handler) - обробник повідомлення, реалізований просто як виклик impl-> set_handler (id, handler), тоесть просто делегує виклик реалізації. Метод loop, реалізований таким чином, спочатку він запам'ятовує в безлічі threads_ ідентифікатор потоку в якому він викликаний, потім у циклі отримує повідомлення і передає їх обробникам (які сервіс запам'ятовує в безлічі processors_ під час створення кожного з них) а після отримання повідомлення WM_QUIT він видаляє з безлічі thread_ ідентифікатор потоку в якому виконувався. Завдяки цьому можна запустити кілька циклів обробки повідомлень в різних потоках і один обробник зможе отримувати повідомлення з будь-якого. Ну і останнє, метод shutdown_service, повинен привести до завершення всіх циклів обробки повідомлень, тому він просто посилає всім їм повідомлення WM_QUIT. Ну і саме останнє що нам потрібно зробити - реалізувати io_object. Це дуже просто зробити.
template < typename Service>
Class basic_messageloop: Public Boost:: asio:: basic_io_object <Service> {
public :
Explicit basic_messageloop (boost:: asio:: io_service & io_service): boost:: asio:: basic_io_object <Service> (io_service) {}
Void Loop () {
this -> service.loop ();}
template < Class Handler >
Void set_handler ( DWORD ID, Handler Handler) {
this -> service.set_handler ( this -> implementation, id, handler);}
template < Class Handler >
Void set_handler ( LPCTSTR Name, Handler Handler) {
DWORD msg_code =:: RegisterWindowMessage (name);
this -> service.set_handler ( this -> implementation, msg_code, handler);}};
typedef basic_messageloop <basic_messageloop_service> messageloop;
Єдина вимога - клас повинен бути спадкоємцем boost:: asio:: basic_io_object, Де Service - наш сервіс обробників повідомлень. У цьому класі метод set_handler просто викликає метод set_handler свого сервісу і передає в нього свою реалізацію (implementation) та додаткові параметри. Загалом цей клас - просто інтерфейс, він не повинен мати стан (хоча його батько має), а просто перенаправляти всі виклики своєму сервісу. Юзати це можна так:
boost:: asio:: io_service io; asio_aux:: messageloop message_loop1 (io); asio_aux:: messageloop message_loop2 (io); message_loop1.set_handler ( WM_CLOSE , & message_handler); message_loop1.set_handler ( L " MyMessage " , & message_handler); message_loop2. set_handler ( WM_CLOSE , & message_handler); message_loop2.set_handler ( L " MyAnotherMessage " , & message_handler); boost:: thread thr (boost:: bind (& boost:: asio:: io_service:: run, & io)); message_loop1.loop () ;

Немає коментарів:

Дописати коментар