プロが教える店舗&オフィスのセキュリティ対策術

InternetReadFileでウェブ上のファイルをローカルPCにコピーするプログラムが、ファイルサイズが大きくなると最後までコピーできません。具体的には1Mバイト以上のファイルの読み取りにおいて、150K~400Kバイトまでしか読み取ることができません。読み取られたバイト数(以下のプログラムでdwByteRead)が0になり、それ以降のデータを読み取れません。読み取られた総バイト数は読み取りバッファサイズ(以下のプログラムでREAD_BUF_SIZE)の整数倍ですが、その値は毎回異なります。(殆どの場合、150K~400Kバイト)dwByteReadが0なった後、10回再試行を行うルーチンを追加してみましたが、一旦0になるとそれ以降は復活することはなく効果無しでした。このプログラムは数10Kバイトまでの読み取りでは安定して動作しているので、プログラム自体の安定性は問題ないと思います。

InternetReadFileで1Mバイト以上のファイルを読み取ることに成功して方がいらっしゃいました、その方法をご教示ください。他の方法でも1Mバイト以上のファイルをhttpから取り込むことができる方法で結構です。

HINTERNET hSession = InternetOpen( "MyApp", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0 );
if( hSession ){
HINTERNET hService = InternetOpenUrl( hSession, url, NULL, 0, 0, 0 );
if( hService ){
...
if( (fp=fopen(fName.c_str(), "wb")) != NULL ){
fwrite( lpBuffer, dwBytesRead, 1, fp );
while(true){
Byte lpBuffer[READ_BUF_SIZE+1];
DWORD dwBytesRead = READ_BUF_SIZE;
InternetReadFile( hService, lpBuffer, READ_BUF_SIZE, &dwBytesRead );
if( dwBytesRead == 0 ) break;
fwrite( lpBuffer, dwBytesRead, 1, fp );
}
...

A 回答 (4件)

★今回は簡単にアドバイス


・『InternetReadFile』関数の戻り値を調べてみましょう。
・『FALSE』が返ったらエラーです。
・『GetLastError』関数で調べてみて下さい。
・また、『Sleep』関数でウェイトを入れたらどうなる?
・あと、前回のサンプルで大きいサイトは正常にすべてを読み込めなかったかな?

最後に:
・InternetQueryDataAvailable( hUrl, &dwSize, 0, 0 ); で HTML ソースの
 ファイルサイズから全体のどの部分で『例外発生』や読み込み停止になるのかを
 解析してみましょう。→『Sleep』関数も使って。
・以上。おわり。
    • good
    • 0
この回答へのお礼

いろいろとご指導いただき、有難うございました。ループに最後に5msのSleepを入れることで解決しました。(もっと短くてもよさそうなので、最終値は調整中)

>『InternetReadFile』関数の戻り値を調べてみましょう。
ファイルを読みきらずにdwByteがゼロになるときはFalseになっていました。

>『GetLastError』関数で調べてみて下さい。
12031 (ERROR_INTERNET_CONNECTION_RESET)でした。

お礼日時:2006/12/30 14:43

★新しいサンプルをご紹介します。


・大きいファイルもダウンロードできるタイプです。
・今回は、引数に『dwDebug』のデバッグ変数を付けてみました。
・通常は、『dwDebug=1』を指定します。『dwDebug=10』を指定すると
 読み取りサイズが0バイトのとき、9回だけリトライさせることが出来ます。
・通信速度によって、途中で中断された場合も考慮してみましょう。
・また、サイトによっては上記のサンプルでは、正しく読み取れないこと
 があります。この場合は、別の方法になりますが、あまりネットワークには
 詳しくないため別の方法は今現在知りません。
・以上。おわり。まずは試してみて下さい。

★サンプル・ソース

/* 大きいHTMLソースをダウンロード */
extern DWORD MyDownloadFile( FILE fp, LPCTSTR lpURLName, DWORD dwDebug )
{
 HINTERNET hInet, hUrl;
 DWORD dwReadSize = 0; ←読み込んだバイト数⇒戻り値用
 
 if ( (hInet = InternetOpen(TEXT("MyApp"),INTERNET_OPEN_TYPE_DIRECT,NULL,NULL,0)) != NULL ){
  if ( (hUrl = InternetOpenUrl(hInet,lpURLName,NULL,0,0,0)) != NULL ){
   TCHAR szBuff[ READ_BUF_SIZE ]; ←ここで宣言した方が良い。while文中で確保・解放が繰り返されるため。遅くなるよ。
   DWORD dwSize;
   
   do {
    InternetReadFile( hUrl, szBuff, READ_BUF_SIZE, &dwSize );
    fwrite( szBuff, dwSize, 1, fp );
    dwReadSize += dwSize;
   } while ( (dwSize != 0) || (--dwDebug != 0) );
   
   InternetCloseHandle( hUrl );
  }
  InternetCloseHandle( hInet );
 }
 return( dwReadSize ); ←読み込んだバイト数
}

この回答への補足

結果は「デバッガでステップ実行すれば最後まで読み取れるが、実際に走らせると駄目」でした。実際に走らせた場合は、数100Kで停止するか(以前とほぼ同じ感じ)「モジュール'WININET.DLL'のアドレス 761A6531 でアドレス 00000000 に対する読み込み違反がおきました。」という例外発生で停止するかのいずれかです。ステップ実行とは具体的には、doループ内をすべてステップ実行するか、dwReadSize += dwSize;とfclose( fp );にブレークポイントを置いてfclose( fp );に至るまでGOをクリックし続けるかです。いずれでも最後まで行きました。

ということで、サンプルプログラムは問題なく動作するが、何かしら環境に問題あるようです。Borland C++Builder 6.0を使っているので、それとWININET.DLLの相性に問題があるのかもしれません。デバッガモードをオフにして作成した実行型を単独で走らせても同じ問題が発生するので、Borlandのデバッガ自体の問題ではないと思います。

以下が実際にテストに使われたプログラムです。(ファイルのOpen/Closeを追加し、Borland用に若干改造しました)READ_BUF_SIZEは1024/2048/4096/5000を試してみましたが、結果に違いは見られないようです。

int __fastcall TForm1::getFileFromWeb(AnsiString fName, AnsiString ansiURL)
{
 char lpURLName[256];
 strcpy(lpURLName, ansiURL.c_str());

 HINTERNET hInet, hUrl;
 DWORD dwReadSize = 0;

 if ( (hInet = InternetOpen(TEXT("MyApp"),INTERNET_OPEN_TYPE_DIRECT,NULL,NULL,0)) != NULL ){
  if ( (hUrl = InternetOpenUrl(hInet,lpURLName,NULL,0,0,0)) != NULL ){
   FILE *fp;
   if ( (fp = fopen(fName.c_str(),TEXT("wb"))) != NULL ){
    TCHAR szBuff[ READ_BUF_SIZE ];
    DWORD dwSize;
    int dwDebug = 10;

    do {
     InternetReadFile( hUrl, szBuff, READ_BUF_SIZE, &dwSize );
     fwrite( szBuff, dwSize, 1, fp );
     dwReadSize += dwSize;
    } while ( (dwSize != 0) || (--dwDebug != 0) );
    fclose( fp );
   }
   InternetCloseHandle( hUrl );
  }
  InternetCloseHandle( hInet );
 }
 return( dwReadSize );
}

まずは、ご報告まで。

補足日時:2006/12/30 11:10
    • good
    • 0

★サンプル・ソース



/* 一度にHTMLソースをダウンロード */
extern BOOL MyDownloadFile( LPCTSTR lpURLName )
{
 HINTERNET hInet, hUrl;
 BOOL bSuccess = FALSE;
 DWORD dwSize;
 LPTSTR lpBuff;
 FILE fp;
 
 if ( (hInet = InternetOpen(TEXT("MyApp"),INTERNET_OPEN_TYPE_DIRECT,NULL,NULL,0)) != NULL ){
  if ( (hUrl = InternetOpenUrl(hInet,lpURLName,NULL,0,0,0)) != NULL ){
   InternetQueryDataAvailable( hUrl, &dwSize, 0, 0 );
   
   if ( dwSize != 0 ){
    if ( (lpBuff = (LPTSTR)GlobalAlloc(GMEM_FIXED,dwSize)) != NULL ){
     InternetReadFile( hUrl, lpBuff, dwSize, &dwSize );
     
     if ( (fp = fopen(lpURLName,TEXT("wb"))) != NULL ){
      fwrite( lpBuff, dwSize, 1, fp );
      bSuccess = TRUE;
      fclose( fp );
     }
     GlobalFree( lpBuff );
    }
   }
   InternetCloseHandle( hUrl );
  }
  InternetCloseHandle( hInet );
 }
 return( bSuccess );
}
    • good
    • 0

★助言


・『fopen』の次の行の『fwrite』関数にある引数、『lpBuffer』、『dwBytesRead』の2つは
 どこかで、宣言されていますか?また、『dwBytesRead』の値はセット済みですか?
・少し省略し過ぎです。→省略する場合は、『while』文の中だけか、または『fopen』文の中
 だけを記述しましょう。
・また、『while』文の中で、『lpBuffer』、『dwBytesRead』の2つを宣言していますが、
 同じ名前の変数はブロック・スコープを用いて宣言しないようにしましょう。→混乱するため。
・最後に一つ、無限ループの場合は『while(true)』よりも『for(;;)』で記述するとコンパイル時
 ワーニング(警告)が出ません。→デバッグ時に助かります。『true』だと警告メッセージが出て
 うっとうしいですよ。

お試し:
・サンプル・ソースを紹介します。
・このサンプルは、『InternetQueryDataAvailable』関数でサイズを取得してから、メモリを
 『GlobalAlloc』関数で確保して、そこのバッファに『InternetReadFile』関数でHTMLソース
 を読み込みます。→その後、ファイルへ出力します。
・まずは、このサンプルでウェブ上のHTMLソースをファイルにダウンロードできるか試して下さい。
・また、結果報告の後に1Mバイト以上でも繰り返しでダウンロードできるサンプルも紹介します。
・以上。おわり。結果報告をお願いします。→動作確認ですよ。
    • good
    • 0
この回答へのお礼

早速の回答ありがとうございます。
文字数の制限内に収めようとして、切り詰めた結果判り難くなし申し訳ありませんでした。

いただいたサンプルで小さいファイルがダウンロードできることは確認できました。

ご指摘いただいた通りで、同じ変数名がブロック内で再宣言しているの確かにまずいですね。 省略されていますが、whileの前にInternetReadFileが追加されたのですが、そのときに削除すべきだった宣言が残されたままになっていたようです(汗)

お礼日時:2006/12/29 22:04

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!