Изпращане на Delphi съобщения. Делфи

Изпращане на Delphi съобщения. Делфи

Последователност за обработка на съобщения в Delphi
Всички класове на Delphi имат вграден механизъм за обработка на съобщения, наречен манипулатори на съобщения. Класът получава съобщение и извиква един от набор от дефинирани методи в зависимост от полученото съобщение. Ако съответният метод не е дефиниран, тогава се извиква манипулаторът по подразбиране. По-подробно този механизъм работи по следния начин.

След получаване на съобщение VCL системата за съобщения извършва много предварителна работа, за да го обработи.

Както беше отбелязано по-горе, съобщението първоначално се обработва от метода TApplication.ProcessMessage, който го избира от опашката в главния цикъл на съобщенията. В същото време той проверява съдържанието на полето FOnMessage (всъщност проверява за наличието на манипулатор за събитието OnMessage) и ако не е празно, извиква манипулатора за това събитие, а ако полето е празно ( Нищо), след това извиква API функцията DispatchMessage(Msg). Това не се случва при изпращане на съобщение.

Ако манипулаторът на събитие OnMessage не е дефиниран, тогава функцията DispatchMessage API се извиква за обработка на полученото съобщение, което предава съобщението на главната процедура на прозореца.

Нека разгледаме цикъла за обработка на съобщението, след като пристигне в главния прозорец на компонента. Последователността на обработка на съобщението е показана на следната фигура:

Може да се види, че съобщението се предава на MainWndProc, след това на WndProc, след това на Dispatch, след това на DefaultHandler.

Delphi има основен невиртуален метод MainWndProc(Var Message: TMessage) за прозореца на всеки компонент. Той съдържа блок за обработка на изключения, предаващ структурата на съобщението от Windows към виртуалния метод, дефиниран в свойството WindowProc. Този метод обаче обработва всички изключения, които възникват по време на обработката на съобщението, като извиква метода HandleException на приложението. Започвайки от това място, можете да предоставите специално отношениесъобщения, ако това се изисква от логиката на вашата програма. Обикновено на този етап обработката се променя, за да се предотврати извършването на стандартната VCL обработка.

По подразбиране стойността на свойството WindowProc на обекта се инициализира към адреса на виртуалния метод WndProc. Освен това, ако няма регистрирани куки за съобщения TWindowHook, WndProc извиква виртуалния метод TObject.Dispatch, който, използвайки полето Msg на структурата на входящото съобщение, определя дали това съобщение е в списъка с манипулатори на съобщения за този обект. Ако обектът не обработва съобщението, се проверява списъкът с манипулатори на предходни съобщения. Ако в крайна сметка бъде намерен такъв метод, той се извиква; в противен случай се извиква виртуалният метод DefaultHandler.

Накрая съобщението достига до съответната процедура за обработка, където се извършва предназначената за него обработка. Като се използва ключова думаНаследеното се изпраща допълнително за обработка в предци. След това съобщението попада и в метода DefaultHandler, който извършва последните действия по обработката на съобщението и го предава на процедурата DefWindowProc (DefMDIProc) за стандартна обработка на Windows.

Обработка на съобщения с компоненти на Delphi
По този начин, Кратко описаниепоследователността на обработка на съобщенията е както следва. Всички съобщения първоначално преминават през метода, чийто адрес е посочен в свойството WindowProc. По подразбиране това е методът WndProc. След това те се разделят и изпращат според техните методи на съобщение. Накрая те отново се събират в метода DefaultHandler, ако не са били обработени по-рано или наследеният манипулатор (Inherited) се извиква в манипулаторите. Следователно компонентите на Delphi имат следните възможности за обработка на съобщения:
а) Преди някой манипулатор на съобщения да види съобщението. В този случай се изисква или замяната на адреса на метода в свойството WindowProc, или замяната на метода TControl.WndProc.
Свойството WindowProc се декларира, както следва:

Туре TWndMethod= процедура(Променливо съобщение: TMessage) на обект;
Имот WindowProc: TWndMethod;

Всъщност, използвайки свойството WindowProc, можете да създадете метод от тип TWndMethod и временно да замените оригиналния метод със създадения, но тъй като адресът на метода не се съхранява в свойството WindowProc, първо трябва да съхраните адреса на оригиналния WndProc метод, така че да може да бъде възстановен по-късно.

