Наша совместная команда Banwar.org

Связаться с нами

  • (097) ?601-88-87
    (067) ?493-44-27
    (096) ?830-00-01

Статьи

Набір об'єктів-нотіфікатор

  1. Проблеми взаємодії клієнта і сервера
  2. Об'єкт ядра "подія"
  3. повідомлення потоку
  4. повідомлення вікна
  5. Виклик процедури клієнта
  6. література
  7. Робочий приклад

Олександр Малигін
дата публікації 09-07-2001 00:00

Дуже часто в структурі програми або пакета програм можна виділити функціональні модулі, які обслуговують інші модулі. Тобто, клієнт-серверна архітектура (в широкому сенсі слова) присутній в будь-якому мало-мальськи складному проекті. У загальному випадку сервер виконує якісь дії за завданням клієнта. Клієнтів, як правило, буває кілька, і функціонує сервер відокремлено (зв'язки з іншими модулями мінімальні і строго обумовлені).

Проблеми взаємодії клієнта і сервера

При синхронному взаємодії клієнт викликає процедуру / функцію / метод сервера, і коли останній повертає управління, завдання вже виконана. А що, якщо виконання триває багато довше, ніж клієнт може чекати? Це призводить до асинхронної моделі взаємодії клієнта і сервера. Клієнт дає завдання сервера і продовжує займатися своєю справою. Сервер по закінченню роботи повинен сповістити клієнта будь-яким чином.

Найдавніший відомий спосіб - виставляння програмного прапора - є і найгіршим рішенням, оскільки змушує клієнта періодично перевіряти цей прапор. Як би зробити так, щоб клієнт отримував повідомлення найприроднішим для нього способом і не витрачав процесорний час на тупе очікування і навіть на перевірку?

Тут слід уточнити, що описувані проблеми найбільш актуальні при взаємодії клієнта і сервера, що виконуються в різних потоках або в різних процесах. Сучасна наука надає наступний вибір засобів нотифікації, не прив'язаних до високорівневих технологій:

  • об'єкти ядра ОС для синхронізації потоків (події, семафори, м'ютекси)
  • повідомлення потоку
  • повідомлення вікна
  • виклик процедури клієнта

Об'єкт ядра "подія"

Найбільш зручним з об'єктів ядра для нашої мети представляється подія (event). Активізується він викликом функції Win32API SetEvent, а контролюється на клієнті наступними функціями:

WaitForSingleObject WaitForMultipleObjects MsgWaitForMultipleObjects

Остання дозволяє виконувати очікування сигналу в циклі отримання / обробки повідомлень.

повідомлення потоку

Повідомлення потоку надсилається функцією PostThreadMessage, і для його отримання потік не зобов'язаний мати вікно, досить містити виклики функцій GetMessage або PeekMessage.

повідомлення вікна

Повідомлення вікна зручно використовувати, якщо клієнтом є модуль з формою. Тоді досить в класі форми зробити обробник цього повідомлення:

procedure WMMyMessage (var Msg: TMessage); message WM_MYMESSAGE;
тут код повідомлення визначено, наприклад, так: const WM_MYMESSAGE = WM_USER + XXXX;

Повідомлення надсилається функцією PostMessage, а не SendMessage, щоб сервер міг продовжити свою роботу, не чекаючи, поки клієнт обробить повідомлення. Таким властивістю володіють всі вищеописані способи сповіщень.

