QT下载大文件错误

时间:2017-03-03 13:46:24

标签: qt file download

当我尝试下载最高50mb的文件时,没问题,但是有大文件会出现以下错误err

 void MainWindow::downloadFile() {
           QNetworkRequest requests;
           requests.setUrl(QUrl("https://urlToFile"));

           QSslConfiguration configSsl = QSslConfiguration::defaultConfiguration();
           configSsl.setProtocol(QSsl::AnyProtocol);
           requests.setSslConfiguration(configSsl);
           QNetworkAccessManager *manager5 = new QNetworkAccessManager(this);
           QNetworkReply *reply5;
           reply5 = manager5->get( requests );
           connect(manager5, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));
}


    void MainWindow::downloadFinished(QNetworkReply *data) {
     QFile localFile("fileName");
        if (!localFile.open(QIODevice::WriteOnly))
            return;
        localFile.write(data->readAll());
        localFile.close();
    }

1 个答案:

答案 0 :(得分:5)

在您的代码中,您将整个文件保留在内存中,直到下载过程完成(即发出QNetworkAccessManager::finished()信号时)。当然,这不是处理大文件的最佳方式。

请注意QNetworkReplyQIODevice。这意味着,一旦从网络接收到数据块,就应该使用readyRead()信号将数据块保存到磁盘,以避免在下载完成之前将整个文件保留在内存中。

这是一个最小的完整示例:

#include <QtNetwork>

int main(int argc, char* argv[]){
    QCoreApplication a(argc, argv);

    QNetworkAccessManager nam;
    QFile file("downloadedFile.xxx");
    if(!file.open(QIODevice::ReadWrite)) return 1;
    QNetworkRequest request(QUrl("http://download_url/..."));
    QNetworkReply* reply = nam.get(request);

    QObject::connect(reply, &QNetworkReply::readyRead, [&]{
        //this will be called every time a chunk of data is received
        QByteArray data= reply->readAll();
        qDebug() << "received data of size: " << data.size();
        file.write(data);
    });

    //use the finished signal from the reply object to close the file
    //and delete the reply object
    QObject::connect(reply, &QNetworkReply::finished, [&]{
        qDebug() << "finished downloading";
        QByteArray data= reply->readAll();
        file.write(data);
        file.close();
        reply->deleteLater();
        a.quit();
    });

    return a.exec();
}

更新

我已将整个内容包装在类FileDownloader中,可用于使用QNetworkAccessManager下载文件,以下是使用此类的示例:

screenshot

#include <QtWidgets>
#include <QtNetwork>

//downloads one file at a time, using a supplied QNetworkAccessManager object
class FileDownloader : public QObject{
    Q_OBJECT
public:
    explicit FileDownloader(QNetworkAccessManager* nam, QObject* parent= nullptr)
        :QObject(parent),nam(nam)
    {

    }
    ~FileDownloader(){
        //destructor cancels the ongoing dowload (if any)
        if(networkReply){
            a_abortDownload();
        }
    }

    //call this function to start downloading a file from url to fileName
    void startDownload(QUrl url, QString fileName){
        if(networkReply) return;
        destinationFile.setFileName(fileName);
        if(!destinationFile.open(QIODevice::WriteOnly)) return;
        emit goingBusy();
        QNetworkRequest request(url);
        networkReply= nam->get(request);
        connect(networkReply, &QIODevice::readyRead, this, &FileDownloader::readData);
        connect(networkReply, &QNetworkReply::downloadProgress,
                this, &FileDownloader::downloadProgress);
        connect(networkReply, &QNetworkReply::finished,
                this, &FileDownloader::finishDownload);
    }

    //call this function to abort the ongoing download (if any)
    void abortDownload(){
        if(!networkReply) return;
        a_abortDownload();
        emit backReady();
    }

