我已经关注了使用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 -
答案 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;