До речі, метод Synchronize (Method: TThreadMethod) класу TThread використовує для спілкування з головним потоком програми саме віконне повідомлення, що посилається через SendMessage. При цьому заданий в параметрах виклику Synchronize метод класу виконується в контексті головного потоку (main VCL thread), і його код є потокобезпечна (може звертатися до будь-яких об'єктів VCL). Але (інша сторона медалі) поки наш клієнт в головному потоці зайнятий фактично виконанням цього методу або іншими справами (повідомлення ставляться в чергу), сервер не може продовжити роботу - він завмер на виклик SendMessage. Часто це дуже небажано.

Виклик процедури клієнта

Асинхронний виклик процедури (функції) клієнта - один з найдавніших способів взаємодії сервера з клієнтом. Це і обробники переривань (апаратних і програмних), це і callback-функції, які використовуються в Win32API, і т.д. Суть в наступному: клієнт повідомляє серверу адресу своєї процедури (і, можливо, деякі параметри для її виклику), а потім сервер в потрібний момент її викликає.

Головний недолік - процедура виконується в контексті викликає потоку (сервера), і програміст повинен сам вжити необхідних заходів для синхронізації з потоком клієнта, якщо є звернення до даних, що розділяються. Крім того, процедура повинна виконатися досить швидко, щоб не затримувати роботу сервера (всілякі очікування і SendMessage, явні і неявні, тут неприпустимі). Внаслідок деякої суперечливості даних вимог можна рекомендувати наступний метод роботи: виконати в асинхронної процедурі якісь оперативні обчислення, а потім сповістити потік клієнта способом, що не вимагає затримки (наприклад - PostMessage), і швиденько віддати управління сервера.

Асинхронний виклик методу класу (procedure of object) є різновидом даного методу.

література

Детальний розгляд згаданих функцій API виходить за рамки цієї статті, тому що цікавляться відсилаю до довідки Delphi по Win32 (вона, на мою скромну думку, організована краще, ніж MSDN), а також до книги Джеффрі Ріхтера "Windows для професіоналів".

Робочий приклад

Ну, тепер - про головне. До уваги шановної публіки пропонується простий модуль з ієрархією об'єктів, що реалізують вищеописані методи нотифікації.

Для зручності використання серверами, що знаходяться в DLL, і експортують тільки функції (не клас), необхідні клієнту визначення винесені в окремий unit:

(************************************************* ******************** * Notifier object definitions * ************************* ********************************************) unit NotifyDef; interface uses Windows; const tnEvent = 0; // Use kernel object Event (SetEvent) tnThreadMsg = 1; // Use message to thread ID (PostThreadMessage) tnWinMsg = 2; // Use message to window HWND (PostMessage) tnCallBack = 3; // Asynchronous call user function TNotifierProc tnCallEvent = 4; // Asynchronous call object event handler TNotifierEvent type TNotifierProc = procedure (Owner: THandle; Msg, UserParam: dword); TNotifierEvent = procedure (Sender: TObject; Msg, UserParam: dword) of object; implementation end.

Для створення об'єкта нотіфікатор заданого типу сервер може використовувати функцію (див. Модуль Notify.pas ):

function MakeNotifier (hEventObj, hSender: THandle; EventType: byte; MsgID, UserParam: dword): TNotify; Опис параметрів Параметр Призначення Значення, використання в способах нотифікації Подія Повідомлення потоку Повідомлення вікна Процедура клієнта hEventObj Хендл низкоуровнего об'єкта, який використовується для нотифікації хендл події - результат виклику CreateEvent ID потоку (можна дізнатися GetCurrentThreadID) хендл вікна адреса процедури hSender Умовний хендл об'єкта сервера - то, що сервер хоче повідомити про себе не використовується TMessage.LParam TMessage.LParam Owner в TNotifierProc EventType Тип об'єкта нотіфікатор - см. константи в модулі NotifyDef.pas tnEvent tnThreadMsg tnWinMsg tnCallBack MsgID Ідентифікатор повідомлення не використовується TMessage.Msg TMessage.Msg Msg в TNotifierProc UserParam користувача параметр не використовується TMessage.WParam TMessage.WParam UserParam в TNotifierProc Власне, параметри hSender, MsgID, UserParam можуть бути заряджені довільними даними на розсуд програміста, нотіфікатор не використовують їх для своїх потреб. Нотіфікатор типу "асинхронний виклик обробника події" (tnCallEvent) не можна створити через функцію MakeNotifier - потрібно явний виклик конструктора. Це пов'язано з тим, що адреса TNotifierEvent не можна привести до чотирибайтових типу THandle. Параметри конструктора аналогічні параметрам MakeNotifier при EventType = tnCallback.

Далі наводиться власне текст юніта реалізації бібліотеки нотіфікатор.

(************************************************* ***************************** * The Collection of Notifier objects. * ************* ************************************************** ***************) unit Notify; interface uses Windows, NotifyDef; type TNotify = class protected hNotify: THandle; hOwner: THandle; Message, UParam: dword; public constructor Create (hEventObj: THandle); procedure Execute; virtual; property Owner: THandle read hOwner write hOwner; property Param: dword read UParam write UParam; end; TThreadNotify = class (TNotify) public constructor Create (hEventObj, hSender: THandle; MsgID, UserParam: dword); procedure Execute; override; end; TWinNotify = class (TThreadNotify) public procedure Execute; override; end; TCallBackNotify = class (TThreadNotify) public procedure Execute; override; end; TCallEventNotify = class (TThreadNotify) private fOnNotify: TNotifierEvent; public constructor Create (hEventObj: TNotifierEvent; hSender: THandle; MsgID, UserParam: dword); property OnNotify: TNotifierEvent read fOnNotify write fOnNotify; procedure Execute; override; end; function MakeNotifier (hEventObj, hSender: THandle; EventType: byte; MsgID, UserParam: dword): TNotify; implementation function MakeNotifier (hEventObj, hSender: THandle; EventType: byte; MsgID, UserParam: dword): TNotify; begin case EventType of tnEvent: result: = TNotify.Create (hEventObj); tnThreadMsg: result: = TThreadNotify.Create (hEventObj, hSender, MsgID, UserParam); tnWinMsg: result: = TWinNotify.Create (hEventObj, hSender, MsgID, UserParam); tnCallBack: result: = TCallBackNotify.Create (hEventObj, hSender, MsgID, UserParam); else result: = nil; end; end; (*** TNotify ***) constructor TNotify.Create (hEventObj: THandle); begin hNotify: = hEventObj; end; procedure TNotify.Execute; begin SetEvent (hNotify); end; (*** TThreadNotify ***) constructor TThreadNotify.Create (hEventObj, hSender: THandle; MsgID, UserParam: dword); begin inherited Create (hEventObj); Owner: = hSender; Message: = MsgID; UParam: = UserParam; end; procedure TThreadNotify.Execute; begin PostThreadMessage (hNotify, Message, UParam, hOwner); end; (*** TWinNotify ***) procedure TWinNotify.Execute; begin PostMessage (hNotify, Message, UParam, hOwner); end; (*** TCallbackNotify ***) procedure TCallbackNotify.Execute; begin TNotifierProc (hNotify) (hOwner, Message, UParam); end; (*** TCalleventNotify ***) constructor TCalleventNotify.Create (hEventObj: TNotifierEvent; hSender: THandle; MsgID, UserParam: dword); begin OnNotify: = hEventObj; Owner: = hSender; Message: = MsgID; UParam: = UserParam; end; procedure TCalleventNotify.Execute; begin if assigned (OnNotify) then OnNotify (TObject (Owner), Message, UParam); end; end.

Порядок роботи дуже простий. Сервер инициализирует екземпляр потрібного типу, скориставшись функцією MakeNotifier або прямим викликом конструктора. Віртуальний метод TNotify.Execute реалізує заданий спосіб нотифікації, саме його сервер викликає, коли потрібно виконати сповіщення клієнта.

Ця бібліотека народилася внаслідок практичної необхідності як результат узагальнення напрацювань на дану тему. Тепер, створюючи серверний модуль, я забезпечую його цим набором нотіфікатор, щоб він міг надати клієнту вільний вибір способу сповіщення.

Практичним прикладом використання об'єктів-нотіфікатор є таймерний менеджер, якому буде присвячена наступна стаття під назвою " Таймер, який не підведе ".

Олександр Малигін
Спеціально для Королівства Delphi


[ TObject ] [ TForm ] [ TEvent ] [ TThread ] [ Об'єкти синхронізації системи ]
Обговорення матеріалу [29-04-2009 13:26] 6 повідомленьА що, якщо виконання триває багато довше, ніж клієнт може чекати?
Як би зробити так, щоб клієнт отримував повідомлення найприроднішим для нього способом і не витрачав процесорний час на тупе очікування і навіть на перевірку?

Новости

Banwar.org
Наша совместная команда Banwar.org. Сайт казино "Пари Матч" теперь доступен для всех желающих, жаждущих волнения и азартных приключений.