Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

Введение в клиентов

01.01.2007

6. Введение в клиентов

6.1. Базовый клиент

Базовый клиент Indy выглядит так:

with IndyClient do 
begin
  Host := 'test.atozedsoftware.com';
  Port := 6000;
  Connect; 
  Try
    // Read and write data here
  finally 
    Disconnect; 
  end;
end;
 

host и port могут быть установлены во время разработки с помощью инспектора объектов. Это минимальный код, который требуется при написании клиента в Indy. Минимальные требования для создания клиентов следующие:

1.Установка свойства Host.
2.Установка свойства Port. Требуется, если нет порта по умолчанию. Большинство протоколов имеют такой порт.
3.Соединение.
4.Передача данных. Включает чтение и запись.
5.Разъединение.
6.2. Обработка исключений

Обработка исключений в клиентах Indy такая же как с файлами. Если ошибка возникнет во время выполнения любого метода Indy, то будет возбуждено соответствующее исключение. Для обработки исключения код надо помещать в блоки try..finally или try..except blocks.

Также отсутствует событие OnError, так что не ищите его. Это может показаться странным, если вы уже работали с другими сокетными библиотеками, но посмотрите на TFileStream,  он также не имеет события OnError, просто если есть проблема, то возбуждается исключение. Indy работает подобным образом.

Подобно тому, как все открытые файлы должны быть закрыты, все вызовы Connect в Indy должны быть закрытым вызовом метода Disconnect. Базовые клиенты должны начитать работу следующим образом:

Client.Connect; 
try
  // Perform read/write here
finally 
  Client.Disconnect; 
end;
 

Исключения Indy только слегка отличаются от исключений VCL, все исключения Indy наследуются от EIdException. Если вы желаете обрабатывать исключения Indy отдельно от исключений VCL, то это можно сделать, как в следующем примере.

Примечание: Для использования EIdException вы должны добавить IdException в uses.

try
  Client.Connect; 
  try
    // Perform read/write here
  finally 
    Client.Disconnect; 
  end;
except
  on E: EIdException do 
  begin
    ShowMessage('Communication Exception: ' + E.Message);
  end 
  else 
  begin
    ShowMessage('VCL Exception: ' + E.Message);
  end;
end;
 

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

Для обработки ошибок во время соединения и отделения от других ошибок связи, требуется изменить ваш код:

try
  IdTCPClient1.Connect; 
  try
    try
      // Do your communications here
    finally 
      IdTCPClient1.Disconnect; 
    end;
  except
    on E: EIdException do 
    begin
      ShowMessage('An network error occurred during communication: ' + E.Message);
    end;
    on E: Exception do 
    begin
      ShowMessage('An unknown error occurred during communication: ' + E.Message);
    end;
  end;
except
  on E: EIdException do 
  begin
    ShowMessage('An network error occurred while trying to connect: ' + E.Message);
  end;
  on E: Exception do 
  begin
    ShowMessage('An unknown error occurred while trying to connect: ' + E.Message);
  end;
end;
 

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

6.3. Исключения это не ошибки

Многие разработчики серьезно считаю, что исключения это ошибки. Но это не так. Если бы это было так, то Borland бы назвал из ошибками, а не исключениями.

Исключение – это что-то, что за пределами ординарного. В терминах программирования, исключение – это что-то, что прерывает нормальный поток исполнения.

Исключения используются для представления ошибок в Delphi и по этому большинство исключений это ошибки. Тем не менее, есть такие исключения, как EAbort, которое не является ошибкой. Indy также определяет ряд исключений, которые не являются ошибками. Такие исключения, как правило, наследованы от EIdSilentException и могут быть легко отделены от ошибок и других исключений. Более сложный пример можно посмотреть в EIdConnClosedGracefully.

6.4. Компонент TIdAntiFreeze

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

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

Поскольку пользовательский интерфейс замораживается только при вызове блокирующих сокетов в главном кодовом потоке, TIdAntiFreeze влияет только на вызовы Indy, сделанные из главного кодового потока. Если приложение использует вызовы Indy из других потоков, TIdAntiFreeze не требуется. Но если используется, то влияет на вызовы сделанные только из главного кодового потока.

Использование TIdAntiFreeze немного замедляет работу сокетов. Сколько давать приоритета приложению задается в свойствах TIdAntiFreeze. Причина, по которой TIdAntiFreeze замедляет сокетовые операции, состоит в том, что главному кодовому потоку разрешается обрабатывать сообщения. По этому надо позаботиться, чтобы не позволять много времени отводилось обработке сообщений. Это включает большинство таких событий, как OnClick, OnPaint, OnResize и многие другие. Поскольку неблокирующие сокеты тоже обмениваются сообщениями, этаже проблема относится и к ним. С Indy и с помощью использования TIdAntiFreeze, программист получает полный контроль.

