Если Вы используете в работе с Сетью библиотеку Synapse, то, вероятно задумывались о том как сделать так, чтобы не просто получать или отправлять данные, но и видеть весь ход процесса. Например, чтобы при скачивании данных заполнялся ProgressBar, или чтобы в процессе работы THTTPSend видеть весь лог его работы, начиная от создания и, заканчивая закрытием сокета.
Сделать это достаточно просто, подключив в uses всего один модуль — blcksock.
У объекта THTTPSend имеются следующие свойства:
property DownloadSize: integer read FDownloadSize; property UploadSize: integer read FUploadSize;
Которые, судя по описанию, в каждый момент времени содержат размер полученных и отправленных данных в байтах. И для того, чтобы получать эти значения достаточно определить событие OnStatus у сокета. На самом деле эти свойства содержат либо 0 и это значит, что данные ещё не принимаются и не отправляются, либо уже принятый или отправленный размер данных.
Убедиться в этом можно следующим образом. Создадим новое приложение и подключим в Uses модули httpsend и blcksock. На форму положим Memo в который будем выводить значения DownloadSize.
Создадим глобальную переменную HTTP: THTTPSend и напишем обработчик события OnStatus у сокета:
procedure TForm4.Status(Sender: TObject; Reason: THookSocketReason; const Value: String); begin Memo1.Lines.Add(IntToStr(HTTP.DownloadSize)); end;
А на OnCreate главной формы:
HTTP:=THTTPSend.Create; HTTP.Sock.OnStatus:=Status; HTTP.HTTPMethod('GET','http://webdelphi.ru');
Теперь запустим программу и посмотрим, что выпишется в Memo. У меня получились следующие строки:
0 0 [....] 0 0 65566 65566 [....] 65566 65566 65566 65566 65566
То есть — прогресса толком и нет. Либо 0, либо сразу всё. Поэтому использование этих свойств оставим на потом. А лучше воспользуемся теми данными, которые возвращает нам сокет, т.е.
Reason: THookSocketReason; const Value: String
Reason — это тип события
Value — значение, возвращаемое при определенном событии.
Всего на OnStatus можно получить данные по четырнадцати различным событиям, но для нас сейчас особую роль будут играть два из них: HR_ReadCount и HR_WriteCount. При первом событии в Value вернется строка, содержащая количество прочитанных байтов, при втором — отправленных.
Соответственно, можно завести какую-нибудь переменную типа integer и каждый раз при событии HR_ReadCount или HR_WriteCount прибавлять к этой переменной значение StrToInt(Value) и заполнять ProgressBar.
Осталось только определить максимальное значение у ProgressBar’a (кстати, можете его уже уложить на форму). Сделать это достаточно просто, отправив запрос «HEAD» на нужный адрес и проанализировав заголовок «Content-Length». Можно воспользоваться, к примеру, такой функцией:
function TForm4.GetLength(const URL: string): integer; var i:integer; size:string; ch:char; begin with THTTPSend.Create do if HTTPMethod('HEAD',URL) then begin for I := 0 to Headers.Count - 1 do begin if pos('content-length',lowercase(Headers[i]))>0 then begin size:=''; for ch in Headers[i]do if ch in ['0'..'9'] then size:=size+ch; Result:=StrToInt(size)+Length(Headers.Text); break; end; end; end else Result:=-1; end;
Следует обратить внимание, что заголовок содержит только размер содержимого тела запроса, а скачиваем мы все вместе с заголовками, поэтому при получении общего размера содержимого суммируется значение из заголовка и размер всех заголовков:
Result:=StrToInt(size)+Length(Headers.Text);
Ну, а теперь нам осталось только немного подправить процедуру Status. Создайте ещё одну переменную, например:
Download: integer;
В ней будем хранить общее количество полученных данных. Будем в Memo выписывать весь лог и заполнять ProgressBar, поэтому подключите дополнительно в uses TypInfo и дописываем Status:
if Reason=HR_ReadCount then begin Download:=Download+StrToInt(Value); if ProgressBar1.Max>0 then begin ProgressBar1.Position:=Download; Memo1.Lines.Add('Получено '+Value+' байт'); Memo1.Lines.Add('Скачано '+IntToStr(Download)+' из '+IntToStr(ProgressBar1.Max)); end; end else if not (Reason=HR_CanRead) then Memo1.Lines.Add(GetEnumName(TypeInfo(THookSocketReason),ord(Reason))+' '+Value); end;
Теперь вешаем на форму кнопку и в обработчике onClick пишем:
Memo1.Clear; Download:=0; ProgressBar1.Max:=GetLength(Edit1.Text); ProgressBar1.Position:=0; HTTP.HTTPMethod('GET',Edit1.Text); Memo1.Lines.Add('Длина заголовков '+IntToStr(Length(HTTP.Headers.Text))); HTTP.Clear;
Здесь следует заметить, что после того как данные получены и переданы дальше на обработку обязательно необходимо выполнять Clear у THTTPSend, иначе при следующем GET-запросе объект попробует не только получить данные, но и отправить все, что было получено при предыдущем запросе, включая куки и заголовки, что приведет к ошибке.
Вот теперь можете запускать программу и смотреть как заполняется ProgressBar при скачивании файла, а в Memo выводится лог работы объекта THTTPSend.
Также, для получения каких либо данных от сокета вы можете определить событие OnHeartBeat («Сердцебиение»), которое будет срабатывать в заданные промежутки времени, но, как говорят сами разработчики Synaps’а — слишком частое использование этого события может привести к «подвисанию» приложения, поэтому лучше сильно не рисковать.
[…] ?? : http://www.webdelphi.ru/2010/05/synapse-skachivaem-dannye-s-progressbarom/ […]
Это понятно, но в том-то и дело, что даже по окончании запроса DownloadSize выдает ноль (это только в случае txt/html.
> Что касается Content-Length, то сервер может, но не обязан выдавать этот заголовок
Но Ваш пример и построен на считывании Content-Length (определение максимального значения ProgressBar-а). Получается не всегда он будет корректно работать?
Код будет работает корректно. В коде никакиех ошибок нет. Просто я ещё раз обращаю Ваше внимание на то, что сервер не обязан возвращать заголовок Content-Length. Если хочется всегда отслеживать процесс загрузки — достаточно просто выводить количество загруженных байт, используя события сокета OnStatus или OnHeartbeat.
Статья полезная, но есть одно НО!
Что за чудесная конструкция?
[code]
for ch in Headers[i]do
if ch in ['0'..'9'] then size:=size+ch;
Result:=StrToInt(size)+Length(Headers.Text);
break;
end;
[/code]
У меня, например на этом месте
[code]for ch in Headers[i]do [/code]
выдаёт ошибку.
А версия Delphi у вас какая?
Delphi 7.
Кстати, чего-то не могу переделать код на аплоад, т.е чтобы показывало, сколько отдал на сервер данных, очень нужно при передачи файлов. Может есть какие наработки или идеи?
В Delphi 7 наскоько я помню конструкции for..in..do ещё не было. Этот кусок вам надо переделать на, например, for..to..do.
А с аплоадом какие проблемы именно? Не знаете какой статус использовать или что?
В процедуре Status (у меня она по-другому называется) я проверяю вместо Reason=HR_ReadCount — Reason=HR_WriteCount (что скорее всего нужно для того, чтобы посчитать отданное в сеть) и там суммирую.
Вопрос в том, что у меня файл допустим 86520 байт, а через сеть по процедуре Status считается только 21351 (оно всегда одно и тоже отдаёт).
Может быть это только заголовок или что-то по смыслу статическое, но не сам файл.
ЗЫ в комментариях теги [code][/code] не работают).
кстати, для тех использует делфи 6-7 часть кода с
for ch in Headers[i]do
if ch in [‘0’..’9′] then size:=size+ch;
запросто переделывается на
for j:=0 to length(Headers.Strings[i]) do begin
ch := Headers.Strings[i][j];
if ch in [‘0’..’9′] then size:=size+ch;
end;
Порешал проблему с аплоудом. Всё отлично работает. Осталось только только с МИМЕ типом исходящим разобратсья (чтобы оплучить размер данных, которые надо отослать).
А проблема кроилась собственно говоря в типах данных — я использовал всего лишь дворд (0…65535), а пакеты посылались в 63536 байт, поэтому инкрементилось всё в ноль). А сейчас всё отлично!
я обычно стараюсь брать тип данных, который максимально подойдет. Например, если надо получить размер файла в Сети, то Integer vожет и не хватить, а int64 вроде бы сбоев не давал. С Mime-типами вроде бы проблем быть не должно — заголовки сообщения оформляются похожим образом также в StringList…по-момему класс TMimeMess в модуле SMTPSend.pas, хотя могу ошибиться с классом — пишу на память.
Про Майм — я немного ошибся. Мне необходимо получить инфу (типо хедера), которую клиент отправляет на сервер перед передачей данных на сервер. Я смог такое реализовать через событие OnMonitor у сокета (вроде). Оттуда по типам событий достал контент-длину (она мне нужна, для реализации аплоуда). И вот так получаю нужные размеры. А потом через то же события OnStatus у сокета смотрю данные. При необходимости могу написать статью или просто поделится решением (в соответсвующем к пониманию виде).
>>При необходимости могу написать статью или просто поделится решением (в соответсвующем к пониманию виде).
Было б не плохо увидеть Ваше решение. Если есть желание — напишите статью — опубликуем. Информации по Synapse в Рунете не так уж и много.
При загрузке страниц не в курсе, а вот при загрузке файла можно легко сделать прогресбар. Вот таким методом.
procedure TFmain.Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
begin
PB1.Position := HTTP1.Document.Size;
PB1.Max := HTTP1.DownloadSize;
end;
Алексей, всё верно при аплоаде проблем с прогресбаром нету, а вот при загрузке странички как раз то что я описал в посте — то 0, а то сразу всё файло :) Ну да события нам в помощь :)
0
HR_WriteCount 120
0
0
Почему-то скачивается не вся страница.
Получено 8192 байт
Скачано 8192 из 4654264
0
Получено 4332 байт
Скачано 12524 из 4654264
0
0
HR_Error 10054,Connection reset by peer
0
HR_SocketClose
Длина заголовков 455
Если скачиваю exe-файл или картинку, то все нормально, а с html почему-то вот такая фигня.^
Все дело в том, что при скачивании text/html заголовка content-length нет.
Также HTTP.DownloadSize при скачивании html-страницы всегда выдает 0. (хотя вся страница на самом деле скачивается).
При скачивании jpg или exe-файла HTTP.DownloadSize показывает сначала 0, а потом значения, равные content-length.
Значит ли это, что в DownloadSize значение записывается прямо из заголовка content-length? (раз при html нет content-length, и DownloadSize тоже нуль).
Владимир, DownloadSize отражает только количество фактически полученной информации от сервера и заполняется после окончания выполнения запроса. Что касается Content-Length, то сервер может, но не обязан выдавать этот заголовок, особенно, если дело касается простых txt/html файлов. Для мониторинга закачки надо использовать события соккета, но никак не свойства THTTPSend DownloadSize или UploadSize
Уважаемый, а почему у вас все циклы сделаны в манере for in?
Не все же пользуются последними Delphi, сделайте так чтобы более ранние версии воспринимали исходный код без проблем.
Василий, ну кто же вам мешает переписать пример самостоятельно? А for..in и прочие нововведения я действительно стараюсь использовать, по крайней мере, в блоге очень активно, чтобы читатели видели что появилось в новых версиях Delphi и как этим можно пользоваться в повседневной работе.
Скажите пожалуйста, как синапсом можно получить файл без сохранения куда либо (в память или на диск)? Есть большой файл на сервере, нужно просто гнать траффик с него без дальнейшей обработки.
Больше 2 Гб не сохраните, не словив при этом «Out of memory». Synapse сохраняет файл в TmemoryStream — ровно то, что вам и требуется, НО — см. мое первое предложение — больше 2 Гб так не сохраните. Я, в свое время, переписывал Synapse под работу с TFileStream
Автор, исправьте у себя ошибку. По мануалу и на практике DownloadSize возврашает общий размер данных, а не размер полученного пакета и инициализируется сразу как эта информация появляется в заголовке. Так что все прекрасно работает и никаких дополнительных запросов для определения размера данных делать не надо….