簡體   English   中英

使用Qt和QNetworkRequest恢復失敗的HTTP下載

[英]Resuming a Failed HTTP Download With Qt & QNetworkRequest

我正在嘗試為我正在開發的應用程序添加自動更新功能。 我已經將這個功能基於Qt HTTP示例 (基於我的意思是我完全復制了這個例子然后從那里開始)。 它正在下載ZIP文件,然后提取其內容以修補應用程序。

有時,下載時,連接將失敗,下載將停止。 為了更加用戶友好,我想我會向下載器添加自動重啟功能,如果失敗則會嘗試重啟下載。

以下是我的代碼的亮點 - 方法名稱與示例中的方法名稱匹配:

void Autopatcher::httpReadyRead()
{
    //file is a QFile that is opened when the download starts
    if (file) {
        QByteArray qba = reply->readAll();
        //keep track of how many bytes have been written to the file
        bytesWritten += qba.size();
        file->write(qba);
    }
}

void Autopatcher::startRequest(QUrl url)
{
    //doResume is set in httpFinished() if an error occurred
    if (doResume) {
        QNetworkRequest req(url);
        //bytesWritten is incremented in httpReadyRead()
        QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(bytesWritten) + "-";
        req.setRawHeader("Range",rangeHeaderValue);
        reply = qnam.get(req);
    } else {
        reply = qnam.get(QNetworkRequest(url));
    }
    //slot connections omitted for brevity
}

//connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(fileGetError(QNetworkReply::NetworkError)));
void Autopatcher::fileGetError(QNetworkReply::NetworkError error) {
    httpRequestAborted = true;
}

void Autopatcher::httpFinished() {
    //If an error occurred
    if (reply->error()) {
        //If we haven't retried yet
        if (!retried) {
            //Try to resume the download
            doResume=true;
            //downloadFile() is a method that handles some administrative tasks
            //like opening the file if doResume=false
            //and calling startRequest() with the appropriate URL
            QTimer::singleShot(5000,this,SLOT(downloadFile()));
        }
        //If we have retried already
        else {
            //Give up :(
            if (file) {
                file->close();
                file->remove();
                delete file;
                file = 0;
            }
        }
    //If no error, then we were successful!
    } else {
        if (file) {
            file->close();
            delete file;
            file = 0;
        }
        //Apply the patch
        doPatch();
    }
    reply->deleteLater();
    reply = 0;
}

現在,如果下載正常完成而沒有中斷,它可以正常工作。 ZIP完美提取。 但是,如果連接失敗並且應用程序重新啟動下載,它完成下載,我可以看到7-zip中的ZIP文件的所有內容,但我無法提取它們(7-zip說了一些“試圖在文件開始之前移動指針)。

我假設我在某個地方做了一個簡單的逐個錯誤,就像在HTTP Range標題中一樣。 我已經看到了如何在這個博客上暫停和恢復下載的示例,但是他在暫停時將流的內容寫入文件,而我將它們流式傳輸到httpReadyRead的文件中。 我不知道這是否會導致問題。

為了測試,我一直在使用Sysinternals TCPView在下載過程中切斷TCP連接。 我不確定如何進一步調試,所以如果有更多信息有用,請告訴我!

所以今天我進行了深入研究 我原本以為不間斷和中斷版本的文件大小是相同的+ - 幾個字節,但我錯了。 我下載了兩個版本的文件,大小偏差大約2兆字節。

所以,我使用VBinDiff比較它們(如果你不害怕控制台界面,這是一個很好的實用工具),這就是我發現的:

  • 文件在地址0x0154 21F3處停止匹配。
  • 壞文件中的地址與好文件中的地址0x0178 1FD3匹配,並且它們一直持續匹配直到結束。
  • 因此,壞文件缺少2,358,752字節 - 這與我在資源管理器中看到的2MB近似值相匹配。

這證實了當我嘗試重新啟動下載時, 我正在跳過遠程文件的重要部分 不確定發生了什么,我決定檢查bytesWritten的值,我用它來跟蹤我寫入文件的字節數。 這個值是我寫入Range Request Header的值,所以它的值必須是不正確的。 (請參閱問題中的httpReadyRead()函數)。

所以我在設置Range Request Header之前添加了下面的代碼:

file->flush();
bytesWritten = file->size();

調試代碼,我很驚訝地發現

bytesWritten = 28,947,923
file->size() = 26,589,171

確認bytesWritten值不正確。 事實上,當我使用文件大小而不是bytesWritten值時,下載能夠重新啟動並成功完成!

我不會再深入了,因為這對我有用。 實際上,這將允許重新啟動應用程序實例之間的下載,因此在我看來這是一種更好的方法。

tl; dr不要跟蹤寫入文件的字節數。 只需在重新啟動失敗的下載時檢查文件大小。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM