В последнее время (а это практически месяц) мне пришлось очень плотно работать с библиотекой Indy, в частности с несколькими её компонентами для работы с HTTP-протоколом. Использование другой библиотеки не обсуждался в принципе, т.к. переписывать весь код уже достаточно большого проекта только из-за того, что мне не нравится Indy никто бы и не стал. Да я и не просил. Опыт лишним не бывает.
Поэтому я с головой влез в дебри модулей последней версии Indy и начал активно разрабатывать свою часть проекта. И, кстати, могу сказать, что в большинстве случаев «глючность» Indy решается простым непринужденным выпрямлением рук и чтением мануалов по библиотеке. Indy не идеальная библиотека, есть и проблемы с её работой (с некоторыми встречался, про другие просто слышал), но в целом для выполнения повседневных задач типа отправить/получить запрос по http/https, провести base-аутентификацию и т.д. библиотека вполне подходит. Как знают мои постоянные читатели, несколько раз я упоминал в своем блоге на ряду с прочими компонентами для работы в Сети библиотеку ICS (Internet Component Suite) и даже некоторое время назад переписал модуль для API Twitter с Indy на ICS версий 5 (этого модуля в доступе нет) и 7. То бишь маленький, скромный, но тем не менее опыт использования ICS имеется.
И вот в процессе работы над текущим проектом у меня родилась мысль-идея — написать статью, которая поможет совсем уж неопытным в деле работы с HTTP-протоколом в Delphi программистам разобраться в различиях, преимуществах, особенностях и недостатках двух библиотек — Indy и ICS. Поэтому в этой статье я постараюсь обойтись без «тыканья палкой с гвоздями» в разработчиков библиотек (это незачем) и ограничиться только рассмотрением фактов. Итак начнем.
Первое и самое главное различие, которое необходимо запомнить и знать заключается в режимах работы. Indy использует блокирующий режим работы с сокетами, в то время как основным режимом ICS является неблокирующий (асинхронный) режим (хотя можно организовать и синхронную передачу данных). Именно по причине этого различия и начинаются разного рода толкования про плохую Indy и хорошую ICS. Сами по себе, что синхронный, что асинхронный режимы работы имеют как свои преимущества так и недостатки, просто надо ещё ДО начала работы над кодом программы определиться какой из режимов Вам больше подойдет. Например, частенько можно встретить высказывания от новичков про то, что дескать Indy «кривая» потому, что при выполнении длительных запросов программа «виснет». Так как (без использования дополнительных компонентов) программе не виснуть на запросе, если режим работы синхронный? Indy в силу своей блокирующей природы поступает абсолютно правильно — не выходит из метода до тех пор, пока не доведет работу до логического завершения. Завершили — вышли. Если уж так хочется Indy и не хочется зависаний — используйте дополнительный компонент idAntiFreeze или работайте с потоками.
И вот, в силу своего главного различия, использование ICS после долгого использования Indy может вызвать некоторые затруднения в разработке.
Так как ICS использует асинхронный режим работы, то большая часть работы над программой будет основываться над обработкой тех или иных событий (про события компонента THTTPCli можно почитать здесь). Приведу простой пример — получения текста web-страницы.
Так Вы бы могли сделать, используя Indy:
Memo1.Lines.Text:=IdHTTP1.Get('http://webdelphi.ru');//выполнили запрос ShowMessage('Страница скачана'); //вывели сообщение
И программа после скачивания страницы выдала бы Вам сообщение «Страница скачана». Это элементарный пример блокирующего режима работы — пока не скачали страницу ждем. Также само мы могли бы организовать и работу в ICS, но, если Вам необходим асинхронный режим, то этот же пример несколько усложняется, т.к. о факте завершения запроса нам необходимо будет ориентироваться по событиям компонента:
procedure TForm2.BtnGetClick(Sender: TObject); begin Memo1.Lines.Clear; RcvdStream:=TStringStream.Create; HttpCli1.URL:='http://webdelphi.ru';//назначили URL HttpCli1.RcvdStream:=RcvdStream; //назначили поток для сохранения данных HttpCli1.GetASync;//запустили асинхронное выполнения запроса end; procedure TForm2.HttpCli1RequestDone(Sender: TObject; RqType: THttpRequest; ErrCode: Word); begin //событие OnRequestDone - завершение запроса Memo2.Lines.LoadFromStrea(RcvdStream); ShowMessage('Страница скачана'); //вывели сообщение RcvdStream.Free end;
Чувствуете различие? С Indy весь код занял две строки, а с ICS пришлось писать дополнительный обработчик события иначе мы б не узнали просто так о завершении запроса. Вот Вам и контраргумент в пользу Indy — при асинхронном режиме работы в ICS требуется иной подход к разработке (событийно-ориентированный), который часто требует больше времени. И этот вывод сделан только лишь на основании одного просто го примерчика. Нет, мы, безусловно, можем также само воспользоваться событием OnWorkEnd у компонента Indy и вывести сообщение там, но зачем без лишней надобности городить огород — все равно результат выполнения запроса мы узнаем только после выхода из метода Get().
Второе также немаловажное различие между Indy и ICS заключается в обработке исключений. В одном из последних постов в блоге я говорил о том, что меня жутко бесит принцип обработки исключений в Indy. Меня это способ и сейчас не радует, но делать нечего, если решили остановить свой выбор на Indy — привыкайте, что и на ответ сервера «200 Ok» Вам могут просигналить об исключении. Опять же небольшой, но показательный пример. Довольно частой при работе с HTTP является проблема остановки выполнения запроса в любой момент времени, например, на середине работы или после получения определенных данных. Про решение этой задачи в Synapse я рассказывал неоднократно, теперь примеры в Indy и ICS.
Как принудительно прервать выполнение запроса в Indy? Для этого надо как минимум:
- Воспользоваться обработкой сообщений
- Уметь работать с исключениями Indy и учится их правильно отлавливать.
- OnWorkBegin — сообщает о начале работы
- OnWork — сообщает информацию о ходе выполнения запроса
- OnWorkEnd — сообщает о завершении работы.
procedure TForm2.Button1Click(Sender: TObject); begin Memo1.Lines.Clear; try Memo1.Lines.Text:=IdHTTP1.Get('http://webdelphi.ru'); except on E: EIdConnClosedGracefully do ShowMessage('Сработало исключение EIdConnClosedGracefully'); on E:EIdException do ShowMessage('Сработало другое исключение Indy (EIdException)'); end; end;
Здесь мы как и в предыдущем примере Indy пробуем запросить контент страницы блога, выполнив GET-запрос. Если запрос пройдет успешно — в Memo выведется весь текст со страницы. Если же закачка будет прервана по какой-либо причине, то мы попытаемся знать какое из исключений Indy сработало. В качестве примера я проверяю было ли исключение принудительного закрытия соединение (EIdConnClosedGracefully), которое относится к «тихим» исключениям Indy (к ним относятся все исключения порожденные от EIdSilentException) или же сработало какое-либо другое исключение.
Теперь обратимся к обработчику OnWork компонента idHTTP. В чем будет заключаться код обработчика? Во-первых в обработчике OnWork мы должны определить надо ли разрывать соединение или нет. Для этого у нас есть значение — 1024 байта или более полученной информации. Во-вторых, не забывая о том, что Indy работает в блокирующем режиме, здесь же в обработчике нам необходимо как-то сохранить уже полученные данные.
Пишем такой обработчик:
procedure TForm2.IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64); var S: TStringStream; begin if AWorkCount>1024 then begin S:=TStringStream.Create; try S.LoadFromStream(TIdHTTP(ASender).Response.ContentStream);//получили текст Memo1.Lines.LoadFromStream(S);//вывели в Memo finally TIdHTTP(ASender).Socket.Close;//в любом случае закрыли соединение S.Free; end; end; end;
Здесь я воспользовался методом Close сокета (Socket) и принудительно оборвал передачу данных. Также я мог воспользоваться методом CloseFraceFully того же сокета или методом Disconnect объекта idHTTP — результат был бы тот же, различие только в работе методов (можете на досуге покопаться в их коде).
Теперь можете запустить программу и проверить работу. При закрытии сокета будет срабатывать исключение EIdConnClosedGracefully о чем будет выводится соответствующее сообщение, а в Memo выводится текст размер которого будет чуть превышать размер в 1 Kb.
Как принудительно прервать выполнение запроса в ICS?
Для ICS работа остается прежней — необходимо только ещё одно обработать событие OnDocData, которое сигнализирует нам о том, что получена очередная порция данных. Вначале пишем обработчик OnClick кнопки:
procedure TForm2.Button2Click(Sender: TObject); begin HttpCli1.URL:='http://webdelphi.ru'; HttpCli1.RcvdStream:=RcvdStream; {начали асинхронное выполнение запроса} HttpCli1.GetASync; end;
Обработчик события OnRequestDone:
procedure TForm2.HttpCli1RequestDone(Sender: TObject; RqType: THttpRequest; ErrCode: Word); begin {выводим полученный текст в Memo} Memo2.Lines.LoadFromStream(RcvdStream); end;
Следующий обработчик — решение нашей задачи. В обработчике OnDocData:
procedure TForm2.HttpCli1DocData(Sender: TObject; Buffer: Pointer; Len: Integer); begin if Len>1024 then HttpCli1.CtrlSocket.Close; end;
Вот и весь код для ICS. Запустите программу и убедитесь, что код работает исправно. Вот вам, кстати и наглядным пример, когда не Indy, а ICS дает выигрыш как в размере исходного кода так и в логике приложения в целом: для Indy нам пришлось:
- писать обработчик OnWork и принудительно обрывать соединение
- обрабатывать исключения
- лезть в свойства компонента и «выуживать» оттуда поток с данными, чтобы не потерять их.
Книжная полка
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|
Здравствуйте! Можете помочь с компонентом TIdTCPServer? При остановке сервера если есть подключенные клиенты программа зависает. Как принудительно отключить все клиенты до остановки сервера?
arankar, я сервера не писал уже лет эдак пять и то тогда делал простеньки для локалки из 10 машин. Гляньте у сервера свойства Socket или что-то типа того и у этого свойства методы Close, CloseGracefully, Abort, Disconnect и т.д. И не забывайте отработать исключения как в клиентах так и на сервере…Indy любит исключения :)
кстати у меня Indy 10.5.8.0 и Delphi2009
ICS никому не советую, глючна до безобразия. Лучше пользоваться другими аналогами, тем более бесплатными. Indy — прошлый век, и использовать лучше когда нужно написать что-то свое и маленькое.
Не верю, что ICS глючная. Покажи реальный пример, когда она неправильно работает.
Попробуйте на ICS закачку файлов по http написать, и Вы поймете о чем я говорю. Файлы закачиваются не до конца, а этому конвееру пофиг, при этом даже event не срабатывает. Или пытается создать соединение на закачку и пропускает его по TTL, при этом TTL запроса редактировать насколько я помню нельзя (точнее можно, но эффекта 0), в любом случае при запросе на соединение приложение виснет. При этом не понятно почему запрос подвисает, на другом компоненте подобных глюков не наблюдается. А глюки с FTP, я вообще молчу. Upload по FTP становится просто карой небесной. В общем если нравится писать кучу заплаток для… Подробнее »
Речь шла о CIS — Clever Internet Suite. К ICS претензий не имею… Перехожу на RAD, голова кипит.
Вот и у меня что-то голова закипела после второго вашего коммента =)
Перестановка букв, а какая разница!)))
Спасибо! CloseGracefully закрывает соединение но не всегда, может у меня код не правильный
[code]
var
List: TList;
Context: TIdContext;
i: integer;
begin
list:= idtcpsrvrServer.Contexts.LockList;
try
for i := 0 to list.Count - 1 do
begin
context := list[i];
context.Connection.Socket.CloseGracefully;
end;
finally
idtcpsrvrServer.Contexts.UnlockList;
end;
[/code].
CloseGracefully чем-то напоминает работу Terminate в TThread, т.е. просто переключает флаг в значение True, а дальше клиент продолжает работу и где-то в дебрях Indy прерывает передачу данных. Я использовал в Indy CloseGracefully и Close, чтобы гарантировано в нужный момент оборвать соединение, ну и естественно весь код был в try…except…end
Спасибо. Полезная статья.
А как правильно с этой либой останаливать соединение?
http.abort или http.close ?
Добрый день не плучаеться установить ICS, при установке пакета (C:\Users\Lahmadgun\Desktop\My Lib\ICS\Delphi\Vc32\OverbyteIcsD2010Design.dproj) вылазит собщение: «Can’t load package Delphi\Vc32\OverbyteIcsD2010Design.bpl», несмотря на то что я прописал пути.
День добрый!
Почитал Ваши статьи и написал парсер позиций сайта. Все работает за исключением одного: Яндекс очень скоро выдает капчу. Юзера меняю, анонимные прокси применяю, задержку 3 секунды делаю. Ничего не помогает. Может Яндекс еще и MAC-адрес проверяет? Что можете посоветовать?
Вячеслав, я уже давно парсерами для ПС не занимался. Вполне возможно, что и Яндекс усилил свою защиту от парсилок..не пробовали делать разные задержки по времени в течение одной сессии? Например, первая задержка 3 сек., вторая — 5, третья — 4, четвертя — опять 3 и т.д.?