Время ожидания соединения не прерывается при загрузке файла из Интернета

Связано с моим сообщением (Как получить файл из Интернета через HTTP?) о том, как легко и надежно загрузить файл из Интернета, я нашел возможное решение, но оно не работает так, как предполагалось.

Согласно документации MS, приведенный ниже код должен отключиться через 500 мс после того, как я отключился от Интернета. Однако похоже, что он полностью игнорирует настройку INTERNET_OPTION_RECEIVE_TIMEOUT. Приложение зависает во время загрузки. Для этой функции требуется около 20-30, чтобы понять, что соединение с Интернетом отсутствует, и вернуть управление графическому интерфейсу.

Кто-нибудь знает почему?

function GetBinFileHTTP (const aUrl: string; const pStream: TStream; wTimeOut: Word= 500; wSleep: Word= 500; wAttempts: Word= 10): Integer;
CONST
  BufferSize = 1024;
VAR
  hSession, hService: HINTERNET;
  Buffer     : array[0..BufferSize-1] of Char;
  dwBytesRead, dwBytesAvail: DWORD;
  lSucc        : LongBool;
  lRetries, dwTimeOut: Integer;   
begin
 Result:= 0;
 if NOT IsConnectedToInternet then
  begin
   Result:= -1;
   EXIT;
  end;

 hSession := InternetOpen(PChar(ExtractFileName(Application.ExeName)), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);  { The INTERNET_OPEN_TYPE_PRECONFIG flag specifies that if the user has configured Internet Explorer to use a proxy server, WinInet will use it as well. }
 if NOT Assigned(hSession) then
  begin
   Result:= -4;
   EXIT;
  end;

 TRY
   hService := InternetOpenUrl(hSession, PChar(aUrl), nil, 0, INTERNET_FLAG_RELOAD, 0);
   if NOT Assigned(hService) then Exit;
   TRY
     FillChar(Buffer, SizeOf(Buffer), 0);

     { Set time out }
     dwTimeOut:= wTimeOut;
     InternetSetOption(hService, INTERNET_OPTION_RECEIVE_TIMEOUT, @dwTimeOut, SizeOf(dwTimeOut));   { use INTERNET_FLAG_RELOAD instead of NIL to redownload the file instead of using the cache }


     InternetSetOption(hService, INTERNET_OPTION_CONNECT_TIMEOUT, @dwTimeOut, SizeOf(dwTimeOut));

     REPEAT
       lRetries := 0;

       REPEAT
         lSucc:= InternetQueryDataAvailable( hService, dwBytesAvail, 0, 0);
         if NOT lSucc
         then Sleep( wSleep );
         if lRetries > wAttempts
         then Result:= -2;
       UNTIL lSucc OR (Result= -2);

       if NOT InternetReadFile(hService, @Buffer, BufferSize, dwBytesRead) then
        begin
          Result:= -3;                                                          { Error: File not found/File cannot be downloaded }
          EXIT;
        end;

       if dwBytesRead = 0
       then Break;

       pStream.WriteBuffer(Buffer[0], dwBytesRead);

     UNTIL False;
   FINALLY
     InternetCloseHandle(hService);
   end;
 FINALLY
   InternetCloseHandle(hSession);
 end;

 Result:= 1;
end;

Вот документация:

{

INTERNET_OPTION_CONNECT_TIMEOUT         Sets or retrieves an unsigned long integer value that contains the time-out value to use for Internet connection requests. If a connection request takes longer than this time-out value, the request is canceled. When attempting to connect to multiple IP addresses for a single host (a multihome host), the timeout limit is cumulative for all of the IP addresses. This option can be used on any HINTERNET handle, including a NULL handle. It is used by InternetQueryOption  and InternetSetOption.
INTERNET_OPTION_RECEIVE_TIMEOUT         Sets or retrieves an unsigned long integer value that contains the time-out value to receive a response to a request.      If the response takes longer than this time-out value, the request is canceled. This option can be used on any HINTERNET handle, including a NULL handle. It is used by InternetQueryOption and InternetSetOption. For using WinInet synchronously, only the default value for this flag can be changed by calling InternetSetOption and passing NULL in the hInternet parameter.
                  INTERNET_OPTION_CONTROL_RECEIVE_TIMEOUT - Identical to INTERNET_OPTION_RECEIVE_TIMEOUT. This is used by InternetQueryOption and InternetSetOption.
}

Изменить: я отключаю Интернет, отсоединяя кабель или (для беспроводной сети) от программного обеспечения ПОСЛЕ того, как приложение начинает загрузку (я выбрал загрузку большого файла). Он имитирует отключение веб-сайта.


person Z80    schedule 28.06.2010    source источник
comment
Разве вы не рады, что не усложняли ситуацию, просто используя Инди?   -  person Rob Kennedy    schedule 28.06.2010
comment
Просто для ясности. Я не говорю, что Инди плохой! Это слишком много для того, что мне нужно. Я хочу иметь возможность взять приложение с собой и скомпилировать его на ЛЮБОМ компьютере с поддержкой Delphi. Портативность - это не плохо, правда?   -  person Z80    schedule 28.06.2010
comment
Чтобы ответить на свой вопрос, нажмите кнопку «Ответить на свой вопрос» внизу этой страницы. Затем введите свой ответ, как и на любой другой вопрос на сайте. После периода ожидания вы можете пометить его как принятый ответ.   -  person Rob Kennedy    schedule 29.06.2010
comment
Я не люблю Инди. Путь к большому и ВСЕГДА с большими проблемами обратной совместимости.   -  person Z80    schedule 10.06.2014


Ответы (4)