OldWndProc: TWndMethod;
процедура NewWndProc(променливо съобщение: TMessage);
процедура TForm1.NewWndProc(променливо съобщение: TMessage);
var Ch: char;
започвам
if message.Msg= WM_MOUSEMOVE тогава започнете
Edit1.Text:='x='+inttostr(message.LParamLo)+', y='+inttostr(message.LParamHi);
край
иначе WndProc(Съобщение);
край;

процедура TForm1.FormCreate(Подател: TObject);
започвам
OldWndProc:=WindowProc;
край;

процедура TForm1.CheckBox1Click(Подател: TObject);
започвам
Ако CheckBox1.Checked тогава WindowProc:= NewWndProc
иначе WindowProc:= OldWndProc;
край;

b) В рамките на съответния метод на съобщение.
Да вземем друг подобен пример.
Използвайте съобщението, изпратено до компонентите, за да преначертаете WMPAINT.

В класа TForml ще декларираме този метод, за да го заменим и да представим изпълнението на заменения метод на съобщението:

Тип TForml=Class(TForm)
… // Всички други необходими декларации
защитени
Процедура WMPaint(Var Msg: TWMPaint); Съобщение WM_PAINT; край;
Процедура TForml.WMPaint(Var Msg: TWMPaint); Започнете
If CheckBox1.Checked Then ShowMessage('O6pa6o проверка на съобщения!');
Наследени;
край;

Когато заменяте конкретни манипулатори на съобщения, винаги е добра идея да извикате Inherited, за да извършите основната обработка на съобщения, от която Windows се нуждае.

c) След като всеки от методите, съответстващ на съобщението, го види.

В този случай трябва да замените DefaultHandler.

процедура DefaultHandler(varMessage); отмяна;
procedure TForm1.DefaultHandler(var Message);
var i:integer;
започвам
if Cardinal(Message)=WM_defh тогава
за i:= 0 до 10 направете начало
звуков сигнал;
сън(100);
край
друго
наследени;
край;

процедура TForm1.Button5Click(Подател: TObject);
започвам
Изпращане на съобщение (манипулатор, WM_defh, 0,0);
край;

Комуникация между съобщения и събития
Много събития на Delphi VCL са пряко свързани със съобщенията на Windows. IN помощна система Delphi изброява тези съвпадения. Те са представени в табл.1.

маса 1

VCL събитиеWindows съобщениеVCL събитиеWindows съобщение
OnActivateWM_ACTIVATEOnKeyPressWM_CHAR
onclickWM_LBUTTONDOWNOnKeyUpWM_KEYUP
OnCreateWM_CREATEOnPaintWM_PAINT
OnDblClickWM_LBUTTONDBLCLKOnResizeWM_SIZE
OnKeyDownWM_KEYDOWNOnTimerWM_TIMER

Не трябва да създавате манипулатори на съобщения, ако за тях има предварително дефинирано събитие. В тези случаи има смисъл да се използва обработка на събития, защото е по-малко ограничаваща.

При разработването на приложения може да има ситуация, в която дадено приложение трябва да изпрати съобщение до себе си или до друго потребителско приложение. Някои може да са озадачени от предишното твърдение: защо приложението трябва да изпраща съобщение до себе си, когато можете просто да извикате подходящата процедура? Това Добър въпрос, и има няколко отговора на него. Първо, използването на съобщения е механизъм за поддържане на реален полиморфизъм, тъй като не изисква никакви познания за типа на обекта, получаващ съобщението. По този начин технологията за съобщения има същата сила като механизма на виртуалния метод, но има много по-голяма гъвкавост. Второ, съобщенията позволяват незадължителна обработка - ако обектът на получателя не обработи входящото съобщение, тогава няма да се случи нищо ужасно. И, трето, съобщенията ви позволяват да излъчвате до множество получатели и да организирате паралелно слушане, което е доста трудно да се приложи с помощта на механизма за извикване на процедури.

Използване на съобщения в приложение

Да накарате приложение да изпрати съобщение до себе си е много лесно - просто използвайте функциите API интерфейс Win32 SendMessage() или PostMessage() или метода Perform(). Съобщението трябва да има идентификатор в диапазона WM_USER+100 до $7FFFF (който Windows запазва за потребителски съобщения). Например: const

SX_MYMESSAGE = WM_USER + 100;

