Глубины Indy

         

Компонент TIdIOHandlerStream


Компонент TIdIOHandlerStream используется для отладки и тестирования. При его использовании все взаимодействие с сервером в TCP сессии может быть записаны. Позже вся сессия может быть «проиграна». Компоненты Indy не знают, работают ли они с реальным сервером и реальным соединением.

Это очень мощное средство отладки в дополнение к инструменту QA отладки. Если у пользователя есть проблемы, то специальная версия программы может быть послана пользователю или включены средства отладки для ведения лога сессии. Используя лог файлы, вы можете затем реконструировать сессию пользователя в локальной отладочной среде.



Компонент TIdSSLIOHandlerSocket


Компонент TIdSSLIOHandlerSocket используется для поддержки SSL. Обычно компрессия и декомпрессия данных может быть реализована с использованием перехватчиков (Intercepts) вместо IOHandlers. Но SSL библиотека используемая Indy (OpenSSL), работает напрямую с сокетом, вместо трансляции данных посылаемых Indy.  Поэтому реализация выполнена как IOHandler. . TIdSSLIOHandlerSocket является наследником TIdIOHandlerSocket.



Компонент TIdThread


Компонент TIdThread – это наследник от TThread и добавляет дополнительные расширенные свойства, большинство из которых предназначены для построения серверов и также предоставляет поддержку пулов потоков и повторного использования.

Если вы знакомы с потоками VCL, то очень важно принять во внимание, что TIdThread резко различается в нескольких ключевых областях. В TThread, метод Execute должен быть перегружен в наследниках, но в TIdThread должен быть перегружен метод Run. Ни в коем случаен не перегружайте метод Execute класса TIdThread, так как это будет препядствовать внутренним операциям TIdThread.

Для всех наследников TIdThread, метод Run должен быть перегружен. Когда поток становится активным, выполняется метод Run. Метод Run последовательно вызывается в TIdThread, пока поток не будет остановлен.  Это может быть не очевидно для большинства клиентских программ, тем не менее это может быть особо полезно для всех серверов и некоторых случаев на клиенте. В этом также отличие от метода Execute класса TThread. Метод Execute вызывается только один раз. Когда завершается Execute, то поток также завершен.

Есть и другие различия между TIdThread и TThread, но они не так значительны, как Run и Execute.



Компонент TIdUDPClient


TIdUDPClient – это базовый компонент для посылки UDP пакетов. Наиболее часто используемый метод – это Send, в котором используются свойства Host и Port для посылки UDP пакета. Аргументом функции является строка.

Есть также метод SendBuffer, который выполняет туже задачу, но аргументами являются Buffer и Size.

TIdUDPClient также может использоваться как сервер, для приема входящих UDP пакетов.





Компонент TIdUDPServer


Поскольку UDP работает без соединений, то TIdUDPServer немного иначе, чем TIdTCPServer. TIdUDPServer не имеет режимов подобным TIdSimpleServer, Так как UDP не использует соединений, то TIdUDPClient (видимо здесь опечатка и имеется в виду TIdUDPServer) имеет только простые слушающие методы.

При активации TIdUDPServer создает слушающий поток для входящих UDP пакетов. Для каждого принятого UDP пакета, TIdUDPServer возбуждает событие OnUDPRead в главном кодовом потоке, или в контексте слушающего потока, в зависимости от свойства ThreadedEvent.

Когда свойство ThreadedEvent сброшено, то событие OnUDPRead возбуждается в контексте главного кодового потока. Когда свойство ThreadedEvent установлено, то событие OnUDPRead возбуждается в контексте слушающего потока.

Вне зависимость от состояния ThreadedEvent true или false, блокируется обработка других сообщений. Поэтому обработка OnUDPRead по возможности должна быть короткой.



Компонент TThreadList


Компонент TThreadList – это потоко-безопасная реализация класса TList. Класс TList может быть использован в любом количестве потоков без необходимости защиты от одновременного доступа.

