Delphi. Надсилання пошти засобами Delphi

Розробити програму, яка надаватиме інтерфейс для використання стандартної для Win2000/XP команди передачі повідомлень net send. Дати можливість вказати користувачеві адресу одержувача, текст повідомлення та кількість повідомлень, що відправляються. Також передбачити можливість встановлення блокування на отримання повідомлень з інших комп'ютерів.

Розробка форми

Створіть новий проект Delphi. Змініть заголовок форми (властивість Caption) на Net Sender. Розмістіть вздовж лівого краю форми один над одним три компоненти Label категорії Standardі надайте їх властивості Caption значення IP-адреса:, Повідомлення: І Кількість:.

Поруч із кожною з міток розмістіть за компонентом Edit категорії Standard. Найвищий назвіть ip (властивість Name), а властивості Text надайте значення 192.168.0.1.; середнє поле назвіть txt, а властивості Text надайте будь-який текст повідомлення за замовчуванням; найнижче поле назвіть how, а властивості Text надайте значення 1.

Під перерахованими компонентами розмістіть компонент Checkbox категорії Standard. Надайте йому ім'я secure, властивості Caption надайте значення Вимкнути прийом повідомлень, а властивості Checked - значення True.

У самому низу форми розмістіть кнопку (компонент Button категорії Standard), надавши її властивості Caption значення Send. Також нам знадобиться таймер (компонент Timer категорії System), для якого властивості Interval слід присвоїти значення 10.

Отримана форма має відповідати рис. 15.1.

Мал. 15.1. Форма для програми надсилання повідомлень у локальної мережі

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

Насамперед напишемо власну процедуру bomb, яка зчитуватиме всі налаштування і надсилатиме повідомлення. Оголосіть цю процедуру як закритий член класу форми:

Також нам знадобиться глобальна змінна i типу integer:

Тепер створимо реалізацію процедури bomb у розділі implementation:

procedure TForm1.bomb();
if how.Text= "" then how.Text:= "1";
if ip.Text = "" then ip.Text:= "127.0.0.1";(якщо ip-адреса не вказана, то відправляємо на локальний комп'ютер}
WinExec(PChar("net send" + ip.Text + """ + txt.Text + """), 0);//відправка повідомлення

У цій процедурі виконується перевірка: чи заповнені всі необхідні поля. Якщо немає тексту повідомлення, то встановлюємо знак "!"; якщо не вказана IP-адреса, то надсилаємо повідомлення на локальний комп'ютер з адресою 127.0.0.1; якщо не вказано кількість повідомлень, то надсилаємо одне повідомлення. Повідомлення надсилаються за допомогою стандартної команди net send, яка має наступний синтаксис:

net send ip-адреса повідомлення.

Тепер обробимо подію таймера OnTimer:

h: HWND;//Зберігає ідентифікатор вікна
if not secure.Checked then//якщо прапорець не встановлено
Timer1.Enabled:= False;//відключаємо моніторинг
if secure.Checked then//якщо прапорець встановлений
//Шукаємо вікна з повідомленнями
h:= FindWindow(nil, "Служба повідомлень");//закриваємо всі знайдені вікна
if h<>

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

Для того, щоб можна було перемикатися між цими двома режимами, необхідно створити обробник події secure.OnClick:

if secure.Checked then//якщо прапорець встановлений…
Timer1.Enabled:= True;//…включаємо моніторинг

При натисканні кнопки Sendми просто викликатимемо процедуру bomb:

Для того щоб полегшити користувачеві життя, зробимо так, щоб надсилання повідомлення здійснювалося також після натискання клавіші у будь-якому текстовому полі введення. Для цього необхідно створити обробник події OnKeyPress для кожного поля. Код цього обробника для поля ip, який можна призначити полям txt і how:

if key= #13 then//якщо натиснута клавіша
bomb;//відправка повідомлення

Повний вихідний код модуля

Повний код модуля програми надсилання повідомлень локальною мережею представлений у лістингу 15.1.

Лістинг 15.1. Модуль програми надсилання повідомлень по локальній мережі

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls;

procedure Timer1Timer(Sender: TObject);
procedure secureClick(Sender: TObject);
procedure ipKeyPress (Sender: TObject; var Key: Char);
procedure txtKeyPress(Sender: TObject; var Key: Char);
procedure howKeyPress(Sender: TObject; var Key: Char);
procedure Button1Click(Sender: TObject);


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

procedure TForm1.Timer1Timer(Sender: TObject);
h: HWND; //Зберігає ідентифікатор вікна
if not secure.Checked then //якщо прапорець не встановлений
Timer1.Enabled:= False; //відключаємо моніторинг
if secure.Checked then //якщо прапорець встановлений
//Шукаємо вікна з повідомленнями
h:= FindWindow(nil, "Служба повідомлень"); //закриваємо всі знайдені вікна
if h<>0 then PostMessage(h, WM_QUIT, 0, 0);

procedure TForm1.secureClick(Sender: TObject);
if secure.Checked then //якщо прапорець встановлений…
Timer1.Enabled:= True; //…включаємо моніторинг

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

procedure TForm1.Button1Click(Sender: TObject);

⊚ Усі файли проекту та виконуваний файл розглянутої програми знаходяться на компакт-диску, що додається до книги, у папці Chapter 15.

Надсилання повідомлень

Так само як система Windowsпосилає свої повідомлення різним вікнам, у додатку також може виникнути необхідність обміну повідомленнями між його власними вікнами та елементами управління. Для надсилання повідомлень існує кілька способів: метод PerForm() (працюючий незалежно від API Windows), а також функції API Win32 SendMessage() та PostMessage().

Метод PerForm(), яким володіють усі нащадки класу TControl:

function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;

Щоб надіслати повідомлення формі або елементу управління, застосовується наступний синтаксис:

RetVal:=ControlName.PerForm(MessageID, wParam, lParam);

При виклику PerForm() керування програмою, що викликає, не повернеться, поки повідомлення не буде оброблено. Цей метод надсилає повідомлення, минаючи систему передачі повідомлень API Windows.

Функції API SendMessage() та PostMessage(), оголошені в модулі Windows наступнимчином:

function SendMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM;

lParam: LPARAM): LRESULT; stdcall;