    //connect to the following signals to get information about the ongoing download
    Q_SIGNAL void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
    Q_SIGNAL void downloadSuccessful();
    Q_SIGNAL void downloadError(QString errorString);
    //the next two signals are used to indicate transitions between busy and
    //ready states of the file downloader, they can be used to update the GUI
    Q_SIGNAL void goingBusy();
    Q_SIGNAL void backReady();

private:
    Q_SLOT void readData(){
        QByteArray data= networkReply->readAll();
        destinationFile.write(data);
    }
    Q_SLOT void finishDownload(){
        if(networkReply->error() != QNetworkReply::NoError){
            //failed download
            a_abortDownload();
            emit downloadError(networkReply->errorString());
        } else {
            //successful download
            QByteArray data= networkReply->readAll();
            destinationFile.write(data);
            destinationFile.close();
            networkReply->deleteLater();
            emit downloadSuccessful();
        }
        emit backReady();
    }
    //private function, cleans things up when the download is aborted
    //(due to an error or user interaction)
    void a_abortDownload(){
        networkReply->abort();
        networkReply->deleteLater();
        destinationFile.close();
        destinationFile.remove();
    }

    QNetworkAccessManager* nam;
    QUrl downloadUrl;
    QFile destinationFile;
    QPointer<QNetworkReply> networkReply;
};

//A sample GUI application that uses the above class
class Widget : public QWidget{
    Q_OBJECT
public:
    explicit Widget(QWidget* parent= nullptr):QWidget(parent){
        layout.addWidget(&lineEditUrl, 0, 0);
        layout.addWidget(&buttonDownload, 0, 1);
        layout.addWidget(&progressBar, 1, 0);
        layout.addWidget(&buttonAbort, 1, 1);
        layout.addWidget(&labelStatus, 2, 0, 1, 2);
        lineEditUrl.setPlaceholderText("URL to download");
        connect(&fileDownloader, &FileDownloader::downloadSuccessful,
                this, &Widget::downloadFinished);
        connect(&fileDownloader, &FileDownloader::downloadError,
                this, &Widget::error);
        connect(&fileDownloader, &FileDownloader::downloadProgress,
                this, &Widget::updateProgress);
        connect(&buttonDownload, &QPushButton::clicked,
                this, &Widget::startDownload);
        connect(&buttonAbort, &QPushButton::clicked,
                this, &Widget::abortDownload);

        showReady();
        //use goingBusy() and backReady() from FileDownloader signals to update the GUI
        connect(&fileDownloader, &FileDownloader::goingBusy, this, &Widget::showBusy);
        connect(&fileDownloader, &FileDownloader::backReady, this, &Widget::showReady);
    }

    ~Widget() = default;

    Q_SLOT void startDownload(){
        if(lineEditUrl.text().isEmpty()){
            QMessageBox::critical(this, "Error", "Enter file Url", QMessageBox::Ok);
            return;
        }
        QString fileName =
                QFileDialog::getSaveFileName(this, "Destination File");
        if(fileName.isEmpty()) return;
        QUrl url= lineEditUrl.text();
        fileDownloader.startDownload(url, fileName);
    }
    Q_SLOT void abortDownload(){
        fileDownloader.abortDownload();
    }

    Q_SLOT void downloadFinished(){
        labelStatus.setText("Download finished successfully");
    }
    Q_SLOT void error(QString errorString){
        labelStatus.setText(errorString);
    }
    Q_SLOT void updateProgress(qint64 bytesReceived, qint64 bytesTotal){
        progressBar.setRange(0, bytesTotal);
        progressBar.setValue(bytesReceived);
    }
private:
    Q_SLOT void showBusy(){
        buttonDownload.setEnabled(false);
        lineEditUrl.setEnabled(false);
        buttonAbort.setEnabled(true);
        labelStatus.setText("Downloading. . .");
    }
    Q_SLOT void showReady(){
        buttonDownload.setEnabled(true);
        lineEditUrl.setEnabled(true);
        buttonAbort.setEnabled(false);
        progressBar.setRange(0,1);
        progressBar.setValue(0);
    }

    QGridLayout layout{this};
    QLineEdit lineEditUrl;
    QPushButton buttonDownload{"Start Download"};
    QProgressBar progressBar;
    QPushButton buttonAbort{"Abort Download"};
    QLabel labelStatus{"Idle"};
    QNetworkAccessManager nam;
    FileDownloader fileDownloader{&nam};
};

int main(int argc, char* argv[]){
    QApplication a(argc, argv);

    Widget w;
    w.show();

    return a.exec();
}

#include "main.moc"