В данной статье рассмотрим методы отправки и приема почты средствами Delphi . Прием и отправку почты реализовать довольно просто, но всё же существует множество подводных камней, поэтому лучше всего читать данную статью внимательно. Для отправки почты нам понадобится компонент idSMTP со страницы Indy Clients палитры компонентов Delphi . Данный компонент реализует всё необходимое для отправки электронной почты по протоколу SMTP (Simple Mail Transfer Protocol ), обычно он использует 25 порт, но его можно поменять на другой (свойство Port ). Так же необходимо произвести настройки формы (размер, цвет и т.д.)

Начнем реализовывать наш клиент, для этого разместим на форме idSMTP . Так же произведите настройку внешнего вида формы (название, размер и т.д.). По необходимости можно изменить порт, но главное не стоит забывать, что данный порт должен быть разблокирован в файерволе. Для соединения с SMTP сервером необходимо указать его хост (свойство host ). Например: IdSMTP1.Host:= " smtp . mail . ru "; Или (как сделаем мы) разместить на форму Label и Edit . Немного забегая вперед скажу, что соединение с сервером осуществляется методом 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:="password";

IdSMTP1.AuthenticationType:=atNone ;

В нашем же примере мы будем считать, что для авторизации нам будет требоваться логии и пароль.

После применения метода Connect необходимо проверить результат его выполнения. Это делается с помощью свойства Connected, если оно имеет значение true , то подключение прошло нормально.

Для отправки сообщений используется функция Send.

Пример:

IdSMTP1.Send(Msg);

Теперь более подробно рассмотрим структуру письма. Как было уже сказано раньше, метод send отправляет тело сообщения, представляющего из себя структуру типа TIdMessage . Сама структура письма в Delphi реализуется отдельным компонентов TIdMessage . Он находиться на палитре компонентов Indy Misc .

Пример определения TIdMessage структуры:

var
Msg: TIdMessage;
begin
Msg.Subject:="тема сообщения"; //текст темы сообщения
Msg.Recipients.EMailAddresses:="[email protected]";
//указываем адрес получателя
Msg.From.Adress:="[email protected]"; //указываем автора письма
Msg.Body.Text:="текст сообщения"; //размещаем текст сообщения
Msg.Date:=StrToDate("01.12.2004");//дата отправки письма может быть любой
end;

Свойство Subject определяет тему сообщения. Свойство Recipients включает в себя свойство EMailAddresses оно определяет собой адресатов. То есть попросту кому предназначается письмо, разделителем двух и более почтовых адресов служит запятая.

Пример:

Msg.Recipients.EMailAddresses:="builder @mail.ru, [email protected] ";

Свойство From представляет из себя объект типа TIdEmailAddressItem в нем содержится информация об отправителя письма. Это свойство включает в себя три свойства: Name, Address, Text . Свойство Address содержитинформацию об электроном ящике отправителя, оно имеет тип String .

Пример:

Msg.From.Address:="[email protected]";

Свойство name представляет собой имя отправителя, имеет тип String .

Пример:

Msg.From.Name:="Иван Иванович";

Свойство Text содержит себе объединенную информацию этих обоих свойств. Теперь перейдем к телу письма, оно имеет тип TStrings . Теперь же поговорим об присоединение к письму файлов, то есть об аттачменте . Если вам потребовалась прикрепить файл к письму, то необходимо будет создать объект класса TidAttachment . Для этого необходимо будет применить конструктор вида:

constructor Create(Collection: TIdMessageParts; const AFileName: TFileName = ""); reintroduce;

Где Collection представляет собой коллекцию приложений к письму, его тип TIdMessageParts . Контстанта AFileName имеющая тип TFileName являеться обычной текстовой строкой. В ней необходимо указать правильный путь к файлу.

Пример:

TIdAttachment.Create(Msg.MessageParts,"c:file.zip");

После отправки сообщения желательно разорвать связь с сервером, что бы не грузить канал связи. Разрыв связи производиться методом Disconnect.

Пример:

IdSMTP1.Disconnect;

Разработать программу, которая будет предоставлять интерфейс для использования стандартной для 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.

где то так

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 это специальный объект, который содержит всю необходимую информацию о клиенте. Сам idTcpServer содержит список этих контекстов и к нему можно обратиться. Рассмотрим более сложный бродкаст. То есть, разослать одно сообщение всем

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. А потом, когда нужно делать цикл по подключенным клиентам, просто вычитываем его. Конечно, если пользователей тысячи, каждый раз просматривать всех пользователей будет накладно. Но как делать это более оптимально - тема другой большой статьи.

Посылка сообщений

Так же как система 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;

function 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.