Глубины Indy. 2. Техническая поддержка
Скачать 1.03 Mb.
|
5.11. Недостатки неблокирующего режима 1. Более сложное программирование – неблокирующие сокеты требуют использования опроса или обработки событий. События наиболее используемый метод, а циклы опроса менее эффективны. При использовании обработчиков событий, код размазан по куче процедур, поэтому требуется отслеживание состояния. Это означает большее количество ошибок и более сложная модификация кода. 5.12. Сравнение технологий 18 Если вы хорошо знаете Indy и его методологию, то вы можете пропустить эту главу. Но даже если вы ранее программировали сокеты, до использования Indy, то все равно данная глава будет вам полезна. Для тех, кто никогда не программировал сокеты до Indy, то будет легко и естественно использовать его. Но для тех кто программировал сокеты ранее, Indy будет камнем преткновения. Поскольку Indy работает совсем по другому. Попытка программировать в Indy тем же самым образом. Это не означает, что другие решения неправильные, просто Indy работает иначе. Пытаться программировать в Indy так же, как с другими сокетными библиотеками, равносильна попытке приготовить пирожное в микроволновой печи, как в духовке. Результатом будет испорченное пирожное. Если вы использовали другие сокетные библиотеки ранее, пожалуйста следуйте следующему девизу: Забудьте все, что вы знали раньше! Это легко сказать, труднее сделать, менять привычки тяжело. Чтобы подчеркнуть разницу, приведем абстрактный пример. Для абстрагирования концепции, используем в качестве аналога файлы. Данный документ подразумевает, что вы умеете работать с файлам. Надеемся, что Бейсик программисты не читают эту книгу. 5.13. Файлы против сокетов Разница между файлами и сокетами в основном в скорости доступа. Доступ к файлу не всегда быстрый. Флоппи диски, сетевые диска, ленточные устройства архивирования и иерархические системы хранения часто имеют медленную скорость. 5.14. Сценарий записи в файл Представим простой сценарий записи в файл. Поскольку данная процедура очень простая, то она очень подходит демонстрации. 1. Открыть файл 2. Записать данные 3. Закрыть файл 5.15. Блокирующий режим записи файла Блокирующая запись в файл выглядит следующим образом: procedure TForm1.Button1Click(Sender: TObject); var s: string ; begin s := 'Indy Rules the (Kudzu) World !' + #1 3 #1 0; try // Open the file with TFileStream.Create( 'c:\temp\test.dat' , fmCreate) do try // Write data to the file WriteBuffer(s[ 1 ], Length(s)); // Close the file finally 19 Free; end ; end ; end ; Как вы видите, это практически повторяет приведенный выше псевдокод. Код последовательный и легкий для понимания. 5.16. Неблокирующий режим записи файла Не существует такой вещи как неблокирующий режим записи файла (может быть исключая 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!' + #1 3 #1 0; 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 будет повторяться для оставшихся данных. 20 4. Шаг 3 повторяется до тех пор, пока все данные не будут записаны методом Write. По окончанию записи всех данных вызывается метод Close. Но файл пока еще не закрыт. 5. The OnClose event is fired. The file is now closed. 5.17. Сравнение записи файлов Оба примера только записывают данные. Чтение и запись данных будут сложнее для неблокирующего режима, но только добавлением одной строки для блокирующего режима. Для блокирующего примера, просто откройте, записывайте данные, и закройте файл когда необходимо: 3 File1 события 1 поле в Form Неблокирующая версия более сложная и более тяжелая для понимания. Дадим шанс выбора между обеими, если надо будет выбирать, то большинство выберет неблокирующий путь. Большинство C++ программистов, исключая конечно просто мазохистов или вообще не будет выбирать, поскольку все они почти просты. Почти все сокетные функции используют неблокирующий режим. 5.18. Почти как файлы Использование Indy почти равносильно использованию файлов. В действительности Indy еще проще, поскольку Indy имеет ряд методов для чтения и записи. Indy пример, эквивалентный примеру с файлами выглядит так: with IndyClient do begin Connect; Try WriteLn( 'Hello World.' ); finally Disconnect; end ; end ; Как вы можете видеть, Indy в действительности очень похож работе с файлами. Метод Connect замещает функцию Open, а метод Disconnect замещает функцию Close. Если вы думаете и признаете сокеты как чтение и запись в файл, то вам будет использовать Indy очень просто. 6. Введение в клиентов 6.1. Базовый клиент Базовый клиент Indy выглядит так: with IndyClient do begin Host := 'test.atozedsoftware.com' ; Port := 6 000; 21 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 22 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. Исключения это не ошибки 23 Многие разработчики серьезно считаю, что исключения это ошибки. Но это не так. Если бы это было так, то Borland бы назвал из ошибками, а не исключениями. Исключение – это что-то, что за пределами ординарного. В терминах программирования, исключение – это что-то, что прерывает нормальный поток исполнения. Исключения используются для представления ошибок в Delphi и по этому большинство исключений это ошибки. Тем не менее, есть такие исключения, как EAbort, которое не является ошибкой. Indy также определяет ряд исключений, которые не являются ошибками. Такие исключения, как правило, наследованы от EIdSilentException и могут быть легко отделены от ошибок и других исключений. Более сложный пример можно посмотреть в EIdConnClosedGracefully. 6.4. Компонент TIdAntiFreez e Indy имеет специальный компонент, который прозрачно разрешает проблему с замораживанием пользовательского интерфейса. Достаточно одного экземпляра компонента TIdAntiFreeze в приложении, позволяя использовать блокирующие вызовы в главном кодовом потоке, без замораживания пользовательского интерфейса. TIdAntiFreeze работает внутренне, независимо от вызова стека, и позволяет обрабатывать сообщения в течении периода таймаута. Внешний вызовы Indy продолжают быть блокированы и их код работает точно так же, как и без компонента TIdAntiFreeze. Поскольку пользовательский интерфейс замораживается только при вызове блокирующих сокетов в главном кодовом потоке, TIdAntiFreeze влияет только на вызовы Indy, сделанные из главного кодового потока. Если приложение использует вызовы Indy из других потоков, TIdAntiFreeze не требуется. Но если используется, то влияет на вызовы сделанные только из главного кодового потока. Использование TIdAntiFreeze немного замедляет работу сокетов. Сколько давать приоритета приложению задается в свойствах TIdAntiFreeze. Причина, по которой TIdAntiFreeze замедляет сокетовые операции, состоит в том, что главному кодовому потоку разрешается обрабатывать сообщения. По этому надо позаботиться, чтобы не позволять много времени отводилось обработке сообщений. Это включает большинство таких событий, как OnClick, OnPaint, OnResize и многие другие. Поскольку неблокирующие сокеты тоже обмениваются сообщениями, этаже проблема относится и к ним. С Indy и с помощью использования TIdAntiFreeze, программист получает полный контроль. 6.5. Пример - Проверка почтового индекса - клиент Данный пример – это клиент, протокол просмотра почтовых индексов. Протокол очень простой и предполагается, что сервер уже реализован. В данной главе рассматривается только клиент. Клиент обеспечивает получении имени города и штата по почтовому индексу (Zip код для американских пользователей). Исходные данные находятся на сервере для американских почтовых индексов. Американские почтовые индексы (называемые zip коды) состоят из 5 цифр. 24 Код сервера будет приведен позже. 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 25 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], 2 00); Capture(lboxResults.Items); lboxResults.Items.Add( '' ); end ; SendCmd( 'Quit' , 2 01); 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, чтобы обе стороны знали что произошло разъединение. 7. UDP 7.1. Обзор UDP (User Datagram Protocol) используется для датаграмм (datagram) и без установки соединения. UDP позволяет посылать короткие пакеты на узел, без установки соединения 26 с ним. Для UDP пакетов не гарантируется доставка и не гарантируется тот же порядок приема, в каком они были посланы. При посылке UDP пакета, он посылается в одном блоке. Поэтому вы не должны превышать максимальный размер пакета, указанный в вашем TCP/IP стеке. Из-за этих факторов, многие люди считают UDP малопригодными. Но это не совсем так. Многие потоковые протоколы, такие как Real Audio, используют UDP. Примечание: термин "потоковый" может быть легко перепутан с термином поток (stream) для TCP. Когда вы видите эти термины, вы должны разобраться с контекстом, в котором они применяются, для определения точного значения. |