Класс TThreadList работает подобно TList, но не совсем также. Некоторые методы, такие как as Add, Clear и Remove аналогичны. Для других операций, класс the TThreadList должен быть заблокирован с помощью метода LockList. метод LockList – это функции и она возвращает ссылку на внутренний экземпляр класса TList. Когда он заблокирован, все другие потоки будут заблокированы. По этому, очень важно разблокировать (Unlock) как можно быстрее.

Пример операций с TThreadList:

with MyThreadList.LockList do

try

  t := Bytes div {/} KILOry

  for i := 0 to Count - 1 do

  begin

    // Operate on list items

    Items[i] := Uppercase(Items[i]);

  end;

finally

  MyThreadList.UnlockList;

end;

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



Контексты (Contexts)


В серверах Indy 9, данные специфичные для соединения были частью класса потока. Они были доступны, или через свойство thread.data, или наследованием от класса потока с добавлением новых полей или свойств для хранения. Это работало, потому что каждое соединение имело только один поток, который был специально назначен для данного соединения.

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

Indy 10 использует новую концепцию, названную контекстами. Контексты хранят специфические данные соединения и они обслуживаются и отслеживаются Indy.



Критические секции (Critical Sections)


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

Для входа в критическую секцию вызовите метод Enter и затем метод Leave для выхода из секции. Класс TCriticalSection также имеет методы Acquire и Release, которые аналогичны Enter и Leave соответственно.

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

var

  GLogCS: TCriticalSection;

  GUserLog: TStringList;

procedure TformMain.IdTCPServer1Connect(AThread: TIdPeerThread);

var

  s: string;

begin

  // Username

  s := ReadLn;

  GLogCS.Enter;

  try

    GUserLog.Add('User logged in: ' + s);

  finally

    GLogCS.Leave;

  end;

end;

procedure TformMain.Timer1Timer(Sender: TObject);

begin

  GLogCS.Enter;

  try

    listbox1.Items.AddStrings(GUserLog);

    GUserLog.Clear;

  finally

    GLogCS.Leave;

  end;

end;

initialization

  GLogCS := TCriticalSection.Create;

  GUserLog := TStringList.Create;

finalization

  FreeAndNil(GUserLog);

  FreeAndNil(GLogCS);

end.

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

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

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

Примечание пользователям Delphi 4: Класс TCriticalSection находится в модуле SyncObjs. Модуль SyncObjs обычно не включен в Delphi 4 Standard Edition. Если Вы используете Delphi 4, то Вы можете загрущить SyncObjs.pas файл с web сайта Indy. Этот файл не срдержит всей функциональности реализованной Борланд, но имеет реализацию класса TCriticalSection.



Курица или яйцо?


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

Ответ прост, проще построить сервер первым. Для тестирования клиента, вы должны иметь сервер. Для тестирования сервера вам нужен клиент. Но поскольку, протоколы как правило текстовые, то клиент легко эмулировать с помощью телнета.

Для тестирования, подсоединитесь к серверу на его порт. В командной строке введите:

Telnet newsgroups.borland.com 119

Теперь нажмите клавишу enter. После соединения вы должны увидеть экран, подобный нижнему рисунку.

На Windows 95/98/ME и NT это может выглядеть немного иначе, чем на Windows 2000 или на Windows XP, но результат должен быть таким же. Другие версии Windows загружают телнет, как новое приложение в свом окне. Выше показанный рисунок относится к Windows XP', в котором телнет является консольным приложением.

Команда "Telnet newsgroups.borland.com 119" указывает клиенту телнета, подсоединиться к newsgroups.borland.com на порт 119. Порт 119 используется для протокола NNTP (News). Подобно подсоединения к серверу новостей Borland, телнет может использоваться для соединения с любым сервером, использующим текстовый протокол.

Для отсоединения от сервера новостей введите команду "Quit" и нажмите enter.



LAN


LAN это сокращение от Local Area Network.