SomeForm.Perform(SX_MYMESSAGE, 0, 0);

SendMessage(SomeForm.Handle, SX_MYMESSAGE, 0, 0);

PostMessage(SomeForm.Handle, SX_MYMESSAGE, 0, 0);

След това, за да прихванете това съобщение, създайте нормална манипулаторна процедура, която изпълнява необходимите действия във формуляра:

TForm1 = клас (TForm)

процедура SXMyMessage(var Msg: TMessage); съобщение SX_MYMESSAGE;

процедура TForm1.SXMyMessage(вар. Msg: TMessage);

MessageDlg('Тя ме превърна в тритон!',

mtInformation, , 0);

Както можете да видите от примера, има малка разлика в това как се обработва собствено съобщение от стандартно съобщение на Windows. Те се състоят от използване на идентификатори, вариращи от WM_USER+100 и по-горе, както и даване на име на всяко съобщение, което по някакъв начин ще отразява значението му.

Никога не изпращайте съобщения със стойност на WM_USER, по-голяма от $7FFF, освен ако не сте абсолютно сигурни, че получателят е в състояние да обработи съобщението правилно. Тъй като всеки прозорец може самостоятелно да избира стойностите, които използва, е много вероятно да възникнат фини грешки, освен ако не създадете предварително таблици с идентификатори на съобщения, с които всички податели и получатели на съобщения ще работят.

Съобщения между приложения

Ако трябва да обменяте съобщения между две или повече приложения, трябва да използвате API функцията RegisterWindowMessage() в тях. Този метод гарантира, че за даден тип съобщение всяко приложение ще използва същото номер на съобщението(номер на съобщение).

Функцията RegisterWindowMessage() приема като параметър низ с

завършва с нулев знак и връща идентификатор в диапазона $C000 - $FFFF за новото съобщение. Това означава, че извикването на тази функция със същия низ като параметър във всяко приложение ще бъде достатъчно, за да гарантира едни и същи номера на съобщения във всички приложения, участващи в обмена. Друго предимство на такава функция е, че системата гарантира, че идентификаторът, присвоен на даден ред, е уникален. Това позволява излъчени съобщения да бъдат изпращани до всички съществуващи прозорци в системата без страх от нежелани странични ефекти. недостатък този методе известно усложнение при обработката на такива съобщения. Изводът е, че идентификаторът на съобщението е известен само когато приложението работи, така че използването на стандартните процедури за обработка на съобщения не е възможно. За да работите с такива съобщения, трябва да предефинирате стандартни методи WndProc() или DefaultHandler() контроли, или съответните процедури на клас прозорец.

НА ЗАБЕЛЕЖКА

Числото, върнато от функцията RegisterWindowMessage(), се генерира динамично и може да приеме различни значенияв различни Windows сесии, което означава, че не може да бъде определено, докато програмата не бъде изпълнена.

Излъчване на съобщения

Всеки клас, извлечен от класа TWinControl, позволява използването на метода Broadcast() за изпращане излъчено съобщение(излъчвано съобщение) към всеки контрол, чийто собственик е. Тази техника се използва, когато се изисква да се изпрати едно и също съобщение до група компоненти. Например, за да изпратите персонализирано съобщение с име um_Foo до всички контроли, които принадлежат към обекта Panel1, можете да използвате следния код:

Съобщение:= UM_FOO;

Разработете програма, която ще предостави интерфейс за използване на стандартната команда за изпращане на съобщения в мрежата на Win2000/XP. Позволете на потребителя да посочи адреса на получателя, текста на съобщението и броя на съобщенията, които да бъдат изпратени. Също така предвидете възможност за задаване на блок за получаване на съобщения от други компютри.

Развитие на формата

Създайте нов Delphi проект. Променете заглавието на формуляра (свойство Caption) на Net Sender. Поставете три компонента на етикета на категорията един над друг по левия край на формуляра стандартени задайте свойството Caption на IP адрес:, Съобщение: и Количество:.

До всеки от етикетите поставете компонент Редактиране на категорията стандартен. Наименувайте горния ip (свойство Name) и присвоете стойността 192.168.0.1 на свойството Text; наименувайте средното поле txt и присвоете някакъв текст на съобщението по подразбиране към свойството Text; Наименувайте долното поле как и задайте свойството Text на 1.