Очевидно, что тайм-аут подключения неприменим в вашем тесте, потому что к тому времени, когда вы начнете тест (т. Е. Вытащите вилку), соединение уже будет установлено. Действительно, соединение уже установлено, прежде чем вы даже дойдете до установки параметра тайм-аута.

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

Наиболее многообещающим является тайм-аут отключения, но MSDN утверждает, что это еще не реализовано.

Мне кажется, что лучше всего использовать асинхронные операции. Используйте InternetReadFileEx и используйте флаги irf_Async и irf_No_Wait. Если прошло слишком много времени без получения данных, закройте соединение. Другой вариант - придерживаться синхронных вызовов, но затем вызывать InternetCloseHandle из другого потока, если загрузка занимает слишком много времени.

person Rob Kennedy    schedule 28.06.2010
comment
Я отключаю Интернет, отсоединяя кабель или (для беспроводной сети) от программного обеспечения ПОСЛЕ того, как приложение начинает загрузку (я выбрал загрузку большого файла). Он имитирует отключение веб-сайта. - person Z80; 28.06.2010
comment
Правильно. Если загрузка началась, значит, соединение уже установлено и ответ уже начался. Следовательно, тайм-аут соединения больше не актуален, как и тайм-аут получения. - person Rob Kennedy; 29.06.2010
comment
Таким образом, в этой библиотеке практически нет тайм-аута (после начала загрузки). - person Z80; 29.06.2010

В коде MS IE есть задокументированная ошибка. Можно решить только путем использования кода в потоке и повторной реализации механизма тайм-аута.

Подробности:

«В этой статье показано обходное решение ошибки InternetSetOption API при установке значений тайм-аута путем создания второго потока. InternetSetOption не устанавливает значения тайм-аута»

http://support.microsoft.com/default.aspx?scid=kb;en-us;Q224318
(Ссылка была нарушена. Виновата MS, а не я)

Может быть, кто-нибудь может помочь с внедрением этого исправления ошибки также в Delphi. У меня лично нет опыта работы с C. Даже основа в псевдопаскале будет хороша.

person Z80    schedule 28.06.2010
comment
Есть ссылка на эту документацию? - person Rob Kennedy; 29.06.2010
comment
Привет, Роб, см. Ссылку, которую я только что добавил. - person Z80; 29.06.2010
comment
Похоже на последнее предложение в моем ответе. Однако вместо ожидания тайм-аута и вызова InternetCloseHandle в отдельном потоке статья базы знаний устанавливает соединение в другом потоке. Пример кода почти полностью состоит из вызовов API. Какая часть доставляет вам проблемы? - person Rob Kennedy; 29.06.2010
comment
Прости. Это работало до сих пор. Но вы, наверное, знаете, что Microsoft часто перемещает вещи (страницы). Вы пробовали выполнить поиск в Google по тексту, который я заключил в кавычки? - person Z80; 29.01.2014
comment
PS: может быть, они исправили ошибку в новом IE 11 и удалили статью? - person Z80; 29.01.2014

ИМО, вы должны запустить это в ветке. Потоки не обязательно должны означать зацикливание - это может быть поток «все готово». Запустите его таким образом, и ваш графический интерфейс останется отзывчивым, пока поток не завершится. Я понимаю, что это на самом деле не отвечает на ваш вопрос, но улучшит ваш код.

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

Наконец, я не думаю, что вам следует использовать EXIT, когда у вас есть открытые дескрипторы и прочее. Вместо этого прервитесь, чтобы вы все еще проходили через разъединения. Я ожидал, что ваш код заблокирует сокет. Я видел это недавно во время проверки кода, когда имел место EXIT и BREAK, и это вызывает утечку памяти, потому что объекты создаются и никогда не освобождаются. Я бы использовал здесь то же правило.

person Chris Thornton    schedule 28.06.2010
comment
-1. Не отвечает на вопрос. Кроме того, как узнать, что этот код уже не запущен в отдельном потоке? - person Rob Kennedy; 28.06.2010
comment
@Chris, я думаю, что выход здесь может быть прекрасным, поскольку OP использует try ... наконец, чтобы закрыть соединения. - person Marcus Adams; 28.06.2010
comment
@Rob - он сказал, что его приложение зависает, поэтому я предполагаю, что он не запускает это в потоке. - person Chris Thornton; 28.06.2010
comment
Нет, я не запускаю это в потоке. Я хочу это сделать, но не уверен, насколько потокобезопасен этот код. - person Z80; 28.06.2010
comment
@Andreas - Exit действительно так работает? Я предположил, что он просто вышел из функции / процедуры. Если он, наконец, попадает в точку, то я с этим согласен. Думаю, я тогда долго смотрел тот Exit некорректно! - person Chris Thornton; 29.06.2010
comment
@ Крис Торнтон: Да. Попробуй сам! try Exit; finally ShowMessage('test'); end; - person Andreas Rejbrand; 29.06.2010
comment
@Altar, вы можете использовать одноэлементный поток, даже если он не потокобезопасен. - person Marcus Adams; 29.06.2010
comment
+1 от меня, чтобы зафиксировать отрицательный голос. Ответ хороший. Нет причин для отрицательного голосования. - person Z80; 29.01.2014

Вы уверены, что не попали в INTERNET_OPTION_CONNECT_TIMEOUT? Сначала он попытается подключиться, а затем получит.

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

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

person Marcus Adams    schedule 28.06.2010
comment
Вы уверены, что не попали в INTERNET_OPTION_CONNECT_TIMEOUT? Сначала он попытается подключиться, а затем получит. - Абсолютно уверен !! Я сделал несколько тестов для этого. - person Z80; 28.06.2010