Что именно локальная сеть очень зависит от конкретной топологии сети и может варьироваться. Тем не менее, LAN относится ко всем системам, подключенным к Ethernet повторителям (hubs) и коммутаторам (switches), или в некоторых случаях к Token ring или другим. К LAN не относятся другие LAN, подключенные с помощью мостов или маршрутизаторов. То есть к LAN относятся только те части, до которых сетевой трафик доходит без использования мостов и маршрутизаторов.

Можно думать об LAN, как об улицах, с мостами и маршрутизаторами, которые объединяют города скоростными трассами.



Localhost (Loopback)


LOCALHOST подобен "Self" в Delphi или "this" в C++. LOCALHOST ссылается на компьютер, на котором выполняется приложение. Это адрес обратной петли и это реальный физический IP адрес, со значением 127.0.0.1. Если 127.0.0.1 используется на клиенте, он всегда возвращает пакет обратно на тот же компьютер, для сервера на том же компьютере, что и клиент.

Это очень полезно для отладки. Это также может быть использовано для связи с сервисами, запущенными на этом же компьютере. Если вы имеете локальный web сервер, то вам не надо знать его адрес и изменять свои скрипты, каждый раз как адрес будет изменен, вместо этого используйте 127.0.0.1.

От переводчика: вообще то Localhost, может иметь и другой адрес, но как правило, это 127.0.0.1



Маппирование портов / Туннели


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

Внутренняя сеть определятся как одна сторона сети в локальной сети и другой сети или внешней сети, к которой подключена внутренняя сеть. (Бр – фраза, не подлежащая к переводу, очень трудная к пониманию, принимайте как получилось. Если сказать своими словами – это часть сети отделенная от других сетей, как локальных, так и глобальных Почему бы так и не сказать?)

Поскольку маршрут блокирован, весь доступ возможен только через туннелирование порта. Порты назначаются на локальном сервере, который доступен извне. Этот сервер затем пересылает данные из и во внутреннею сеть. Недостатком маппированых портов является то, что маппированый порт маппируется на фиксированный удаленный узел и порт. Для таких протоколов, как почтовые и сервера новостей они определены заранее и работают нормально. Но для протоколов подобных HTTP данный способ не работает, поскольку удаленное место неизвестно до общения (речь про доступ изнутри).

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



Маппирование типов в CTS


Что бы работать с .NET классами все языки должны использовать CTS (Common Type System). Delphi .NET может делать это просто, в дополнение к типам Delphi. Это может иметь место в ситуации, когда обычный Delphi код использует один набор типов, а интерфейсы к .NET использует другой набор. В результате потребуется постоянное копирование данных туда и обратно, поэтому это не очень хорошая идея с .NET. Подобная ситуация аналогична ситуацией с COM.

Ел избежание подобной проблемы, родные типы в Delphi .NET имеют их маппированые типы в CTS. Так что при объявлении Integer, это в реальности .NET Integer из CTS. Данная связь не ограничена только простыми типами, но также расширена и на объекты.

Здесь приведен список некоторых подстановок:

Delphi .Net

Common Type System

String

System.String

Variant

System.ValueType

Records

System.ValueType

Exception

System.Exception

TObject

System.Object

TComponent

System.ComponentModel.Component



Метод TIdSync


Метод TThread имеет метод Synchronize, но он не имеет возможности передавать параметры в синхронизационные методы. Метод TIdSync имеет такую возможность. Метод TIdSync также позволяет возвращать значения из главного потока.



Методология Indy


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

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

Работа с Indy аналогична работе с файлами.  Indy позволяет поместить весь код в одно место, вместо создания различных разработчиков событий. В дополнение, многие находят Indy более простым в использовании. Indy также разработан на работу с потоками. Если вы имеет проблемы с реализацией чего-либо в Indy, то вернитесь назад и реализуйте это как для файлов.



Мьютексы (Mutexes)


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

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

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



Модели программирования


В Windows есть две модели программирования сокетов – блокирующий и неблокирующий. Иногда они также называются как – синхронный (blocking) и асинхронный (non-blocking). В данном документе мы будем использовать термины блокирующий и неблокирующий.

На платформе Unix, поддержан только блокирующий режим.



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


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