Под изброените компоненти поставете компонента на полето за отметка на категорията стандартен. Наречете го защитено, задайте свойството Caption на Disable receive messages и задайте свойството Checked на True.

Поставете бутон в най-долната част на формуляра (компонентът Button на стандартен), като зададете свойството Caption на Send. Нуждаем се също от таймер (компонента Timer на Система), за което свойството Interval трябва да бъде зададено на 10.

Получената форма трябва да съответства на фиг. 15.1.

Ориз. 15.1. Форма за изпращане на съобщения от програмата локална мрежа

Разработка на програмен код

Първо, нека напишем собствена процедура за бомба, която ще прочете всички настройки и ще изпрати съобщение. Декларирайте тази процедура като частен член на класа на формуляра:

Нуждаем се също от глобална променлива i от тип integer:

Сега нека създадем имплементация на процедурата за бомба в раздела за реализация:

процедура TForm1.bomb();
ако how.Text= "" тогава how.Text:= "1";
if ip.Text = "" тогава ip.Text:= "127.0.0.1";(ако ip-адресът не е посочен, тогава го изпращаме на локалния компютър)
WinExec(PChar("net send " + ip.Text + """ + txt.Text + """), 0);//изпрати съобщение

Тази процедура проверява дали всички задължителни полета са попълнени. Ако няма текст на съобщението, поставете знака "!"; ако IP адресът не е посочен, тогава изпращаме съобщение до локалния компютър с адрес 127.0.0.1; ако броят на съобщенията не е посочен, тогава изпращаме едно съобщение. Съобщенията се изпращат с помощта на стандартната команда net send, която има следния синтаксис:

net изпрати съобщение за ip адрес.

Сега нека се справим със събитието OnTimer на таймера:

h: HWND;//съхранява ID на прозореца
ако не е защитено. Проверено тогава//ако квадратчето не е отметнато
Timer1.Enabled:= False;//деактивиране на наблюдението
ако е защитено. Проверено тогава//ако квадратчето е отметнато
//потърсете прозорци със съобщения
h:= FindWindow(nil, "Услуга за съобщения ");// затворете всички намерени прозорци
ако з<>

Ако има отметка в квадратчето Забраняване на получаването на съобщения, тогава започваме да наблюдаваме прозорци, чието заглавие казва, че това е съобщение, и затваряме всички намерени прозорци. Ако квадратчето за отметка не е отметнато, наблюдението е деактивирано.

За да можете да превключвате между тези два режима, трябва да създадете манипулатор на събития secure.OnClick:

ако е защитено. Проверено тогава//ако квадратчето е отметнато...
Timer1.Enabled:= True;//... активиране на наблюдението

Когато натиснете бутон изпратипросто ще извикаме процедурата бомба:

За да улесним живота на потребителя, ние ще се погрижим съобщението да се изпраща и чрез натискане на клавиша във всяко поле за въвеждане на текст. За да направите това, трябва да създадете манипулатор на събития OnKeyPress за всяко от полетата. Кодът за този манипулатор за полето ip, което след това може да бъде присвоено на полетата txt и how:

ако е ключ= #13 тогава//ако е натиснат клавиш
бомба;//изпрати съобщение

Изходен код на пълен модул

Пълният код за програмния модул за LAN съобщения е показан в листинг 15.1.

Листинг 15.1. Програмен модул за LAN съобщения

Windows, съобщения, SysUtils, варианти, класове, графики, контроли, формуляри, диалогови прозорци, StdCtrl, ExtCtrl;

процедура Timer1Timer(Sender: TObject);
процедура secureClick(Подател: TObject);
процедура ipKeyPress(Подател: TObject; var Key: Char);
процедура txtKeyPress(Подател: TObject; var Key: Char);
процедура howKeyPress(Sender: TObject; var Key: Char);
процедура Button1Click(Подател: TObject);


// проверка дали текстовото съобщение не е празно
ако txt.Text = "" тогава txt.Text:= "!";
//ако количеството не е посочено, изпратете едно съобщение
if how.Text= "" then how.Text:= "1";
if ip.Text = "" тогава ip.Text:= "127.0.0.1"; (ако ip-адресът не е посочен, тогава го изпращаме на локалния компютър)
//изпрати определения брой съобщения
for i:=1 to StrToInt(how.Text) do
WinExec(PChar("net send " + ip.Text + """ + txt.Text + """), 0); //изпрати съобщение

