В Indy есть
несколько серверных моделей.
Наиболее известный сервер в Indy – это TIdTCPServer.
Класс TIdTCPServer
создает вторичный слушающий поток, который независим от главного потока
программы. Слушающий поток следит за входящими запросами от клиентов. Для
каждого клиента, которому он отвечает, он создает новый поток, для специфического сервиса,
индивидуального соединения. Все соответствующие события затем возбуждаются к
контексте этого потока.
Есть два пути построения TCP серверов: с командными обработчиками и с событиями OnExecute. Командные обработчики делают построение серверов
много проще, но не во всех ситуациях.
Командные обработчики удобны для протоколов, которые
обмениваются командами в текстовом формате, но не очень удобны для протоколов,
которые имеют команды двоичной структуры или совсем не имеют командной
структуры. Большинство протоколов текстовые и могут использоваться командные
обработчики. Командные обработчики полностью опциональны. Если они не
используются сервера Indy продолжают
использовать старые методы.
Некоторые протоколы являются двоичными или не имеют
командной структуры и не пригодны для использования командных обработчиков. Для
таких серверов должно использоваться событие OnExecute. Событие OnExecute
постоянно вызывается пока существует соединение
и передает соединение, как аргумент. Реализация очень простого сервера с
использованием события OnExecute
выглядит так:
procedure
TformMain.IdTCPServer1Execute(AThread: TIdPeerThread);
var
LCmd: string;
begin
with
AThread.Connection do
begin
LCmd := Trim(ReadLn);
if SameText(LCmd,
'QUIT') then
begin
WriteLn('200 Good bye');
Disconnect;
end
else if
SameText(LCmd, 'DATE') then
begin
WriteLn('200 ' + DateToStr(Date));
end
else
begin
WriteLn('400 Unknown command');
end;
end;
end;
здесь нет необходимости проверять действительность
соединения, так как Indy делает это автоматически. Так
же нет необходимости производить опрос, поскольку и это Indy делает автоматически за вас. Она вызывает событие
периодически, пока соединение не прекратится. Это может быть вызвано или явным
отсоединением, или по сетевой ошибке, или если клиент отсоединился. В
действительности, не требуется делать никаких опросов на предмет отсоединений.
Если данный опрос все же необходимо делать, вам надо только позаботиться об
возбуждении исключений, что бы Indy смог
нормально их отработать.
Командные
обработчики – это новая концепция, используемая в TIdTCPServer, которая позволяет серверу выполнять парсинг команды и обрабатывать его
для вас. Для каждой команды, которую вы желаете обрабатывать сервером,
создается командный обработчик. Думайте об командных обработчиках, как о списке
действий для сервера. Командный обработчик содержит свойства, которые
указывают, как парсить параметры, команды, некоторые действия, которые могут
быть выполнены автоматически и дополнительные автоматические ответы. В
некоторых случаях, используя только свойства, вы сможете создать полностью
функциональную команду без необходимости написания какого либо кода. Каждый
командный обработчик имеет уникальное событие OnCommand. Когда событие вызывается, то нет необходимости
определять какая команда была запрошена, так как данное событие уникально для
каждого командного обработчика. В дополнение, командный обработчик уже
распарсил параметры и выполнил автоматические действия для вас.
Вот маленький пример использования командных
обработчиков. Во-первых вы должны определить две команды: QUIT и DATE. Создадим
два командных обработчика, как указано ниже:
Для cmdhQuit свойство
disconnect устанавливается в true. Для cmdhDate событие
OnCommand определяется так:
procedure
TForm1.IdTCPServer1cmdhDateCommand(ASender: TIdCommand);
begin
ASender.Reply.Text.Text :=
DateTimeToStr(Date);
end;
это законченный код командного обработчика. Все другие
детали, специфицированы установкой свойств в командных обработчиках.
Поскольку UDP не
требуется соединения (по определению), то класс TIdUDPServer работает отлично от TIdTCPServer. Подобно TIdSimpleServer класс TIdUDPServer не имеет некоторых
режимов, и поскольку UDP не требуется
соединения, TIdUDPClient имеет только
слушающие методы.
При активизации, класс TIdUDPServer создает слушающий поток, для прослушивания входящих UDP пакетов. Для каждого, принятого UDP пакета, класс TIdUDPServer возбуждает событие OnUDPRead в главном потоке или в контексте слушающего потока, в
зависимости от значения свойства ThreadedEvent.
Когда значение свойства ThreadedEvent = false, то
событие OnUDPRead возбуждается в контексте главного потока программы.
Когда значение свойства ThreadedEvent = true, то событие OnUDPRead
возбуждается в контексте слушающего потока.
Когда значение свойства ThreadedEvent равно false, то
блокируется прием дальнейших сообщений. Поэтому событие OnUDPRead должно быть как можно более быстрым.
Класс TIdSimpleServer
предназначен для разового использования серверов. Класс TIdSimpleServer предназначен для обслуживания одного соединения за
раз. Хотя он может обслуживать другие запросы по окончанию, обычно он
используется только для одного запроса.
Класс TIdSimpleServer
не создает потоков для прослушивания или вторичных потоков соединения. Вся
функциональность реализована в одном потоке.
Компонент клиента TIdFTP использует класс TIdSimpleServer. Когда FTP выполняет
передачу, вторичное TCP соединение открывается для
передачи данных и закрывается, когда данные будут переданы. Данное соединение
называется «канал данных (data channel)» и оно уникально для каждого передаваемого файла.
События TIdTCPServer
потоковые. Это означает, что они не являются частью потока, они выполняются
внутри потока.
Каждый клиент создает свой собственный поток. При
использовании данного потока, события TCP
сервера (который является частью формы или модуля данных) вызываются из данных
потоков. Это означает, что одиночное событие может быть вызвано множество раз
из разных потоков. Такие события получают в качестве аргумента AThread, который указывает на поток из которого возбуждено
событие.
Примерами потоковых события являются командные
обработчики сервера OnConnect, OnExecute и OnDisconnect.
Событие OnExecute
ссылается на событие OnExecute класса
TIdTCPServer. При реализации сервера по данной модели, должно быть
определено событие OnExecute или перекрыт метод DoExecute.
Модель OnExecute
допускает полный контроль разработчиком и позволяет реализовывать любые типы
протоколов, включая двоичные протоколы.
После подсоединения клиента к серверу, возбуждается
событие OnExecute. Если событие OnExecute не определено, то возбуждается исключение. Событие OnExecute возбуждается в цикле, как только подсоединяется
клиент. Это очень важная деталь и поэтому разработчик должен побеспокоиться об
1.
Помнить о
том, что событие возникает в цикле.
2.
Не препядствовать
Indy выполянть обработку в цикле.
На этапе проверки соединения выполняется следующие
проверки:
·
Клиент еще
подсоединен
·
Disconnect не
был вызван во время OnExecute
·
Отсутствуют
фатальные ошибки
·
Не было
возбуждено необработанное исключение в OnExecute
·
Сервер еще
активен
Если все эти проверки и другие проверки истинны, то
событие OnExecute возбуждается снова. Поэтому, разработчик никогда не
должен конструировать свой цикл внутри OnExecute, который будет дублировать это, так как это будет
мешать Indy.
Обработчики команд подобны спискам действий для
сервера. Командные обработчики работают следующим образом: - вы создаете
обработчик команд для каждой команды и затем используя обработчик команд
определяете поведение для конкретной команды. Когда команда принята от клиента,
то сервер автоматически разбирает ее и передает конкретному обработчику.
Обработчики команд не только имеют свойства для настройки поведения, но также
методы и события.
Обработчики команд работают только с текстовыми
командами и ответами TCP протоколов.
Тем не менее это покрывает нужды почти 95% серверов используемых в
настоящее время.
Класс TCPServer
содержит свойство, именуемое CommandHandlers,
которое является коллекцией обработчиков команд. Обработчики обычно создаются
во время разработки, тем не менее, при реализации наследников они могут
создаваться и во время исполнения. Класс TCPServer содержит несколько свойств и событий имеющих
отношение к обработчикам команд. Свойство CommandHandlersEnabled разрешает или запрещает работу обработчика как
единого целого. Свойство OnAfterCommandHandler возбуждается после выполнения каждого
обработчика и событие OnBeforeCommand
возбуждается перед выполнением каждого
обработчика. Событие OnNoCommandHandler
возбуждается если обработчик, соответствующий команде, не найден.
Если свойство CommandHandlersEnabled равно true и
определены обработчики, то выполняется
их обработка. Иначе вызывается событие OnExecute, если оно назначено. Обработчик OnExecute не вызывается если были обработаны команды.
Если есть соединение, TCPServer читает строки текста из соединения и пытается найти
подходящий обработчик команд. Любые пустые строки игнорируются. Для непустых
строк сначала возбуждается событие OnBeforeCommandHandler. Затем ищется подходящий командный обработчик. Если
командный обработчик найден и его свойство enabled установлено в true, то возбуждается его событие OnCommand, а иначе возбуждается событие OnNoCommandHandler. После всего этого возбуждается событие OnAfterCommand.
Для демонстрации базовой реализации обработчиков
команд, определим простой протокол. Для демонстрации пользовательского сервера
времени реализуем три команды:
·
Help – показывает список
поддерживаемых команд и их форматы.
·
DateTime
<format> - возвращает текущую дату и время в указанном формате,
если формат не указан, то по умолчанию используется формат yyyy-mm-dd hh:nn:ss.
·
Quit – закрывает сессию и
отсоединяется.
. Для построения базового примера выполним следующие
шаги:
1. Создадим новое приложение.
2. Добавим TIdTCPServer на
форму.
3.
Установим
свойство TIdTCPServer.Default в 6000.
4.
Установим
свойство TIdTCPServer.Active в True. Это должно
активировать сервер при старте приложения.
Обработчики команд создаются при редактировании
свойства CommandHandlers класса TIdTCPServer. Свойство CommandHandlers – это коллекция. Обработчики могут быть модифицированы как во время
исполнения, так и во время разработки. Для редактирования обработчиков во время разработки нажмите на
кнопку <…> на свойстве CommandHandlers
в инспекторе объектов. Появится следующий диалог:
Он пока пуст, поскольку еще нет ни одного обработчика.
Для создания обработчика команд, или
нажмите правую кнопку мыши и выберите пункт Add, или нажмите первую кнопку в панели инструментов
диалога. После это в списке появится обработчик команд.
Для редактирования обработчика выберите его в
инспекторе объектов. Инспектор объектов выглядит как на приведенном рисунке.
Показано, что есть уже одно измененное свойство, реализующее команду. Это
команда QUIT и она будет обсуждена ниже.
Пошаговое описание реализации команды QUIT:
1.
Command = Quit – это команда сервера которую сервер будет
использовать для поиска обработчика при чтении ввода. Команда не чувствительна
к регистру.
2.
Disconnect = True – это значит, что сервер отсоединится от клиента
после получении и обработки данной
команды.
3.
Name = cmdhQuit – данное свойство не оказывает никакого влияния на
обработку, но оно предназначено для упрощения идентификации обработчика в коде.
Данный шаг необязательный.
4.
ReplyNormal.NumericCode = 200 – Команды обычно возвращают 3-х разрядный код и
необязательный текст. Задав это
свойство, мы указывем обработчику возвращать в ответ на команду код 200 и
дополнительный текст из ReplyNormal.Text если конечно не произойдет ошибка во время выполнения
команды.
5.
ReplyNormal.Text = Good Bye – дополнительный текст, который посылается вместе с ReplyNormal.NumericCode.
После этого мы имеем полностью работоспособный
обработчик команд.
Теперь когда обработчик создан, есть еще несколько глобальных
параметров, относящихся к северам на текстовых командах серверов и обработчиков
команд, которые также должны быть установлены. Все это свойства TIdTCPServer, а не обработчикам команд.
Обычной практикой для серверов, является
предоставления информации, приветствия от сервера, перед тем как сервер начнет
обрабатывать команды клиента. Типичный ответ сервера, показывающий, что сервер
готов, это код 200 и установка кода не равным нулю, разрешит посылку приветствия
Установите свойства Greeting. NumericCode = 200 и Greeting.Text в "Hello".
Если во время
обработки команд обнаружатся не обслуженные исключения, то используется данное
свойство со значением отличным от нуля. Код 500 это типичный ответ для
внутренних, неизвестных ошибок. Вместе с кодом отсылается и текстовый отзыв.
Установите ReplyExceptionCode в 500.
Если во время
обработки команд обнаружатся не обработанные исключения, то данное свойство
будет использовано для построения ответа, если его значение отличается от нуля.
Код 400 наиболее общий ответ для подобных случаев.
Установите ReplyUnknown.NumericCode в 400 и ReplyUnknown.Text в "Unknown Command".
Теперь, когда команда уже реализована можно приступить
и к тестированию, просто воспользуемся Telnet, поскольку протокол текстовый:
1. Запустим приложение.
2.
В меню Start: Run введем: telnet
127.0.0.1 6000 и нажмем OK. Это указывает Telnet подсоединиться к компьютеру на порт 6000, который
используется в демонстрационном примере.
3.
Сервер должен
ответить 200 Hello, что является приветствием,
из свойства Greeting of TIdTCPServer.
4.
Telnet затем
покажет каретку. Это означает, что сервер готов и ожидает команду.
5.
Введем HELP и нажмем enter.
Сервер ответит "400 Unknown
Command". Поскольку пока мы не создали командного
обработчика для команды HELP и ответ
"400 Unknown Command" был взят из свойства ReplyUnknown.
6.
Введем QUIT. Сервер ответит "200 Good Bye" и
отсоединится от клиента.
Команда HELP подобно по
поведению на команду QUIT за
исключением двух различий.
1. Не происходит разъединение сеанса.
2.
В дополнение
ответ также предоставляет текстовый отклик со справочной информацией.
Для реализации команды HELP выполним следующие шаги:
1. Создадим новый командный обработчик.
2. Command = Help
3. Name = cmdhHelp
4. ReplyNormal.NumericCode
= 200
5. ReplyNormal.Text =
Help Follows
Все эти шаги знакомы вам по реализации команды QUIT. Дополнительное свойство, которое здесь используется
- это свойство Response, которое является списком
строк. Если свойство Response
содержит текст, то оно посылается клиенту после отсылки ReplyNormal. Для реализации команды HELP используется редактор строк свойства Response:
Help - Display a
list of supported commands and basic help on each.
DateTime
<format> - Return the current date and/or time using the specified
format.
If no format is
specified the format yyyy-mm-dd hh:nn:ss will be used.
Quit - Terminate the session and disconnect.
Теперь если вы подсоединитесь к серверу и пошлете
команду HELP, то сервер ответит следующим образом:
200 Hello
help
200 Help Follows
Help - Display a
list of supported commands and basic help on each.
DateTime
<format> - Return the current date and/or time using the specified
format.
If no format is
specified the format yyyy-mm-dd hh:nn:ss will be used.
Quit - Terminate
the session and disconnect.
.
Команда DATETIME – это
последняя команда данного протокола. Оно отличатся и от QUIT и от HELP, в том, что
требует особой функциональности, которая не может быть создана только с помощью
свойств. Для реализации команды DATETIME будет
использован обработчик события.
Для начала построим базовый обработчик команды,
используя шаги с которым вы уже знакомы:
1. Создадим новый командный обработчик.
2. Command = DateTime
3. Name = cmdhDateTime
4. ReplyNormal.NumericCode
= 200
В данный момент свойство ReplyNormal.Text не
определяется, обработчик события будет его определять для каждого ответа. Для
определения обработчика, используйте инспектор объектов, выбрав командный
обработчик для DATETIME. Переключитесь на
закладку events и создайте событие OnCommand.
procedure
TForm1.IdTCPServer1TIdCommandHandler2Command(ASender: TIdCommand);
begin
end;
В обработчик OnCommand передается аргумент of ASender типа TIdCommand. Это
не командный обработчик, а сама команда. Командные обработчики глобальны для
всех соединений, тогда как, команды специфичны для соединения connection и обрабатываются в рамках экземпляра события OnCommand. Это гарантирует, что обработчик выполнит корректную
обработку для каждого клиентского соединения.
Перед вызовом обработчика события, Indy создает экземпляр команды и инициализирует его
свойства на основе данных обработчика команд. Вы можете использовать команды
для смены свойства со значений по умолчанию, вызывать методы для выполнения
задач или для доступа к свойству Connection для
общения с соединением напрямую.
Данный протокол определяет команду DATETIME, как имеющую дополнительный параметр, указывающий
формат даты и времени. Команда (TIdCommand) реализует это с помощью свойства Params, которое является списком строк. Когда команда
принимается от клиента и свойство ParseParams
установлено в true (по умолчанию) Indy
использует свойство CmdDelimeter (по умолчанию равное
#32 или пробел) для разделения команды и параметров.
Например, в данном протоколе, клиент может послать
следующее:
DATETIME hhnnss
В этом случае, свойство ASender.Params будет
содержать строку "hhnnss" в
свойстве ASender.Params[0]. Количество параметров может быть определено с
помощью свойства ASender.Params.Count.
Используя данные свойства обработчик OnCommand может быть реализован следующим образом:
procedure
TForm1.IdTCPServer1TIdCommandHandler2Command(ASender: TIdCommand);
var
LFormat: string;
begin
if
ASender.Params.Count = 0 then
begin
LFormat := 'yyyy-mm-dd
hh:nn:ss';
end
else
begin
LFormat := ASender.Params[0];
end;
ASender.Reply.Text.Text :=
FormatDateTime(LFormat, Now);
end;
данная реализация просто читает параметры и использует
ASender.Reply.Text для посылки ответа обратно клиенту. Не требуется
устанавливать ASender.Reply.NumericCode, так
как Indy инициализирует его значением 200, из командного
обработчика ReplyNormal.NumericCode.
Если снова протестировать пример с помощью telnet, то теперь ответ будет таким:
200 Hello
datetime
200
2002-08-26 18:48:06
В некоторых случаях свойство Params не может быть использовано. Свойство DATETIME одно из них. Представим себе следующую команду:
DATETIME mm dd yy
В данном случае значение свойства Params.Count будет
равно 3 и событие будет неверно обработано, возвратит только значение месяца (mm). Для данных случаев, когда значение параметра
включает разделители, можно использовать свойство UnparsedParams. Дополнительно, свойство ParseParams можно установить в False.
Свойство UnparsedParams содержит данные независимо от свойства ParseParams, но установка ParseParams в false увеличивает эффективность, сообщая Indy, что не требуется разбирать параметры в свойство Params.
Обработчик события, модифицированный для использования
с UnparsedParams:
procedure
TForm1.IdTCPServer1TIdCommandHandler2Command(ASender: TIdCommand);
var
LFormat: string;
begin
if
ASender.Params.Count = 0 then
begin
LFormat := 'yyyy-mm-dd
hh:nn:ss';
end
else
begin
LFormat := ASender.UnparsedParams;
end;
ASender.Reply.Text.Text := FormatDateTime(LFormat, Now);
end;
Управление потоками абстрагировано в Indy в менеджеры потоков. Менеджеры потоков позволяют
иметь различные (даже пользовательские) реализации стратегий управления
потоками.
Управление потоками - это необязательная
дополнительная возможность. Если вы не определяете менеджер потоков в свойстве ThreadManager в компоненте, который поддерживает управление
потоками (таком как TIdTCPServer) Indy неявно создает и уничтожает экземпляр менеджера
потоков.
Стратегия управления потоками по умодчанию в Indy очень простая. Каждый раз, когда требуется поток, он
создается. Когда поток не нужен, он уничтожается. Для большинства серверов –
это приемлемо и пока вам не понадобится пул потоков, вы должны использовать
стратегию управления потоками по умолчанию. В большинстве серверов различие в производительности
ничтожное или полностью отсутствует.
Стратегия по умолчанию также дает дополнительное
преимущество, так как, каждый поток гарантировано «чистый». Потоки часто
распределяют память или другие объекты. Эти объекты обычно освобождаются
автоматически, когда поток разрушается. Использование управления потоками по
умолчанию дает вам уверенность, что вся память освобождена и все очищено. Когда
используется пуле потоков, то вы должны быть уверены, что все очищено перед
повторным использованием потока. Несоблюдение этого правила, может привести к
тому что, пользователь будет иметь доступ к информации предыдузего
пользователя.. Такие предосторожности не требуются при использовании менеджера
потоков по умолчанию, поскольку все ассоциированные данные уничтожаются вместе
с потоком.
Для серверов, которые обслуживают коротко живущие
соединения, создание и уничтожение потоков, сравнимо со временем обслуживания
запроса. В данной ситуации, лучше использовать пул потоков.
В пуле потоки создаются предварительно и используются
повторно. Они создаются до использования и хранятся неактивными в пуле. Когда
требуется поток, то он берется из пула и активируется. Если требуется больше
потоков, чем есть в пуле, то создаются дополнительные потоки. Когда поток
больше не требуется, то вместо его разрушения он деактивируется и возвращается
в пул.
Создание и разрушение потоков может быть очень
интенсивным. Это особо относится к серверам, которые обслуживают коротко
живущие соединения. Такие сервера создают поток, который используется только
короткое время и затем уничтожается. Это приводит к очень высокой частоте
создания и уничтожения потоков. Примером таких серверов могуь служить сервера
времени или даже web сервера. Посылается простой
запрос и отсылается простой ответ. При использование браузера для просмотра
некоторых сайтов могут создаваться сотни соединений к серверу.
Пул потоков может смягчить данную ситуацию. Вместо
создания и уничтожения потоков по требованию, потоки выдаются из списка
неактивных потоков, которые уже созданы. Когда поток больше не нужен, он
возвращается обратно в пул. Пока потоки находятся в пуле – они отмечены как
неиспользуемые и поэтому не требуют ресурсов процессора. Как дальнейшее
усовершенствование - размер пула можно настраивать динамически, в зависимости
от потребностей системы. Indy имеет
поддержку пула потоков. Пул потоков в Indy
доступен через использование компонента TIdThreadMgrPool.