Модели TCP серверов


TCP сервер Indy поддерживает две модели для построения серверов. Данные методы это OnExecute и командные обработчики. Indy 8 поддерживает только OnExecute.



Модули и пространство имен


Директивы Uses должны быть преобразованы к использованию пространства имен.

Если код требует одновременного использования в Windows и в .NET framework данные модули должны использовать IFDEF как указано. Константа CLR определена в Delphi .NET.

uses

{$IFDEF CLR}Borland.Win32.Windows{$ELSE}Windows{$ENDIF},

{$IFDEF CLR}Borland.Delphi.SysUtils{$ELSE}SysUtils{$ENDIF},

{$IFDEF CLR}Borland.Vcl.Forms{$ELSE}Forms{$ENDIF};

Пространство имен будет увидено. Три главных из них – это Borland.Win32 (Windows), Borland.Delphi (RTL) and Borland.VCL (VCL for .NET).



Надежность


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

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

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

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



Не безопасные элементы


В Delphi 7 появилось новое свойство, названое "Unsafe". При компилировании вашего кода в Delphi 7, вы получите предупреждение об не безопасных элементах. Не безопасные элементы – это такие элементы, которые непозволительно использовать в .NET рантайм.

Данные предупреждения включены по умолчанию и серьезно замедляют работу компилятора. Поэтому если вы не компилируете код для .NET, то вы можете их отключить. Они производят, то что я назвал эффект "C++ effect". Они замедляют компиляцию и генерируют большое количество предупреждений, что приводит к высокому соотношению сигнал-шум ".

Delphi 7 может быть использован для разработки кода, который вы желаете перенести в .NET, но DCCIL не генерирует предупреждений об небезопасных элементах. Поэтому первый шаг – это откомпилировать код в Delphi 7 и затем удалить предупреждения об небезопасных элементах.

Delphi разделяет предупреждения на три группы – небезопасные типы, небезопасный код и небезопасное приведение.



Не явное разрушение


.Net разделяет эти функции – финализации и освобождения памяти, поскольку памятью заведует .NET. .Net использует неявное разрушение. Если вы работали с интерфейсами, то семантика подсчета ссылок, используемая в интерфейсах аналогична.

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



Не нужна последовательность


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

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



Небезопасные приведения (Casts)


Небезопасное приведение включает следующее:

Приведение к экземпляру класса, если он не является наследником. Любые приведения записей, тип record

Небезопасные типы


Небезопасные типы включают следующее:

Символьные указатели: PChar, PWideChar, and PAnsiChar Не типизированные указатели Не типизированные var и out параметры File of <type> Real48 Записи с вариантными частями (Не путайте с variants)

Небезопасный код


Небезопасный код включает следующее:

Абсолютные переменные (Absolute) Функции Addr(), Ptr(), Hi(), Lo(), Swap() Процедуры BlockRead(), and BlockWrite() Процедура Fail() Процедуры GetMem(), FreeMem(), ReallocMem() Ассемблерный код Операторы работы с указателями

Неблокирующий режим


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

Например, для попытки соединения сокета, вы должны вызвать метод Connect. Данный метод немедленно возвращает управление в программу. Когда сокет будет подсоединен, то будет возбуждено событие. Это требует, что бы логика связи была разделена по многим процедурам или использовать циклы опроса.



Неблокирующий режим записи файла


Не существует такой вещи как неблокирующий режим записи файла (может быть исключая overlapped I/O, но это за пределами данной книги), но здесь мы можем просто эмулировать механизм это. File1 это условный неблокирующий компонент, размещенной на форме.

procedure TForm1.Button1Click(Sender: TObject);

begin

  File1.Filename := 'd:\temp\test.dat';

  File1.Open;

end;

procedure TForm1.File1OnOpen(Sender: TObject);

var

  i: integer;

begin

  FWriteData := 'Hello World!' + #13#10;

  i := File1.Write(FWriteData);

  Delete(FWriteData, 1, i);

end;

procedure TForm1.File1OnWrite(Sender: TObject);