процедура TForm1.Timer1Timer(Подател: TObject);
h: HWND; //съхранява ID на прозореца
if not secure.Checked then //ако квадратчето за отметка не е отметнато
Timer1.Enabled:= False; //деактивиране на наблюдението
if secure.Checked then //ако квадратчето е отметнато
//потърсете прозорци със съобщения
h:= FindWindow(nil, "Услуга за съобщения "); // затворете всички намерени прозорци
ако з<>0 след това PostMessage(h, WM_QUIT, 0, 0);

процедура TForm1.secureClick(Подател: TObject);
if secure.Checked then //ако квадратчето е отметнато...
Timer1.Enabled:= True; //... активиране на наблюдението

procedure TForm1.ipKeyPress(Sender: TObject; var Key: Char);
if key = #13 then //ако е натиснат клавиш
бомба; //изпрати съобщение

процедура TForm1.Button1Click(Подател: TObject);

⊚ Всички файлове на проекта и изпълнимият файл на разглежданата програма се намират на компактдиска, прикачен към книгата в папката Глава 15.

Често програмите на Delphi използват имейл инструменти. Тази статия ще обясни напълно как вашият имейл се изпраща до друг потребител. използвайки delphi. В този случай ще използваме само стандартни Delphi компоненти.

Като начало, нека създадем нов проект и го кръстим \"Изпращане на имейли чрез delphi\". След това няколко компонента 1x Memo, 3x Edit, 2x Botton трябва да бъдат прехвърлени към формуляра и IdSMTP, IdAntiFreeze, IdMessage също трябва да бъдат прехвърлени. След това при събитието onclick на произволен бутон пишем:

//избирам SMTP сървър. IN този моментструва yandex. IdSMTP1.Host:= "smtp.yandex.ru"; //вашето логин (за някои е необходимо да се пише с домейн). IdSMTP1.Потребителско име:=" [имейл защитен]"; //парола за поща. IdSMTP1.Password:= "qwerty123"; //порт, препоръчваме да използвате 587. IdSMTP1.Port:=587; //темата на съобщението ще се побере в Edit2. IdMessage1.Subject:= Edit2 .Text; // Edit1 ще съдържа адреса на получателя. IdMessage1.Recipients.EMailAddresses:= Edit1.Text; //вашият имейл, от който отива подателят. IdMessage1.From.Address:= " [имейл защитен]"; // memo1 ще съдържа текста, който искате да изпратите. IdMessage1.Body.Text:= memo1.Text ; // Edit3 ще съдържа вашия електронен подпис(Име). IdMessage1.From.Name:= Edit3.Text; // свързване IdSMTP1.connect; //изпращане на IdSMTP1.Изпращане(IdMessage1); //прекъсване на връзката IdSMTP1.Прекъсване на връзката;

Ако IdMessage показва въпросителни знаци

Тази грешка е свързана с факта, че въвеждате руски букви и компонентът memo не може да ги прочете правилно, за това трябва да посочите кодирането Utf8.

// задаване на кодиране IdMessage1.Charset:="UTF-8"; // превеждане на текста в необходимото кодиране IdMessage1.Body.Text:=UTF8Encode(memo1.text);

някъде така

IdTCPClient1.Host:= "127.0.0.1"; IdTCPClient1.Connect;// свързан IdTCPClient1.Socket.WriteLn("команда"); // изпратена команда команда и подаване на ред //Изчакайте отговор и затворете връзката txtResults.Lines.Append(IdTCPClient1.Socket.ReadLn); IdTCPClient1.Disconnect;

в този случай командата е просто текст с нов ред. Това прави много по-лесно получаването на команда от другата страна (само ReadLn). В общия случай трябва да измислите (или да използвате готов) протокол.

