17. Серверы

В Indy есть несколько серверных моделей.

17.1.1. Класс TIdTCPServer

Наиболее известный сервер в Indy – это TIdTCPServer.

Класс TIdTCPServer создает вторичный слушающий поток, который независим от главного потока программы. Слушающий поток следит за входящими запросами от клиентов. Для каждого клиента, которому он отвечает, он создает  новый поток, для специфического сервиса, индивидуального соединения. Все соответствующие события затем возбуждаются к контексте этого потока.

17.1.1.4. Модели серверов

Есть два пути построения 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 смог нормально их отработать.

17.1.1.5. Командные обработчики

 Командные обработчики – это новая концепция, используемая в TIdTCPServer, которая позволяет серверу  выполнять парсинг команды и обрабатывать его для вас. Для каждой команды, которую вы желаете обрабатывать сервером, создается командный обработчик. Думайте об командных обработчиках, как о списке действий для сервера. Командный обработчик содержит свойства, которые указывают, как парсить параметры, команды, некоторые действия, которые могут быть выполнены автоматически и дополнительные автоматические ответы. В некоторых случаях, используя только свойства, вы сможете создать полностью функциональную команду без необходимости написания какого либо кода. Каждый командный обработчик имеет уникальное событие 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.1 Событие OnExecute

Событие OnExecute ссылается на событие OnExecute класса TIdTCPServer. При реализации сервера по данной модели, должно быть определено событие OnExecute или перекрыт метод DoExecute.

Модель OnExecute допускает полный контроль разработчиком и позволяет реализовывать любые типы протоколов, включая двоичные протоколы.

После подсоединения клиента к серверу, возбуждается событие OnExecute. Если событие OnExecute не определено, то возбуждается исключение. Событие OnExecute возбуждается в цикле, как только подсоединяется клиент. Это очень важная деталь и поэтому разработчик должен побеспокоиться об

1.       Помнить о том,  что событие возникает в цикле.

2.       Не препядствовать Indy выполянть обработку в цикле.

 

На этапе проверки соединения выполняется следующие проверки:

·        Клиент еще подсоединен

·        Disconnect не был вызван во время OnExecute

·        Отсутствуют фатальные ошибки

·        Не было возбуждено необработанное исключение в OnExecute

·        Сервер еще активен

Если все эти проверки и другие проверки истинны, то событие OnExecute возбуждается снова. Поэтому, разработчик никогда не должен конструировать свой цикл внутри OnExecute, который будет дублировать это, так как это будет мешать Indy.

17.4. Обработчики команд (Command Handlers)

 

Обработчики команд подобны спискам действий для сервера. Командные обработчики работают следующим образом: - вы создаете обработчик команд для каждой команды и затем используя обработчик команд определяете поведение для конкретной команды. Когда команда принята от клиента, то сервер автоматически разбирает ее и передает конкретному обработчику. Обработчики команд не только имеют свойства для настройки поведения, но также методы и события.

Обработчики команд работают только с текстовыми командами и ответами TCP протоколов. Тем не менее это покрывает нужды почти 95% серверов используемых в настоящее  время.

17.4.1. Реализация

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

. Для построения базового примера выполним следующие шаги:

1.       Создадим новое приложение.

2.       Добавим TIdTCPServer на форму.

3.       Установим свойство TIdTCPServer.Default в 6000.

4.       Установим свойство TIdTCPServer.Active в True. Это должно активировать сервер при старте приложения.

17.4.4. Создание обработчика команд

Обработчики команд создаются при редактировании свойства 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.

После этого мы имеем полностью работоспособный обработчик команд.

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.6. Тестирование новой команды

Теперь, когда команда уже реализована можно приступить и к тестированию, просто воспользуемся 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" и отсоединится от клиента.

17.4.7. Реализация HELP

Команда 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.

.

17.4.8. Реализация DATETIME

Команда DATETIME – это последняя команда данного протокола. Оно отличатся и от QUIT и от HELP, в том, что требует особой функциональности, которая не может быть создана только с помощью свойств. Для реализации команды DATETIME будет использован обработчик события.

Для начала построим базовый обработчик команды, используя шаги с которым вы уже знакомы:

1.       Создадим новый командный обработчик.

2.       Command = DateTime

3.       Name = cmdhDateTime

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.

Если снова протестировать пример с помощью 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.7. Управление потоками

Управление потоками абстрагировано в Indy в менеджеры потоков. Менеджеры потоков позволяют иметь различные (даже пользовательские) реализации стратегий управления потоками.

Управление потоками - это необязательная дополнительная возможность. Если вы не определяете менеджер потоков в свойстве ThreadManager в компоненте, который поддерживает управление потоками (таком как TIdTCPServer) Indy неявно создает и уничтожает экземпляр менеджера потоков.

17.7.1. Класс TIdThreadMgrDefault

Стратегия управления потоками по умодчанию в Indy очень простая. Каждый раз, когда требуется поток, он создается. Когда поток не нужен, он уничтожается. Для большинства серверов – это приемлемо и пока вам не понадобится пул потоков, вы должны использовать стратегию управления потоками по умолчанию. В большинстве серверов различие в производительности ничтожное или полностью отсутствует.

Стратегия по умолчанию также дает дополнительное преимущество, так как, каждый поток гарантировано «чистый». Потоки часто распределяют память или другие объекты. Эти объекты обычно освобождаются автоматически, когда поток разрушается. Использование управления потоками по умолчанию дает вам уверенность, что вся память освобождена и все очищено. Когда используется пуле потоков, то вы должны быть уверены, что все очищено перед повторным использованием потока. Несоблюдение этого правила, может привести к тому что, пользователь будет иметь доступ к информации предыдузего пользователя.. Такие предосторожности не требуются при использовании менеджера потоков по умолчанию, поскольку все ассоциированные данные уничтожаются вместе с потоком.

17.7.2. Пул потоков (Thread Pooling)

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

В пуле потоки создаются предварительно и используются повторно. Они создаются до использования и хранятся неактивными в пуле. Когда требуется поток, то он берется из пула и активируется. Если требуется больше потоков, чем есть в пуле, то создаются дополнительные потоки. Когда поток больше не требуется, то вместо его разрушения он деактивируется и возвращается в пул.

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

Пул потоков может смягчить данную ситуацию. Вместо создания и уничтожения потоков по требованию, потоки выдаются из списка неактивных потоков, которые уже созданы. Когда поток больше не нужен, он возвращается обратно в пул. Пока потоки находятся в пуле – они отмечены как неиспользуемые и поэтому не требуют ресурсов процессора. Как дальнейшее усовершенствование - размер пула можно настраивать динамически, в зависимости от потребностей системы. Indy имеет поддержку пула потоков. Пул потоков в Indy доступен через использование компонента TIdThreadMgrPool.

 

Hosted by uCoz