функція PostMessage(hWnd: HWND; Msg: UINT;

wParam: WPARAM; lParam: LPARAM): BOOL; stdcall;

hWnd – дескриптор вікна одержувача повідомлення; Msg - ідентифікатор повідомлення; wParam та lParam – додаткові дані.

Функція SendMessage() подібно до методу PerForm() посилає повідомлення безпосередньо процедурі вікна і чекає його обробки, а функція PostMessage() поміщає повідомлення в чергу повідомлень і повертає управління програмі, що викликала її, не чекаючи результатів обробки.

Функція SendMessage() повертає значення, отримане внаслідок обробки повідомлення. Функція PostMessage() – повертає значення, яке показує, чи вдалося помістити повідомлення у чергу повідомлень.

Користувальницькі повідомлення

При розробці додатків може виникнути ситуація, коли додатку потрібно надіслати спеціальне повідомлення або самому собі, або іншому додатку до виконання деяких дій. Для повідомлень, придуманих користувачем, Windows зарезервовані значення від WM_USER до $7FFF.

Приклад повідомлення користувача:

TestMsg = WM_USER + 100; // ідентифікатор повідомлення

TForm1 = class(TForm)

//Метод обробки повідомлення:

procedure MyMessage(var Msg: TMessage); message TestMsg;

procedure TForm1.MyMessage(var Msg: TMessage);

ShowMessage("Працює повідомлення TestMsg");

Msg.Result:= 1; // Повертається результат

Приклади посилки повідомлення формі:

if Form1.PerForm(TestMsg, 0, 0) = 1 then

if SendMessage(Form1.Handle, TestMsg, 0, 0) = 1 then

ShowMessage("Повідомлення успішно оброблено");

if PostMessage(Form1.Handle, TestMsg, 0, 0) then

ShowMessage("Повідомлення розміщено в чергу повідомлень");

Події Delphi

Подія – те, що відбувається у процесі роботи програми. З погляду мови Delphi, подія – це властивість процедурного типу, та її значенням є покажчик деякий метод. Привласнити таку властивість значення означає вказати адресу методу, який буде виконуватися в момент настання події. Такі методи називаються обробниками подій.

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

Властивості подій намагаються починати зі слова "On", за яким слідує ім'я події.

Взаємозв'язок повідомлень та подій

Delphi є інтерфейсом для взаємодії з повідомленнями Windows, принаймні – з деякою їх частиною. Багато подій компонентів бібліотеки VCL безпосередньо пов'язані з повідомленнями Windows типу WM_XXX.

Послідовність обробки повідомлень у Delphi
Всі класи Delphi мають вбудований механізм обробки повідомлень, який називають обробниками повідомлень. Клас отримує якесь повідомлення і викликає один із набору певних методів залежно від отриманого повідомлення. Якщо відповідний метод не визначено, викликається заданий за замовчуванням обробник. Більш детально цей механізм працює в такий спосіб.

