Koneksi tidak habis saat mengunduh file dari internet

Terkait dengan postingan saya ( Cara mengambil file dari Internet melalui HTTP? ) tentang cara mengunduh file dengan mudah dan kuat dari Internet, saya telah menemukan solusi yang mungkin - namun tidak berfungsi sebagaimana mestinya.

Menurut dokumentasi MS, kode di bawah ini seharusnya habis pada 500 ms setelah saya memutuskan sambungan dari internet. Namun, sepertinya pengaturan 'INTERNET_OPTION_RECEIVE_TIMEOUT' sepenuhnya diabaikan. Aplikasi macet saat diunduh. Dibutuhkan sekitar 20-30 untuk fungsi ini untuk menyadari bahwa koneksi Internet sedang down dan mengembalikan kontrol ke GUI.

Ada yang tahu kenapa?

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;

Berikut dokumentasinya:

{

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.
}

Sunting: Saya memutuskan sambungan Internet dengan mencabut kabel atau (untuk nirkabel) dari perangkat lunak SETELAH aplikasi mulai mengunduh (saya memilih untuk mengunduh file besar). Ini mensimulasikan situs web offline.


person Z80    schedule 28.06.2010    source sumber
comment
Tidakkah kamu senang kamu tidak mempersulit keadaan hanya dengan menggunakan Indy?   -  person Rob Kennedy    schedule 28.06.2010
comment
Hanya untuk mengklarifikasi. Saya tidak mengatakan Indy itu buruk! Itu terlalu banyak untuk apa yang saya butuhkan. Saya ingin dapat membawa aplikasi ini dan mengkompilasinya di komputer APAPUN yang mendukung Delphi. Portabilitas bukanlah hal yang buruk, bukan?   -  person Z80    schedule 28.06.2010
comment
Untuk menjawab pertanyaan Anda sendiri, klik tombol jawab pertanyaan Anda di bagian bawah halaman ini. Kemudian ketikkan jawaban Anda seperti yang Anda lakukan untuk pertanyaan lain di situs. Setelah masa tunggu, Anda diperbolehkan menandainya sebagai jawaban yang diterima.   -  person Rob Kennedy    schedule 29.06.2010
comment
Aku tidak suka Indy. Terlalu besar dan SELALU dengan masalah kompatibilitas ke belakang yang besar.   -  person Z80    schedule 10.06.2014


Jawaban (4)


Batas waktu koneksi jelas tidak berlaku dalam pengujian Anda karena pada saat Anda memulai pengujian (yaitu, mencabut steker), koneksi telah dibuat. Memang benar, koneksi sudah terjalin bahkan sebelum Anda mengatur opsi batas waktu.

Validitas batas waktu penerimaan juga patut dicurigai, karena Anda sudah mulai menerima responsnya juga.

Batas waktu yang tampak paling menjanjikan adalah batas waktu pemutusan sambungan, namun MSDN mengatakan bahwa hal tersebut belum diterapkan.

Menurut saya cara yang harus dilakukan adalah dengan menggunakan operasi asinkron. Gunakan InternetReadFileEx dan gunakan tanda irf_Async dan irf_No_Wait. Jika terlalu banyak waktu berlalu tanpa menerima data apa pun, tutup sambungan. Opsi lainnya adalah tetap menggunakan panggilan sinkron, tetapi kemudian memanggil InternetCloseHandle dari thread lain jika pengunduhan memakan waktu terlalu lama.

person Rob Kennedy    schedule 28.06.2010
comment
Saya memutuskan sambungan Internet dengan mencabut kabel atau (untuk nirkabel) dari perangkat lunak SETELAH aplikasi mulai mengunduh (saya memilih untuk mengunduh file besar). Ini mensimulasikan situs web offline. - person Z80; 28.06.2010
comment
Benar. Jika pengunduhan telah dimulai, maka koneksi telah dibuat dan respons telah dimulai. Oleh karena itu, batas waktu koneksi tidak lagi relevan, begitu pula batas waktu penerimaan. - person Rob Kennedy; 29.06.2010
comment
Jadi, pada dasarnya tidak ada batas waktu yang diterapkan di perpustakaan itu (setelah pengunduhan dimulai). - person Z80; 29.06.2010

Ada bug yang terdokumentasi dalam kode MS IE. Hanya dapat diselesaikan dengan menggunakan kode di thread dan menerapkan kembali mekanisme time out.

Detail:

"Artikel ini menunjukkan solusi untuk bug InternetSetOption API pada pengaturan nilai batas waktu dengan membuat thread kedua. InternetSetOption Tidak Menetapkan Nilai Batas Waktu"

http://support.microsoft.com/default.aspx?scid=kb;en-us;Q224318
(Tautan dilaporkan rusak. Salahkan MS, bukan saya)