var

  i: integer;

begin

  i := File1.Write(FWriteData);

  Delete(FWriteData, 1, i);

  if Length(FWriteData) = 0 then

  begin

    File1.Close;

  end;

end;

procedure TForm1.File1OnClose(Sender: TObject);

begin

  Button1.Enabled := True;

end;

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

1. При вызове Button1Click открывается файл. Метод Open немедленно вернет управление в программу, но файл еще не открыт и нельзя с ним еще нельзя работать.

2.      Обработчик события OnOpen будет возбужден, когда файл будет открыть и готов к работе. Делается попытка записать данные в файл, но все данные еще не акцептированы. Метод Write вернет количество записанных байт. Оставшиеся данные будут сохранены позже.

3.      Обработчик события OnWrite будет возбужден, когда файл будет готов воспринять следующую порцию данных, и метод Write будет повторяться для оставшихся данных.

4.      Шаг 3 повторяется до тех пор, пока все данные не будут записаны методом Write. По окончанию записи всех данных вызывается метод Close. Но файл пока еще не закрыт.

5.      The OnClose event is fired. The file is now closed.



Недостатки блокирующего режима


1.      Пользовательский интерфейс замораживается в клиентах - Вызов блокирующего сокета не возвращает управления, пока не выполнит свою задачу. Когда подобные вызовы делаются в главном кодовом потоке, то приложение замораживает пользовательский интерфейс. Замораживание происходит, поскольку сообщения обновления, перерисовки и другие сообщения не обрабатываются до окончания вызова блокирующего сокета.



Недостатки неблокирующего режима


1.      Более сложное программирование – неблокирующие сокеты требуют использования опроса или обработки событий. События наиболее используемый метод, а циклы опроса менее эффективны. При использовании обработчиков событий, код размазан по куче процедур, поэтому требуется отслеживание состояния. Это означает большее количество ошибок и более сложная модификация кода.



Непрозрачные прокси


Непрозрачные прокси требуют вмешательства в используемые протоколы. Многие протоколы имеют встроенную поддержку для непрозрачных прокси.



Несколько процессоров


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

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

Наилучший путь получить преимущества от нескольких процессоров, это использование нескольких потоков в вашем приложении. Это не только позволяет вашему приложению использовать несколько процессоров, но и дает больше процессорного времени вашему приложению, поскольку у вас больше потоков.



Неуправляемый код (Unmanaged Code)


Неуправляемый код – это откомпилированный в машинные инструкции, который получен с помощью Delphi for Windows.  Неуправляемый код также включает код DLL, COM серверов и даже Win32 API. Основная цель любого .NET приложения – это не имееть такого кода.



Нужна удача


Если вам повезло, то ваш проект преобразован и работает нормально.

Перенос – это не только перекомпиляция и потребует некоторого времени. Перенос в .NET требует подобных усилий, которые нужны для переноса в из VCL приложений в Visual CLX. Хотя время и усилия значительны, но это все равно меньше, чем писать с нуля и позволяет делать кросс платформенную разработку, так как код будет повторно использоваться и там и там, конечно если он спланирован должным образом.



Об этой книге


Это предварительное издание. В работе еще находится множество материалов. Оно включает объединение, преобразование и импортирование из разных источников. В дополнение, вы увидите несколько глав, с примечаниями, похожими на лепет. Поскольку мы еще пока работаем над материалом. Не волнуйтесь, если некоторые главы пока немного бессмысленны в данное время.

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

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



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


Клиент просмотра почтового кода содержит две кнопки, 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, чтобы обе стороны знали что произошло разъединение



Обнаружение разъединения


Поскольку Indy блокирующая по природе, и ее события используются только для обработки состояния, поэтому в ней нет событий оповещения о неожиданном разъединении соединения. Если вызов чтения или записи находится в состоянии исполнения и происходит неожиданный разрыв соединения, то вырабатывается исключение. Если же в этот момент нет вызова на чтение/запись, то исключение возникнет при первом вызове.

