здесь нет необходимости проверять действительность соединения, так как Indy делает это автоматически. Так же нет необходимости производить опрос, поскольку и это Indy делает автоматически за вас. Она вызывает событие периодически, пока соединение не прекратится. Это может быть вызвано или явным отсоединением, или по сетевой ошибке, или если клиент отсоединился. В действительности, не требуется делать никаких опросов на предмет отсоединений. Если данный опрос все же необходимо делать, вам надо только позаботиться об возбуждении исключений, что бы Indy смог нормально их отработать.
17.1.1.5. Командные обработчики
Indy 9.0 содержит новое средство, называемое командные обработчики. Командные обработчики – это новая концепция, используемая в TIdTCPServer, которая позволяет серверу выполнять парсинг команды и обрабатывать его для вас. Командные обработчики это разновидность «визуального управления сервером» и является только маленьким заглядыванием в будущие реализации Indy.
Для каждой команды, которую вы желаете обрабатывать сервером, создается командный обработчик. Думайте об командных обработчиках, как о списке действий для сервера. Командный обработчик содержит свойства, которые указывают, как парсить параметры, команды, некоторые действия, которые могут быть выполнены автоматически и дополнительные автоматические ответы. В некоторых случаях используя только свойства, вы сможете создать полностью функциональную команду без необходимости написания какого либо кода. Каждый командный обработчик имеет уникальное событие OnCommand. Когда событие вызывается, то нет необходимости определять какая команда была запрошена, так как данное событие уникально для каждого командного обработчика. В дополнение, командный обработчик уже распарсил параметры и выполнил автоматические действия для вас.
Вот маленький пример использования командных обработчиков. Во-первых вы должны определить две команды: QUIT и DATE. Создадим два командных обработчика, как указано ниже:
Для cmdhQuit свойство disconnect устанавливается в true. Для cmdhDate событие OnCommand определяется так:
procedure TForm1.IdTCPServer1cmdhDateCommand(ASender: TIdCommand);
begin
ASender.Reply.Text.Text := DateTimeToStr(Date);
end;
это законченный код командного обработчика. Все другие детали, специфицированы установкой свойств в командных обработчиках.
Командные обработчики более подробно описаны в главе «командные обработчики».
17.1.2. Класс TIdUDPServer
Поскольку UDP не требуется соединения (по определению), то класс TIdUDPServer работает отлично от TIdTCPServer. Подобно TIdSimpleServer класс TIdUDPServer не имеет некоторых режимов, и поскольку UDP не требуется соединения, TIdUDPClient имеет только слушающие методы.
При активизации, класс TIdUDPServer создает слушающий поток, для прослушивания входящих UDP пакетов. Для каждого, принятого UDP пакета, класс TIdUDPServer возбуждает событие OnUDPRead в главном потоке или в контексте слушающего потока, в зависимости от значения свойства ThreadedEvent.
Когда значение свойства ThreadedEvent = false, то событие OnUDPRead возбуждается в контексте главного потока программы. Когда значение свойства ThreadedEvent = true, то событие OnUDPRead возбуждается в контексте слушающего потока.
Когда значение свойства ThreadedEvent равно false, то блокируется прием дальнейших сообщений. Поэтому событие OnUDPRead должно быть как можно более быстрым.
17.1.3. Класс TIdSimpleServer
Класс TIdSimpleServer предназначен для разового использования серверов. Класс TIdSimpleServer предназначен для обслуживания одного соединения за раз. Хотя он может обслуживать другие запросы по окончанию, обычно он используется только для одного запроса.
Класс TIdSimpleServer не создает потоков для прослушивания или вторичных потоков соединения. Вся функциональность реализована в одном потоке.
Компонент клиента TIdFTP использует класс TIdSimpleServer. Когда FTP выполняет передачу, вторичное TCP соединение открывается для передачи данных и закрывается, когда данные будут переданы. Данное соединение называется «канал данных (data channel)» и оно уникально для каждого передаваемого файла.
17.2. События потоков
События TIdTCPServer потоковые. Это означает, что они не являются частью потока, они выполняются внутри потока. Это очень важная деталь. Будьте уверены, что вы понимаете эту деталь до начала обработки.
Это сначала может показаться странным, что событие может быть частью формы но при этом выполняться внутри потока. Тем не менее, это было умышленно так сконструировано, чтобы события созданные в дизайн тайм, были подобны любым другим событиям, без необходимости создания пользовательского класса и перекрытия метода.
В создаваемых компонентах наследниках, перекрытие тоже доступно. Но для построения приложений, модель обработчиков событий намного проще в применении.
Каждый клиент создает свой собственный поток. При использовании данного потока, события TCP сервера (который является частью формы или модуля данных) вызываются из данных потоков. Это означает, что одиночное событие может быть вызвано множество раз из разных потоков. Такие события получают в качестве аргумента AThread, который указывает на поток из которого возбуждено событие.
Примерами потоковых события являются командные обработчики сервера OnConnect, OnExecute и OnDisconnect.
17.3. Модели TCP серверов
TCP сервер Indy поддерживает две модели для построения серверов. Данные методы это OnExecute и командные обработчики. Indy 8 поддерживает только OnExecute.
17.3.1 Событие OnExecute
Событие OnExecute ссылается на событие OnExecute класса TIdTCPServer. При реализации сервера по данной модели, должно быть определено событие OnExecute или перекрыт метод DoExecute.
Модель OnExecute допускает полный контроль разработчиком и позволяет реализовывать любые типы протоколов, включая двоичные протоколы.
После подсоединения клиента к серверу, возбуждается событие OnExecute. Если событие OnExecute не определено, то возбуждается исключение. Событие OnExecute возбуждается в цикле, как только подсоединяется клиент. Это очень важная деталь и поэтому разработчик должен побеспокоиться об
1. | Помнить о том, что событие возникает в цикле. |
2. | Не препядствовать Indy выполянть обработку в цикле. |
Внутренний цикл показан на следующей диаграмме:
На этапе проверки соединения выполняется следующие проверки:
· | Disconnect не был вызван во время OnExecute |
· | Отсутствуют фатальные ошибки |
· | Не было возбуждено необработанное исключение в OnExecute |
Если все эти проверки и другие проверки истинны, то событие OnExecute возбуждается снова. Поэтому, разработчик никогда не должен конструировать свой цикл внутри OnExecute, который будет дублировать это, так как это будет мешать Indy.
17.3.2. Обработчики команд (Command Handlers)
Командные обработчики подобны спискам действий для построения серверов, в стиле визуальной среды проектирования. Командные обработчики ограничены текстовыми протоколами. Данные, передаваемые по протоколу, могут быть двоичными.
Обработчики команд автоматически читают и разбирают команды. При этом возбуждается специфическое событие OnCommand.
17.4. Обработчики команд (Command Handlers)
Создание серверов в Indy всегда было достаточно простой задачей, тем не менее в Indy 9 это стало проще после введения командных обработчиков в класс TCP сервера (TIdTCPServer).
Обработчики команд подобны спискам действий для сервера. Командные обработчики работают следующим образом: - вы создаете обработчик команд для каждой команды и затем используя обработчик команд определяете поведение для конкретной команды. Когда команда принята от клиента, то сервер автоматически разбирает ее и передает конкретному обработчику. Обработчики команд не только имеют свойства для настройки поведения, но также методы и события.
Обработчики команд работают только с текстовыми командами и ответами TCP протоколов. Тем не менее это покрывает нужды почти 95% серверов используемых в настоящее время. Хотя обработчик в состоянии работать с двоичными данным, но сами команды могут быть только текстовыми. Имеется некоторое количество протоколов, которые работают с помощью двоичных команд. Для протоколов, использующих двоичные команды или текстовые команды, которые не совместимы (от корректора: труднореализуемые??? Не могу себе представить что он имел в виду, потому что conversational это словоохотливый, разговорчивый), реализация обработчиков команд не является обязательной что позволяет сохранить обратную совместимость .
17.4.1. Реализация
Класс TCPServer содержит свойство, именуемое CommandHandlers, которое является коллекцией обработчиков команд. Обработчики обычно создаются во время разработки, тем не менее, при реализации наследников они могут создаваться и во время исполнения. Если командные обработчики создаются во время исполнения, то они должны быть созданы путем перекрытия метода InitializeCommandHandlers. Это гарантирует, что они создадутся только во время исполнения. Если они создаются в конструкторе, они будут создаваться каждый раз, когда TCPServer загружается из потока и записывается обратно в поток. Это может привести к созданию множества копий для каждого обработчика. Инициализация, напротив, вызывается только однажды после первой активации TCPServer.
Класс TCPServer содержит несколько свойств и событий имеющих отношение к обработчикам команд. Свойство CommandHandlersEnabled разрешает или запрещает работу обработчика как единого целого. Свойство OnAfterCommandHandler возбуждается после выполнения каждого обработчика и событие OnBeforeCommand возбуждается перед выполнением каждого обработчика. Событие OnNoCommandHandler возбуждается если обработчик, соответствующий команде, не найден.
Если свойство CommandHandlersEnabled равно true и определены обработчики, то выполняется их обработка. Иначе вызывается событие OnExecute, если оно назначено. Обработчик OnExecute не вызывается если были обработаны команды.
Если есть соединение, TCPServer читает строки текста из соединения и пытается найти подходящий обработчик команд. Любые пустые строки игнорируются. Для непустых строк сначала возбуждается событие OnBeforeCommandHandler. Затем ищется подходящий командный обработчик. Если командный обработчик найден и его свойство enabled установлено в true, то возбуждается его событие OnCommand, а иначе возбуждается событие OnNoCommandHandler. После всего этого возбуждается событие OnAfterCommand.
17.4.2. Пример протокола
Для демонстрации базовой реализации обработчиков команд, определим простой протокол. Для демонстрации пользовательского сервера времени реализуем три команды:
· | Help – показывает список поддерживаемых команд и их форматы. |
· | DateTime <format> - возвращает текущую дату и время в указанном формате, если формат не указан, то по умолчанию используется формат yyyy-mm-dd hh:nn:ss. |
· | Quit – закрывает сессию и отсоединяется. |
Это очень простая базовая реализация, но она работает вполне приемлемо для целей демонстрации. Конечно, вы можете ее расширить для того чтобы изучить возможности обработчиков команд.
17.4.3. Базовый пример
Сначала сконструируем базу для построения примера. Это подразумевает, что вы уже умеете работать с TIdTCPServer и вот ваши следующие шаги. Для построения базового примера выполним следующие шаги:
1. | Создадим новое приложение. |
2. | Добавим TIdTCPServer на форму. |
3. | Установим свойство TIdTCPServer.Default в 6000. порт 6000 это порт для демо, выбранный случайным образом, можно использовать любой свободный порт. |
4. | Установим свойство TIdTCPServer.Active в True. Это должно активировать сервер при старте приложения. |
Этим мы создали базовое приложение. Оно пока ничего не делает, поскольку еще нет ни обработчиков команд, ни событий.
17.4.4. Создание обработчика команд
Обработчики команд создаются при редактировании свойства CommandHandlers класса TIdTCPServer. Свойство CommandHandlers – это коллекция. Обработчики могут быть модифицированы как во время исполнения, так и во время разработки. Для редактирования обработчиков во время разработки нажмите на кнопку <…> на свойстве CommandHandlers в инспекторе объектов. Появится диалог. Он пока пуст, поскольку еще нет ни одного обработчика. Для создания обработчика команд, или нажмите правую кнопку мыши и выберите пункт Add, или нажмите первую кнопку в панели инструментов диалога. После это в списке появится обработчик команд. Для редактирования обработчика выберите его в инспекторе объектов. Редактирование обработчиков подобно редактированию полей набора данных БД или колонок в DBGrid. Если инспектор объектов не виден, то нажмите F11 для его отображения.
Инспектор объектов выглядит как на приведенном рисунке. Показано, что есть уже одно измененное свойство, реализующее команду. Это команда 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. |
После этого мы имеем полностью работоспособный обработчик команд.
17.4.5. Поддержка обработчика команд
Теперь когда обработчик создан, есть еще несколько глобальных параметров, относящихся к северам на текстовых командах серверов и обработчиков команд, которые также должны быть установлены. Все это свойства TIdTCPServer, а не обработчикам команд.
17.4.5.1. Свойство Greeting (приветствие)
Обычной практикой для серверов, является предоставления информации, приветствия от сервера, перед тем как сервер начнет обрабатывать команды клиента. Типичный ответ сервера, показывающий, что сервер готов, это код 200 и установка кода не равным нулю, разрешит посылку приветствия
Установите свойства Greeting. NumericCode = 200 и Greeting.Text в "Hello".
17.4.5.2. Свойство ReplyExceptionCode
Если во время обработки команд обнаружатся не обслуженные исключения, то используется данное свойство со значением отличным от нуля. Код 500 это типичный ответ для внутренних, неизвестных ошибок. Вместе с кодом отсылается и текстовый отзыв.
Установите ReplyExceptionCode в 500.
17.4.5.3. Свойство ReplyUnknownCommand
Если во время обработки команд обнаружатся не обработанные исключения, то данное свойство будет использовано для построения ответа, если его значение отличается от нуля. Код 400 наиболее общий ответ для подобных случаев.
Установите ReplyUnknown.NumericCode в 400 и ReplyUnknown.Text в "Unknown Command".
17.4.5.4. Прочие свойства
У TIdTCPServer есть еще и другие свойства и события, для реализации дополнительного поведения, относящего к командным обработчикам, но приведенные выше являются минимумом, который должен быть реализован.
17.4.6. Тестирование новой команды
Теперь, когда команда уже реализована можно приступить и к тестированию, просто воспользуемся Telnet, поскольку протокол текстовый:
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 и DATETIME, которые имеют отличное от команды QUIT поведение.
17.4.7. Реализация HELP
Команда HELP подобно по поведению на команду QUIT за исключением двух различий.
1. | Не происходит разъединение сеанса. |
2. | В дополнение ответ также предоставляет текстовый отклик со справочной информацией. |
Для реализации команды HELP выполним следующие шаги:
1. | Создадим новый командный обработчик. |
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.
.
17.4.8. Реализация DATETIME
Команда DATETIME – это последняя команда данного протокола. Оно отличатся и от QUIT и от HELP, в том, что требует особой функциональности, которая не может быть создана только с помощью свойств. Для реализации команды DATETIME будет использован обработчик события.
Для начала построим базовый обработчик команды, используя шаги с которым вы уже знакомы:
1. | Создадим новый командный обработчик. |
4. | ReplyNormal.NumericCode = 200 |
В данный момент свойство ReplyNormal.Text не определяется, обработчик события будет его определять для каждого ответа. Для определения обработчика, используйте инспектор объектов, выбрав командный обработчик для DATETIME. Переключитесь на закладку events и создайте событие OnCommand. Delphi создаст обработчик следующим образом:
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.
Примечание: используйте свойство ASender.Reply.Text.Text. Указание слова Text дважды требуется потому что свойство Text команды это список строк и мы имеем также TStrings.Text в дополнение к этому. Поскольку это список строк, другие методы или свойства, такие как Add, Delete и другие также могут использоваться. Свойство Text используется как ASender.Reply.Text, в некоторых случаях может быть предварительно проинициализировано и запись в него вызовет перекрытия текста.
Если снова протестировать пример с помощью 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;
17.4.9. Заключение
Обработчики команд очень гибки и содержат большее количество свойств и методов, чем приведено. Это только введение в обработчики команд и их возможности. Надеюсь, что этого достаточно, что вызвать у вас интерес и начать работать.
Имеется также особые планы для будущих версий – сделать командные обработчики еще более наглядными и удобными на стадии разработки.
17.5. Postal Code Server - реализация OnExecute
OnExecute Implementation
Demo
Threads
17.6. Postal Code Server – командные обработчики
Command Handlers
Greeting
CommandHandlers
ReplyException
ReplyTexts
ReplyUnknownCommand
Demo
17.7. Управление потоками
Управление потоками абстрагировано в Indy в менеджеры потоков. Менеджеры потоков позволяют иметь различные (даже пользовательские) реализации стратегий управления потоками.
Управление потоками - это необязательная дополнительная возможность. Если вы не определяете менеджер потоков в свойстве ThreadManager в компоненте, который поддерживает управление потоками (таком как TIdTCPServer) Indy неявно создает и уничтожает экземпляр менеджера потоков.
17.7.1. Класс TIdThreadMgrDefault
Стратегия управления потоками по умодчанию в Indy очень простая. Каждый раз, когда требуется поток, он создается. Когда поток не нужен, он уничтожается. Для большинства серверов – это приемлемо и пока вам не понадобится пул потоков, вы должны использовать стратегию управления потоками по умолчанию. В большинстве серверов различие в производительности ничтожное или полностью отсутствует.
Стратегия по умолчанию также дает дополнительное преимущество, так как, каждый поток гарантировано «чистый». Потоки часто распределяют память или другие объекты. Эти объекты обычно освобождаются автоматически, когда поток разрушается. Использование управления потоками по умолчанию дает вам уверенность, что вся память освобождена и все очищено. Когда используется пуле потоков, то вы должны быть уверены, что все очищено перед повторным использованием потока. Несоблюдение этого правила, может привести к тому что, пользователь будет иметь доступ к информации предыдузего пользователя.. Такие предосторожности не требуются при использовании менеджера потоков по умолчанию, поскольку все ассоциированные данные уничтожаются вместе с потоком.
17.7.2. Пул потоков (Thread Pooling)
Обычно диспетчеризация потоков по умолчанию вполне приемлема. Тем не менее, для серверов, которые обслуживают коротко живущие соединения, создание и уничтожение потоков, сравнимо со временем обслуживания запроса. В данной ситуации, лучше использовать пул потоков.
В пуле потоки создаются предварительно и используются повторно. Они создаются до использования и хранятся неактивными в пуле. Когда требуется поток, то он берется из пула и активируется. Если требуется больше потоков, чем есть в пуле, то создаются дополнительные потоки. Когда поток больше не требуется, то вместо его разрушения он деактивируется и возвращается в пул.
Создание и разрушение потоков может быть очень интенсивным. Это особо относится к серверам, которые обслуживают коротко живущие соединения. Такие сервера создают поток, который используется только короткое время и затем уничтожается. Это приводит к очень высокой частоте создания и уничтожения потоков. Примером таких серверов могуь служить сервера времени или даже web сервера. Посылается простой запрос и отсылается простой ответ. При использование браузера для просмотра некоторых сайтов могут создаваться сотни соединений к серверу.
Пул потоков может смягчить данную ситуацию. Вместо создания и уничтожения потоков по требованию, потоки выдаются из списка неактивных потоков, которые уже созданы. Когда поток больше не нужен, он возвращается обратно в пул. Пока потоки находятся в пуле – они отмечены как неиспользуемые и поэтому не требуют ресурсов процессора. Как дальнейшее усовершенствование - размер пула можно настраивать динамически, в зависимости от потребностей системы. Indy имеет поддержку пула потоков. Пул потоков в Indy доступен через использование компонента TIdThreadMgrPool.