使用文件流下载多线程TIdHTTP文件会导致文件损坏

时间:2017-03-20 21:43:03

标签: c++builder indy indy10

我已经关注了使用TIdHTTP组件创建多线程文件下载的一些示例,但我对以下问题感到困惑:

但首先,我的代码的简化版本。

此部分计算需要下载的文件的大小:

TIdHTTP* tcpClient = new TIdHTTP(NULL);
tcpClient->ProtocolVersion = pv1_1;
tcpClient->Head(URL);
__int64 LSize = tcpClient->Response->ContentLength;
System::Classes::TFileStream *STFile = new System::Classes::TFileStream(FFileName, fmCreate);
try
{
    STFile->Size = LSize;
}
__finally
{
    delete STFile;
};
delete tcpClient;

Next是我的MainForm多次调用的线程的Execute方法的一部分。 FStartPos是该线程文件的起始位置(i.o.w第一个线程从位置0开始),FEndPos是需要检索的块的结尾:

TFileStream *LStream = new TFileStream(FFileName, fmOpenWrite | fmShareDenyNone);
LHttpClient = new TIdHTTP(NULL);
LHttpClient->ProtocolVersion = pv1_1;
LHttpClient->Request->BasicAuthentication = true;
LHttpClient->Request->Username = FUsername;
LHttpClient->Request->Password = FPassword;
try
{
    LHttpClient->OnWork = ReceiveDataEvent;
    try
    {
    try
    {
      LStream->Seek(FStartPos, TSeekOrigin::soBeginning);
      LHttpClient->Request->Range = "bytes="+UnicodeString(FStartPos)+"-"+UnicodeString(FEndPos);
      LHttpClient->Get(URL, LStream);
      IsFin = true;
    } catch(Exception &e)
    {
        // log the error
    }
    }
    __finally
    {
        LHttpClient->Disconnect();
        delete LHttpClient;
    }
}
__finally
{
    delete LStream;
}

当我尝试下载例如一个87MB的文件,我创建了5个下载线程,所以每个线程应该下载17MB奇数。我看到的是,文件被创建(并报告大小为87MB)。通常第3个线程首先完成,文件大小跳到52MB,然后线程1完成,文件大小跳到17MB,最后线程4完成,文件现在为69MB(而不是87MB开始)。

我有一种感觉,我要么错误地使用TFileStream,要么以不适合使用它的方式使用它。

我的问题是,我的代码错了吗?或者是否有更好/更合适的方式从多个线程写入单个文件,但每个文件都在自己的块中?

(我正在使用内置的Indy 10运行C ++ Builder 10.1)

提前感谢任何建议。

-G -

1 个答案:

答案 0 :(得分:0)

您的问题的根源是您的工作线程在创建fmOpenWrite时使用TFileStream标记:

  

打开文件仅供写入。 写入文件会完全取代当前内容。

这意味着每个线程都会擦除文件中已有的内容并将其大小重置为0!

对于您的尝试,您需要使用fmOpenReadWrite代替:

  

打开文件以修改当前内容而不是替换它们

话虽如此,还有其他一些事情需要考虑:

  • 退出Head()后,您应确保Response->AcceptRange属性设置为"bytes"。如果不是,请不要生成多个线程来下载文件,因为服务器将忽略您分配给Request->Range属性的任何内容,并且每个请求将完整下载整个文件。

  • 当您为下载范围生成多个线程时,Get()收到响应标头后,您应该检查Response->ContentRange...属性以确保获得预期,并且处理服务器可能向您发送的范围小于您请求的范围。例如,您可以在OnHeadersAvailable事件中进行检查,如果发生意外情况,则可以在将任何内容写入TFileStream之前取消下载(通过引发异常)。这对于范围处理尤为重要,以防您收到200响应(接收整个文件)而不是206响应(仅接收文件的某个范围)。

  • 不推荐Request->Range属性,您应该使用Request->Ranges属性:

    //LHttpClient->Request->Range = "bytes="+UnicodeString(FStartPos)+"-"+UnicodeString(FEndPos);      
    TIdEntityRange *range = LHttpClient->Request->Ranges->Add();
    range->StartPos = FStartPos;
    range->EndPos = FEndPos;