Имеется событие OnDisconnected, но это не то, о чем вы подумали. Событие OnDisconnected возникает при вызове метода Disconnect. Это событие никак не связано с неожиданным разъединением.



Обновление пользовательского интерфейса


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

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



Обновления


Обновления можно найти на сайте http://www.atozedsoftware.com/.



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


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

Обработчики команд автоматически читают и разбирают команды. При этом возбуждается специфическое событие OnCommand.


Создание серверов в Indy всегда было достаточно простой задачей, тем не менее в Indy 9 это стало проще после введения командных обработчиков в класс TCP сервера (TIdTCPServer).

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

Обработчики команд работают только с текстовыми командами и ответами TCP протоколов. Тем не менее это покрывает нужды почти 95% серверов используемых в настоящее  время. Хотя обработчик в состоянии работать с двоичными данным, но сами команды могут быть только текстовыми. Имеется некоторое количество протоколов, которые работают с помощью двоичных команд. Для протоколов, использующих двоичные команды или текстовые команды, которые не совместимы (от корректора: труднореализуемые??? Не могу себе представить что он имел  в виду, потому что conversational это словоохотливый, разговорчивый), реализация обработчиков команд не является обязательной что позволяет сохранить обратную совместимость .



Обработчики ввода/вывода (IOHandlers)


Indy может настраиваться и расширяться многими путями, без необходимости напрямую модифицировать исходный код. Примером такой расширяемости являются обработчики ввода/вывода (IOHandlers). Обработчики ввода/вывода позволяют вам использовать любой источник ввода/вывода I/O в Indy. Обработчики ввода/вывода должны использоваться, когда вы желаете использовать альтернативный механизм ввода/вывода или создание нового транспортного механизма.

Обработчики ввода/вывода выполняют весь ввод/вывод (Input/Output) для Indy. Indy не выполняет ни какого своего ввода/вывода, за пределами обработчика ввода/вывода. Обработчик ввода/вывода используется для посылки сырых TCP данных для компонент Indy.

Обработчики ввода/вывода позволяют классам обрабатывать весь ввод/вывод в Indy. Обычно, весь ввод/вывод делается через сокет и обслуживается обработчиком по умолчанию -  TIdIOHandlerSocket.

Каждый TCP клиент имеет свойство IOHandler, которое может быть назначено обработчику IOHandler, как это делает каждое серверное соединение. Если обработчик IOHandler не указан, то неявно используется TIdIOHandlerSocket, который создается автоматически и используется TCP клиентом. TIdIOHandlerSocket реализует ввод/вывод используя TCP сокет. Indy также включает дополнительные обработчики ввода/вывода: TIdIOHandlerStream и TIdSSLIOHandlerSocket.

Другие обработчики ввода/вывода могут быть созданы, позволяя Indy использовать любые источники ввода/вывода, которые вы только можете вообразить. В данный момент Indy поддерживает только сокеты, потоки и SSL как источники ввода/вывода, но источники ввода/вывода позволяют и другие возможности. Пока нет других планов, но обработчики ввода/вывода могут быть созданы для поддержки туннелей, IPX/SPX, RS-232, USB или Firewire. Indy не ограничивает вас в выборе вашего источника ввода/вывода и использование обработчиков ввода/вывода позволяет это сделать.



Обработка исключений


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



Обратная связь


Пожалуйста, посылайте ваши замечания по данной книге на Indy@atozedsoftware.com. Не обращайтесь за технической поддержкой на данный адрес. Для технической поддержки смотрите главу 2. Техническая поддержка.



Общие проблемы


Наибольшей проблемой при работе с потоками является параллельное выполнение. Поскольку потоки выполняются параллельно, то имеется проблема доступа к общим данным. При использовании потоков в приложении, возникают следующие проблемы:

При выполнение клиентов в потоках, приносит следующие проблемы с конкурированием:

1. обновление пользовательского интерфейса из потока.

2.      общение с главным потоком из вторичных потоков.

3.      доступ к данным главного потока из вторичных потоков.

4.      возвращение результата из потока.

5.      определение момента завершения потока.