Після отримання повідомлення, система повідомлень VCL виконує велику попередню роботу з його обробки.

Як зазначалося вище, спочатку повідомлення обробляється методом TApplication.ProcessMessage, який вибирає його з черги переважно циклі повідомлень. При цьому перевіряє вміст поля FOnMessage (фактично перевіряє наявність'обробника у події OnMessage) і, якщо воно не порожнє, викликає обробник цієї події, а якщо поле порожнє (Nil), то викликає функцію API DispatchMessage(Msg). При надсиланні цього повідомлення не відбувається.

Якщо обробник події OnMessage не визначений, для обробки отриманого повідомлення викликається функція API DispatchMessage, яка передає повідомлення головній процедурі вікна.

Розглянемо цикл обробки повідомлення після надходження його у головне вікно компонента. Послідовність обробки повідомлення представлена ​​на наступному малюнку:

Видно, що повідомлення передається в MainWndProc, далі в WndProc, далі в Dispatch, далі в DefaultHandler.

У Delphi передбачено головний не віртуальний метод MainWndProc(Var Message: TMessage) для вікна кожного компонента. Він містить блок обробки спеціальних ситуацій, передаючи структуру повідомлення від Windows до віртуального способу, визначеного як WindowProc. При цьому цей метод обробляє будь-які винятки, які відбуваються протягом обробки повідомлення, викликаючи метод HandleException програми. Починаючи з цього місця, можна забезпечити спеціальну обробку повідомлення, якщо це потрібно логікою роботи вашої програми. Зазвичай цьому етапі обробку змінюють, ніж дати відбутися стандартної обробці VCL.

За промовчанням значення властивості WindowProc об'єкта ініціалізується адресою віртуального методу WndProc. Далі, якщо немає зареєстрованих перехоплювачів повідомлення типу TWindowHook, WndProc викликає віртуальний метод TObject.Dispatch, який, використовуючи поле Msg структури повідомлення, що надійшло, визначає, чи знаходиться це повідомлення в списку обробників повідомлення для даного об'єкта. Якщо об'єкт не обробляє повідомлення, досліджується список обробників повідомлень предків. Якщо такий метод буде знайдений, то він викликається, інакше викликається віртуальний метод DefaultHandler.

Нарешті, повідомлення досягає відповідної процедури його обробки, де проводиться передбачена для нього обробка. За допомогою ключового словаУherited воно далі відправляється для обробки в предках. Після цього повідомлення також потрапляє в метод DefaultHandler, який виконує завершальні дії з обробки повідомлень та передає його процедурі DefWindowProc (DefMDIProc) для стандартної обробки Windows.

Обробка повідомлень компонентами Delphi
Таким чином, короткий описпослідовність обробки повідомлення виглядає наступним чином. Всі повідомлення спочатку проходять через метод, адреса якого вказана у властивості WindowProc. За промовчанням це метод WndProc. Після чого вони поділяються та розсилаються за своїми методами повідомлень. Наприкінці вони знову сходяться в методі DefaultHandler, якщо не були оброблені раніше або в обробниках викликається успадкований обробник (Inherited). Отже, у компонентів Delphiє такі можливості для обробки повідомлень:
а) До того, як якийсь обробник повідомлення побачить повідомлення. І тут потрібно або заміна адреси методу як WindowProc, або заміщення методу TControl.WndProc.
Властивість WindowProc оголошено так:

Туре TWndMethod= Procedure(Var Message: TMessage) Of Object;
Property WindowProc: TWndMethod;

Фактично, використовуючи властивість WindowProc, можна створити метод типу TWndMethod і тимчасово замінити вихідний метод на створений, однак оскільки адреса методу у властивості WindowProc не зберігається, необхідно попередньо зберігати адресу вихідного методу WndProc, щоб його можна було відновити пізніше.

OldWndProc: TWndMethod;
procedure NewWndProc(var Message: TMessage);
procedure TForm1.NewWndProc(var Message: TMessage);
var Ch: char;
begin
if message.Msg= WM_MOUSEMOVE then begin
Edit1.Text:='x='+inttostr(message.LParamLo)+', y='+inttostr(message.LParamHi);
end
else WndProc(Message);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
OldWndProc:=WindowProc;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
If CheckBox1.Checked then WindowProc:= NewWndProc
else WindowProc:= OldWndProc;
end;

б) Усередині відповідного способу повідомлення.
Наведемо ще один аналогічний приклад.
Скористається повідомленням, що надсилаються компонентам для перемальовки WMPAINT.

У класі TForml оголосимо цей метод з метою його перевизначення та представимо реалізацію перевизначеного методу повідомлення:

Туре TForml = Class (TForm)
… // Всі інші необхідні оголошення
Protected
Procedure WMPaint(Var Msg: TWMPaint); Message WM_PAINT; End;
Procedure TForml.WMPaint(Var Msg: TWMPaint); Begin
If CheckBox1.Checked Then ShowMessage('О6pa6oтчик повідомлення!');
Inherited;
End;

При перевизначенні конкретних обробників повідомлень завжди доцільно викликати Inherited для виконання базової обробки повідомлення, яке потрібно Windows.

в) Після того, як кожен із відповідних повідомлень методів побачить його.

У цьому випадку необхідно замінити метод DefaultHandler.

procedure DefaultHandler(var Message); override;
procedure TForm1.DefaultHandler(var Message);
var i:integer;
begin
if Cardinal(Message)=WM_defh then
for i:= 0 to 10 do begin
beep;
sleep(100);
end
else
inherited;
end;

procedure TForm1.Button5Click(Sender: TObject);
begin
SendMessage(Handle,WM_defh,0,0);
end;

Зв'язок між повідомленнями та подіями
Багато подій VCL Delphi безпосередньо пов'язані з повідомленнями Windows. У довідковій системі Delphi ці відповідності перераховані. Подані вони у табл.1.

Таблиця 1

Подія VCLПовідомлення WindowsПодія VCLПовідомлення Windows
OnActivateWM_ACTIVATEOnKeyPressWM_CHAR
OnClickWM_LBUTTONDOWNOnKeyUpWM_KEYUP
OnCreateWM_CREATEOnPaintWM_PAINT
OnDblClickWM_LBUTTONDBLCLKOnResizeWM_SIZE
OnKeyDownWM_KEYDOWNOnTimerWM_TIMER

Не варто створювати обробники повідомлень, якщо для нього є певна подія. У таких випадках доцільно використовувати обробку події, оскільки на неї накладається менше обмежень.

десь так

IdTCPClient1.Host:= "127.0.0.1"; IdTCPClient1.Connect;// підключилися IdTCPClient1.Socket.WriteLn("command"); // відправили команду command та переклад рядка //Чекаємо на відповідь і закриваємо з'єднання txtResults.Lines.Append(IdTCPClient1.Socket.ReadLn); IdTCPClient1.Disconnect;

в даному випадку команда – це просто текст із перекладом рядка. Це спрощує прийом команди з іншого боку (просто ReadLn). В загальному випадку потрібно вигадувати (або використовувати готовий) протокол.

вище це був клієнт. Нині ж сервер. Із сервером все трохи складніше. Зрозуміло, що для сервера нормально обслуговувати не одного клієнта, багатьох. І для цього є кілька "схем".

    Класична – один клієнт – один потік. Схема проста у кодуванні, інтуїтивно зрозуміла. Добре розпаралелюється по ядрах. Недолік - зазвичай дуже складно створити багато потоків, а це обмежує кількість клієнтів. Для 32бітних програм верхня межа десь у районі 1500 (півтори тисячі) потоків на процес. Але в цьому випадку накладні витрати на їх перемикання можуть "з'їсти" весь відсоток. Саме ця схема використовується в indy.

    Друга класична – всі клієнти на один потік. Ця схема часто складніша в кодуванні, але при правильному підході дозволяє тримати 20-30к "повільних користувачів" практично не навантажуючи ядро. Сильний плюс цієї схеми можна обійтися без мютексів та інших примітивів синхронізації. Цю схему використовує NodeJS та стандартні класи для роботи з мережею Qt.

    Змішана. У цьому випадку створюється кілька потоків, кожен з яких обслуговує якусь кількість клієнтів. Найскладніша в кодуванні, але дозволяє максимально використовувати ресурси заліза.

Як це зроблено у Indy. Indy tcp сервер для кожного підключення створює окремий потік (TThread) та подальша роботаз клієнтом йде у ньому. Indy красиво це ховає, залишаючи для користувача лише необхідність реалізувати метод IdTCPServer.onExecute. Але, як я сказав вище, цей метод запускається в окремому треді, і він у кожного клієнта свій особистий. Це означає таке:

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

Розглянемо дуже простий приклад. На будь-який запит клієнта відповідаємо тим самим і закриваємо з'єднання (такий собі сервер echo).

Procedure TForm1.IdTCPServer1Execute(AContext: TIdContext); var strText: String; begin //Приймаємо від клієнта рядок strText:= AContext.Connection.Socket.ReadLn;