6.5. Пример - Проверка почтового индекса - клиент

Данный пример – это клиент, протокол просмотра почтовых индексов. Протокол очень простой и предполагается, что сервер уже реализован. В данной главе рассматривается только клиент.

Клиент обеспечивает получении имени города и штата по почтовому индексу (Zip код для американских пользователей). Исходные данные находятся на сервере для американских почтовых индексов. Американские почтовые индексы (называемые zip коды) состоят из 5 цифр.

Код сервера будет приведен позже.

6.5.1. Проверка почтового индекса - протокол

Протокол клиента очень прост, он содержит только две команды:

·Lookup <почтовый код 1> < почтовый код 2> ...
·Quit
Общение с сервером выглядит так:

Server: 204 Post Code Server Ready.

Client: lookup 16412

Server: 200 Ok

Server: 16412: EDINBORO, PA

Server: .

Client: lookup 37642 77056

Server: 200 Ok

Server: 37642: CHURCH HILL, TN

Server: 77056: HOUSTON, TX

Server: .

Client: quit

Server: 201-Paka!

Server: 201 4 requests processed.

The server responds with a greeting when the client connects. Greetings and replies to commands typically contain a 3 digit number specifying status. This will be covered more in detail in later sections.

После приветственного сообщения сервер готов принимать запросы от клиента. Если принята команда Lookup – сервер отвечает списком почтовых кодов и далее соответствующим именем города и штата. Ответ заканчивается строкой с единственным символом <точка>. Клиент может посылать множество команд, пока не выдаст команду Quit, после происходит рассоединение.

6.5.2. Объяснение кода

Клиент просмотра почтового кода содержит две кнопки, listbox и memo. Одна кнопка используется для очистки окна результатов, а другая для получения ответов от сервера и запрос информации от него. Результаты помещаются в listbox.

В обычном приложении, пользователь должен предоставить информацию об узле, порте и возможно о прокси. Но для демонстрации данная информация указана в коде. В качестве узла используется 127.0.0.1 и порт 6000.

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

procedure TformMain.butnLookupClick(Sender: TObject);
var
  i: integer;
begin
  butnLookup.Enabled := true; 
  try
    lboxResults.Clear;
    with Client do 
    begin
      Connect; 
      try
        // Read the welcome message
        GetResponse(204);
        lboxResults.Items.AddStrings(LastCmdResult.Text);
        lboxResults.Items.Add('');
        // Submit each zip code and read the result
        for i := 0 to memoInput.Lines.Count - 1 do 
        begin
          SendCmd('Lookup ' + memoInput.Lines[i], 200);
          Capture(lboxResults.Items);
          lboxResults.Items.Add('');
        end;
        SendCmd('Quit', 201);
      finally 
        Disconnect; 
      end;
    end;
  finally 
    butnLookup.Enabled := True; 
  end;
end;
 

Методы Indy, использованные здесь, объясняются только коротко, поскольку подробно они рассмотрены в других главах.

Когда код выполняется, то блокируется кнопка, чтобы пользователь не мог послать другой запрос, пока не закончен текущий. Вы можете подумать, что это не возможно, поскольку событие нажатия кнопки обрабатывается с помощью сообщений. Но поскольку данный пример использует TIdAntiFreeze, который вызывает Application.ProcessMessages и позволяет обрабатывать события отрисовки, так и другие события. По этой причине вы должны побеспокоиться о защите пользовательского интерфейса.

Используя TIdTCPClient (Client) – бросьте его на форму во время проектирования и попробуйте подключиться к серверу и подождите приветствия от сервера. GetResponse читает ответы и возвращает ответы как результат. В данном случае результат отбрасывается, но GetResult знает, что надо проверить ответ на число 204. Если Сервет отвечает другим кодом, то возбуждается исключение. Сервер может отвечать разными кодами, если он, например, очень, находится на профилактике и так далее.

Для каждого почтового индекса, который вводит пользователь, пример посылает команду lookup на сервер и ожидает код ответа 200. Если SendCmd закончится успешно, пример вызывает функцию Capture, которая читает ответы, пока не поступит с единственной точкой в строке. Поскольку демонстрационный пример за раз посылает одну команду, то ожидается одна строка в ответ, или ее отсутствие если индекс неверный.

По окончанию пример шлет команду Quit и ожидает ответа с кодом 201, который означает, что сервер понял и отсоединяет клиента. Правильным поведением, является всегда посылка команды Quit, чтобы обе стороны знали что произошло разъединение