Обзор


Данная глава является введением в концепцию сокетов (TCP/IP сокеты). Это не означает, что мы рассмотрим все аспекты сокетов; это только начальное обучение читателя, что бы можно было начать программировать.

Есть несколько концепций, которые должны быть объяснены в первую очередь. При возможности, концепции будут подобны концепция телефонных систем.


UDP (User Datagram Protocol) используется для датаграмм (datagram) и без установки соединения. UDP позволяет посылать короткие пакеты на узел, без установки соединения с ним. Для UDP пакетов не гарантируется доставка и не гарантируется тот же порядок приема, в каком они были посланы. При посылке UDP пакета, он посылается в одном блоке. Поэтому вы не должны превышать максимальный размер пакета, указанный в вашем TCP/IP стеке.

Из-за этих факторов, многие люди считают UDP малопригодными. Но это не совсем так. Многие потоковые протоколы, такие как Real Audio, используют UDP.

Примечание: термин "потоковый" может быть легко перепутан с термином поток (stream) для TCP. Когда вы видите эти термины, вы должны разобраться с контекстом, в котором они применяются, для определения точного значения.




В локальной сети надежность UDP достаточно высокая. Но в случае WAN или Интернет вы, возможно, пожелаете  разработать какую ни будь схему подтверждений.




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

Пример называется «Remote BSOD invocator» - или сокращено RBSOD. RBSOD может быть использован для создания ложных BSOD (От переводчика: Blue Screen Of Dead – это знаменитый экран смерти) на машинах коллег (или врагов). BSOD не является реальным BSOD, и не делает ни каких потерь данных у жертвы. Тем не менее, он  должен напугать его startle. Сам BSOD также может быть сброшен удаленно.

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

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

Предоставлено много параметров. В зависимости от выбранных параметров, BSOD может быть почти настоящим или появляться как шутка (ничего себе шуточки, особенно учитывая возможности бродкаста). Хотя это появляется как шутка, но клиенту нужно несколько секунд, чтобы понять это и течении этого периода он испытывает настоящий страх.

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

Предоставлены следующие параметры.



Обзор клиентов


Indy разработан для создания высокого уровня абстракции. Сложности и детализация TCP/IP стека  скрыты от программиста.

Типичный клиент сессия в Indy выглядит так:

with IndyClient do begin

  Host := 'postcodes.atozedsoftware.com'; // Host to call

  Port := 6000; // Port to call the server on

  Connect;

  try

    // Do your communication

  finally

    Disconnect;

  end;

end;



Обзор серверов


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



Ограничения


Разработка в Delphi .NET требует использования некоторых ограничений. Эти ограничения требуют, что бы код Delphi, подчинялся требованиям и ограничениям .NET.



Определение пользовательского протокола


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

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

При определении протокола, должны быть сделаны следующие решения:

Текстовые или двоичные команды? Пока нет особых требований, используйте текстовые команды. Текстовые команды проще для понимания и для отладки. TCP или UDP? Это определяется от требований к протоколу. Изучите все характеристики и решите с осторожностью. В большинстве случаев TCP правильный выбор. Порт – каждому серверному приложению требуется выделенный порт для прослушивания. Порты ниже 1024 резервированы и никогда не должны использоваться, кроме реализации протокола, которому уже назначен порт ниже 1024.

После определения команд, также должны быть определены ответы и отклики.



От переводчика


Зачем Я стал переводить данную книгу? Ну, это потому что по данной теме очень мало информации, особенно на русском языке. Поэтому я рискнул. Но поскольку я не профессиональный переводчик, то возможны некоторые погрешности в переводе, поэтому принимайте как есть. В конце концов, дареному коню в зубы не смотрят.

Перевод основан на старой предварительной версии книги, к сожалению, у меня нет окончательной редакции. Но даже и в этой редакции, информация приведенная в данной книге того стоит.