//Відповідаємо AContext.Connection.Socket.WriteLn(strText); //Закриваємо з'єднання з користувачем AContext.Connection.Disconnect; end; AContext це спеціальний об'єкт, який містить усю

Var Clients: TList;

i: integer; begin // захист від дурня:) if not Assigned(IdTCPServer1.Contexts) then exit;

// отримаємо список клієнтів і заблокуємо його Clients:=IdTCPServer1.Contexts.LockList;

try for i:= 0 to Clients.Count-1 do try //LBuffer має тип TBytes і містить підготовлені дані для відправки // але можна використовувати і WriteLn.

TIdContext(Clients[i]).Connection.IOHandler.Write(LBuffer);

except // Тут необхідно додати логіку. Клієнт може вимкнутись у процесі end; finally // важливо! Список потрібно розблокувати, інакше інші методи не зможуть пройти далі Contexts.LockList IdTCPServer1.Contexts.UnlockList; end; end;

Інді містить BytesToString() і ToBytes() для передбачення String і TIdBytes один у одного.
Список блокується, щоб інші не могли його модифікувати. Інакше сам цикл дуже ускладнюється. І головне, не забувати розблокувати!
Залишилося останнє питання. Як надіслати повідомлення якомусь певному клієнту. Для цього необхідно навчитися ідентифікувати з'єднання. Це можна зробити кількома способами – подивитися в айпі/порт. Але їсти краще. У IdContext (точніше у його предка idTask) є властивість Data типу TObject. У нього можна записати свій об'єкт та зберігати там усі потрібні дані. Типовий приклад використання буде наступним. Коли клієнт лише підключився – це поле порожнє. Коли він пройшов перевірку імені пароля - створюємо об'єкт (свій), зберігаємо ім'я туди і прописуємо у властивість Data. А потім, коли потрібно робити цикл за підключеними клієнтами, просто вичитуємо його. Звичайно, якщо користувачів тисячі, щоразу переглядати всіх користувачів буде накладно. Але як робити це більш оптимально – тема іншої великої статті.

Відправлення та прийом пошти реалізуються за допомогою Delphi досить просто. Для надсилання пошти нам знадобиться компонент idSMTP зі сторінки Indy Clients на панелі компонентів Delphi.

IdSMTP1.Port:=25;

З'єднання з сервером

Для з'єднання з SMTP сервером, який здійснюватиме відправлення нашої пошти, потрібно вказати його URL, для сервера mail.ru це робиться таким чином:

IdSMTP1.Host:= ′smtp.mail.ru′;

З'єднання з сервером здійснюється методом Connect:


procedure Connect(const ATimeout: Integer); override;

де ATimeout - необов'язковий параметр, задає максимальний час у мілісекундах очікування відповіді з SMTP сервера, після якого спроба встановити з'єднання припиняється.

Наприклад,

IdSMTP1.Connect(5000);

Якщо при з'єднанні з сервером потрібна авторизація, то значення властивості AuthenticationType потрібно встановити в atLogin, при цьому в інспекторі об'єктів також потрібно визначити властивості Username (ім'я користувача. Наприклад, Username поштової скриньки [email protected]- delphi) і Password(пароль на ящик), або зробити те ж програмно:

IdSMTP1.AuthenticationType:=atLogin;
IdSMTP1.Username:='delphi';
IdSMTP1.Password:='something';

IdSMTP1.AuthenticationType:=atNone;

Після застосування методу Connect потрібно аналізувати логічну властивість Connected, яка в разі вдалого з'єднання встановлюється в True. Після цього за допомогою методу Send можна надсилати повідомлення:

if Connected=True then IdSMTP1.Send(Msg);

Структура листа

Метод Send відправляє тіло повідомлення, що є структурою типу TIdMessage;

Структура листа реалізується в Delphi окремим компонентом TIdMessage, розташованим на палітрі компонентів Indy Misc і виглядає так

TidMessage Структура TIdMessage визначається так:

З темою повідомлення, я гадаю, все зрозуміло. Властивість

конкретно визначаються назви електронних облікових записів, яким адресується лист. Назви повинні вказуватися через роздільник виду, тобто через кому. Наприклад:

наприклад,

наприклад,

Властивість Text містить інформацію обох властивостей. Тіло листа є об'єктом типу TStrings:

де Collection - об'єкт класу TIdMessageParts, що є колекцією додатків до електронного листа.
контстанта AFileName типу TFileName - це звичайний текстовий рядок із зазначенням правильного шляху до файлу, наприклад "C:file.zip", за умовчанням має значення ′′.

Таким чином, продовжуючи наш приклад, рядком виду

Поділитися