над него имаше клиент. А сега и сървъра. При сървъра нещата са малко по-сложни. Ясно е, че е нормално един сървър да обслужва повече от един клиент, много. И има няколко "схеми" за това.

    Класически - един клиент - една нишка. Веригата е лесна за кодиране, интуитивна. Той е добре паралелен през ядрата. Недостатъкът е, че обикновено е много трудно да се създадат много теми и това ограничава броя на клиентите. За 32-битови програми горната граница е някъде около 1500 (хиляда и половина) нишки на процес. Но в този случай режийните разходи за превключването им могат да "изядат" целия процент. Това е схемата, използвана в инди.

    Вторият класически - всички клиенти на един поток. Тази схема често е по-сложна в кодирането, но с правилния подход ви позволява да поддържате 20-30 хиляди „бавни потребители“ практически без натоварване на ядрото. Силен плюс на тази схема е, че можете да правите без мутекси и други примитиви за синхронизация. Тази схема се използва от NodeJS и стандартни класове за работа в мрежа в Qt.

    Смесени. В този случай се създават няколко нишки, всяка от които обслужва определен брой клиенти. Най-трудно в кодирането, но ви позволява да използвате максимално железните ресурси.

Как се прави в Инди. Indy tcp сървърът за всяка връзка създава отделна нишка (TThread) и по-нататъшна работас клиента влиза в него. Indy скрива това добре, оставяйки само необходимостта потребителят да внедри метода IdTCPServer.onExecute. Но, както казах по-горе, този метод се стартира в отделна тема и всеки клиент има своя лична. Това означава следното:

  • sleep може да бъде извикан в този метод и само един клиент ще чака. Всичко останало ще работи (но ако извикате заспиване в манипулатора за щракване на бутон, тогава резултатът е известен)
    • по-добре е достъпът до глобалните променливи да става само чрез примитиви за синхронизация.
    • GUI елементите трябва да се обработват внимателно и правилно. По-добре е да не го правите директно (някои компоненти ви позволяват достъп до тях от други нишки, но трябва внимателно да прочетете доковете).
    • трябва да имате достъп до други клиенти чрез заключване (защото ако две нишки искат да пишат на един и същ потребител, нищо добро няма да излезе от това).

Нека да разгледаме един много прост пример. Ние отговаряме на всяка клиентска заявка по същия начин и затваряме връзката (такъв ехо сървър).

Процедура TForm1.IdTCPServer1Execute(AContext: TIdContext); var strText: низ; begin //Получаване на низ от клиента strText:= AContext.Connection.Socket.ReadLn; //Отговор AContext.Connection.Socket.WriteLn(strText); //Затворете връзката с потребител AContext.Connection.Disconnect; край;

AContext е специален обект, който съдържа цялата необходима информация за клиента. Самият idTcpServer съдържа списък с тези контексти и може да бъде достъпен. Нека разгледаме по-сложно излъчване. Тоест, изпратете едно съобщение до всички

VarClients:TList; i: цяло число; начало // безпогрешно :) ако не е присвоено(IdTCPServer1.Contexts), тогава излезте; // вземете списък с клиенти и го заключете Clients:=IdTCPServer1.Contexts.LockList; try for i:= 0 to Clients.Count-1 do try //LBuffer е от тип TBytes и съдържа подготвени данни за изпращане // но може да се използва и WriteLn. TIdContext(Клиенти[i]).Connection.IOHandler.Write(LBuffer); освен // тук трябва да се добави логика. Клиентът може да прекъсне връзката по време на края; накрая // важно! списъкът трябва да бъде отключен, в противен случай другите методи няма да могат да надхвърлят Contexts.LockList IdTCPServer1.Contexts.UnlockList; край; край;

indie съдържа BytesToString() и ToBytes() за преобразуване на String и TIdBytes един в друг.

Списъкът е заключен, така че другите да не могат да го променят. В противен случай самият цикъл става много по-сложен. И най-важното, не забравяйте да отключите!

Остава последния въпрос. Как да изпратите съобщение до конкретен клиент. За да направите това, трябва да се научите как да идентифицирате връзката. Това може да стане по няколко начина - погледнете ip/port. Но има и по-добро. IdContext (по-точно неговият предшественик idTask) има свойство Data от тип TObject. Можете да запишете вашия обект в него и да съхранявате всички необходими данни там. Типичен примеризползването ще бъде следващо. Когато клиентът току-що се е свързал, това поле е празно. Когато премине проверката за име-парола, създаваме обект (наш собствен), запазваме името там и го записваме в свойството Data. И тогава, когато трябва да преминете през свързаните клиенти, ние просто го изваждаме. Разбира се, ако има хиляди потребители, ще бъде скъпо да преглеждате всички потребители всеки път. Но как да го направим по-оптимално е тема на друга голяма статия.