Об авторах, они пришли из мира Юникс, отсюда некоторая ненависть к Windows и к неблокирующим вызовам. У авторов также чувствуется некоторый хакерский и даже вирус-мейкерский подход, это видно из текста, в части приведения примера почти готового трояна, одобрение нарушения законодательства в части мер по передачи алгоритмов строгого шифрования и какими методами это было сделано. Но все это не снижает ценности данной книги. Текст, который очень простой и почти не составил сложностей с переводом, кроме некоторых мест.

В настоящее время есть три направления построения Интернет библиотек:

1. библиотеки событийно-ориентированные, большинство компонент Delphi – к этому классу относится ICS (Internet Component Suite от Франсуа Пьетте http://www.overbyte.be);

2.      библиотеки с линейным кодом, структурно ориентированное программирование – к этому классу относится Indy;

3.      чистые процедуры и функции, типичный представитель Synapse www.ararat.cz/synapse

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

К сожалению, от версии к версии код становится все более и более монстроидальным.

О чем же эта книга, если вы подумали, как следует из названия, что про Indy, то это далеко не так. Эта не книга по Indy, а книга про Интернет, про протоколы, термины, методы работы, а к Indy относятся только примеры. Особенно отличается глава 20, в которой приведены примеры миграции на Delphi for .NET

По окончанию перевод было обнаружено много ошибок и благодаря любезной помощи Михаила Евтеева  (Mike Evteev) была проведена серьезная корректировка книги. Мой корявый язык был заменен литературным, уточнена терминология и были исправлены некоторые грамматические ошибки.

Все, кто участвовал в данной работе, являются полноправными членами команды по переводу данной книги.



Отчеты об ошибках


Все сообщения об ошибках должны посылаться через форму сообщения об ошибке на сайте Indy http://www.nevrona.com/Indy or http://Indy.torry.NET/



Откидываемая функциональность (Deprecated Functionality)


Некоторые элементы были отброшены, так как они не совместимы с .NET и поэтому бесполезны. Многие из этих элементов вы уже знаете из ранних глав.

Тип Real48. используйте BCD или другие математические функции. Функции GetMem(), FreeMem() и ReallocMem(). Используйте динамические массивы или net управление классами. Процедуры BlockRead(), BlockWrite(). Используйте классы из .NET framework. Директива Absolute Функции Addr и @. Используйте классы вместо блоков памяти. Старые тип объектов Паскаль, ключевое слово object. Используйте только ключевое слово class. TVarData и прямой доступ до потрохов variant. Семантика Variant поддержана, но только без прямого доступа до внутренностей. File of <type> - размер типов варьируется от платформы к платформе и не может быть определен во время компилирования и поэтому не может быть использован. Не типизированные var и out параметры. Используйте директиву const для параметра или класс родителя. Указатель PChar. В действительности Delphi .NET поддерживает PChar как не обслуживаемый код. Директивы automated и dispid. Данные директивы неприменимы в .NET. Директива asm – ассемблер не поддержан в .NET, код не компилируется в машинный код. TInterfacedObject, который включает AddRef, QueryInterface и Release. Динамические агрегаты – используйте implements. примечание: Implements не реализовано в текущей версии DCCIL. ExitProc

Отклики (response)


Отклик – это часть данных, которая возвращается в ответ на команду. Отклики дополнительны и не присутствуют во всех команда. Отклики посылаются после ответа, для распознавание правильный ли ответ получен и какой формат этого отклика.

Отклики могут быть текстовыми или двоичными. Если отклик текстовый, то обычно этот отклик в формате RFC откликов.



Отладка


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



Ответы (reply)


Ответы – это короткий ответ на посланную команду. Ответ содержит информацию о состоянии – успешно ли ошибка, и иногда содержит небольшое количество данных.

Например, если команда GET customer.dat, вернет ответ 200 OK, это будет означать, что команда воспринята и будет обработана. Ответы обычно состоят только из одной строки, но могут состоять и из нескольких строк.

В отличие от команд, текстовая часть ответа может быть локализована на другой язык, если она соответствует ограничению 7-бит ASCII. Поскольку протокол использует цифровую часть, а текстовая часть используется только для конечного пользователя и для отладки.