Mungkin ada yang bisa membantu mengimplementasikan perbaikan bug ini juga di Delphi. Saya pribadi tidak punya pengalaman dengan C. Bahkan tulang punggung di pseudo-Pascal akan bagus.

person Z80    schedule 28.06.2010
comment
Punya tautan ke dokumentasi itu? - person Rob Kennedy; 29.06.2010
comment
Hai Rob, lihat tautan yang baru saja saya tambahkan. - person Z80; 29.06.2010
comment
Terlihat mirip dengan saran terakhir dalam jawaban saya. Daripada menunggu batas waktu dan memanggil InternetCloseHandle di thread terpisah, artikel KB mengatur koneksi di thread lain. Kode contoh hampir seluruhnya merupakan panggilan API. Bagian mana yang membuatmu kesulitan? - person Rob Kennedy; 29.06.2010
comment
Maaf. Itu berhasil sampai sekarang. Namun Anda mungkin tahu bahwa Microsoft cenderung sering memindahkan sesuatu (halaman). Sudahkah Anda mencoba melakukan pencarian Google untuk teks yang saya beri tanda kutip? - person Z80; 29.01.2014
comment
PS: mungkin mereka memperbaiki bug di IE 11 baru sehingga artikelnya dihapus? - person Z80; 29.01.2014

IMO, Anda harus menjalankan ini di thread. Threading tidak harus berarti perulangan - ini bisa berupa thread "satu dan selesai". Jalankan seperti itu, dan GUI Anda tetap responsif hingga thread selesai. Saya menyadari bahwa ini tidak benar-benar menjawab pertanyaan Anda, tetapi akan membuat kode Anda lebih baik.

Selain itu, jika Anda memutuskan sambungan internet selama putaran pertama saat Anda memeriksa data, saya rasa ini akan mencoba lagi 10 kali. Anda harus mendeteksinya, lalu segera berhenti.

Terakhir, menurut saya Anda tidak harus menggunakan EXIT ketika Anda memiliki pegangan dan barang terbuka. Sebaliknya, istirahatlah, sehingga Anda masih dapat melewati pemutusan hubungan. Saya berharap kode Anda mengikat soketnya. Saya melihat ini baru-baru ini selama peninjauan kode ketika ada EXIT alih-alih BREAK, dan ini menyebabkan kebocoran memori karena objek dibuat dan tidak pernah dibebaskan. Saya akan menggunakan aturan yang sama di sini.

person Chris Thornton    schedule 28.06.2010
comment
-1. Tidak menjawab pertanyaan itu. Selain itu, bagaimana Anda tahu kode ini belum sudah berjalan di thread terpisah? - person Rob Kennedy; 28.06.2010
comment
@Chris, menurut saya exit mungkin baik-baik saja di sini karena OP menggunakan try...finally untuk menutup koneksi. - person Marcus Adams; 28.06.2010
comment
@Rob - dia bilang aplikasinya macet, jadi saya berasumsi dia tidak menjalankan ini di thread. - person Chris Thornton; 28.06.2010
comment
Tidak, saya tidak menjalankannya di thread. Saya ingin melakukan itu, tetapi saya tidak yakin seberapa aman kode itu. - person Z80; 28.06.2010
comment
@Andreas - apakah Exit benar-benar berfungsi seperti itu? Saya berasumsi itu baru saja keluar dari fungsi/prosedur. Jika akhirnya berhasil, maka saya setuju dengan itu. Saya kira saya sudah lama salah melihat Exit itu! - person Chris Thornton; 29.06.2010
comment
@Chris Thornton: Ya. Cobalah sendiri! try Exit; finally ShowMessage('test'); end; - person Andreas Rejbrand; 29.06.2010
comment
@Altar, Anda dapat menggunakan thread tunggal meskipun thread tersebut tidak aman. - person Marcus Adams; 29.06.2010
comment
+1 dari saya untuk memperbaiki suara negatif. Jawabannya bagus. Tidak ada alasan untuk diremehkan. - person Z80; 29.01.2014

Apakah Anda yakin tidak mencapai INTERNET_OPTION_CONNECT_TIMEOUT? Ia akan mencoba menyambung terlebih dahulu, lalu menerima.

Untuk menguji batas waktu koneksi, koneksi harus diselesaikan, tetapi tidak pernah terhubung. Untuk menguji batas waktu baca, ia harus terhubung, tetapi tidak pernah menerima data apa pun.

Saya biasanya menyetel batas waktu koneksi menjadi 10 detik, dan batas waktu baca menjadi 30 detik. Apa pun yang lebih lama dari itu, saya anggap saja turun.

person Marcus Adams    schedule 28.06.2010
comment
Apakah Anda yakin tidak mencapai INTERNET_OPTION_CONNECT_TIMEOUT? Ia akan mencoba menyambung terlebih dahulu, lalu menerima. - Tentu saja!! Saya telah melakukan beberapa tes untuk itu. - person Z80; 28.06.2010