Ошибка нескольких загрузок QNetworkAccessManager

В моем приложении у меня есть способ загрузки файлов на сервер, он отлично работает.

Но когда я вызываю этот метод несколько раз одновременно (например, перебирая результат ChooseFilesDialog), первые 7 (более или менее) файлов загружаются правильно, остальные никогда не загружаются.

Я думаю, это должно быть связано с тем фактом, что сервер не разрешает более X подключений из одного и того же источника?

Как я могу убедиться, что загрузка ожидает свободного установленного соединения?

это мой метод:

QString Api::FTPUpload(QString origin, QString destination)
{
    qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
    QUrl url("ftp://ftp."+getLSPro("domain")+destination);
    url.setUserName(getLSPro("user"));
    url.setPassword(getLSPro("pwd"));

    QFile *data = new QFile(origin, this);
    if (data->open(QIODevice::ReadOnly))
    {
        QNetworkAccessManager *nam = new QNetworkAccessManager();
        QNetworkReply *reply = nam->put(QNetworkRequest(url), data);
        reply->setObjectName(QString::number(timestamp));
        connect(reply, SIGNAL(uploadProgress(qint64, qint64)), SLOT(uploadProgress(qint64, qint64)));

        return QString::number(timestamp);
    }
    else
    {
        qDebug() << "Could not open file to FTP";
        return 0;
    }
}

void Api::uploadProgress(qint64 done, qint64 total) {
    QNetworkReply *reply = (QNetworkReply*)sender();
    emit broadCast("uploadProgress","{\"ref\":\""+reply->objectName()+"\" , \"done\":\""+QString::number(done)+"\", \"total\":\""+QString::number(total)+"\"}");
}

person Vincent Duprez    schedule 18.08.2013    source источник
comment
Вы создаете новый QNetworkAccessManager для каждого загружаемого файла. Это не обязательно; вам нужен только один. Кроме того, поскольку вы не поддерживаете указатель на объект, у вас также будет утечка памяти. Также обратите внимание, что в ответе QNetwork есть ошибка сигнала (код QNetworkReply::NetworkError), которую вы должны обработать, чтобы увидеть, в чем реальная проблема, когда загрузка не удалась, а не догадываться, что проблема связана с максимальным количеством соединений сервера.   -  person TheDarkKnight    schedule 19.08.2013
comment
Хорошо, я переместил QNetworkAccesManager наружу и повторно использовал то же самое, теперь загрузки выполняются одна за другой с интервалом в секунду или около того. Я предполагаю, что у QnetworkAccessManager есть какая-то очередь. Жаль, что нет какого-то свойства «множество одновременно». Спасибо! Не поддерживать указатель? где какой объект? (Я новичок в концепции указателей)   -  person Vincent Duprez    schedule 20.08.2013
comment
Каждый раз, когда вы используете ключевое слово «новое», вы должны убедиться, что вы поддерживаете указатель на него и вызываете «удалить», когда закончите с ним. В этом случае показанный вами код создал QNetworkAccessManager с именем 'nam', который затем выходит за рамки. Объект существует, но у вас нет указателя для вызова удаления, когда объект больше не нужен или когда программа завершает работу. Те же правила применяются к 'ответу' объекта QNetworkReply, так как это новый объект, созданный и переданный вам вызовом put().   -  person TheDarkKnight    schedule 20.08.2013
comment
Хорошо, понял, так что теперь мой *name, как «глобально используемый», может оставаться, пока приложение работает, но я удаляю *ответ. Спасибо!   -  person Vincent Duprez    schedule 20.08.2013
comment
Вы должны стараться избегать глобальных переменных, если это то, что вы делаете. Но в любом случае убедитесь, что он удален при выходе из программы, если не раньше.   -  person TheDarkKnight    schedule 20.08.2013


Ответы (1)


Во-первых, не создавайте QNetworkManager каждый раз, когда вы начинаете загрузку.
Во-вторых, вам обязательно нужно удалить все, что вы new(), иначе у вас останутся утечки памяти. Сюда входят QFile, QNetworkManager И QNetworkReply (!).
В-третьих, вам нужно дождаться сигнала finished().

Api::Api() {  //in the constructor create the network access manager
    nam = new QNetworkAccessManager()
    QObject::connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(finished(QNetworkReply*)));
}

Api::~Api() {  //in the destructor delete the allocated object
    delete nam;
}

bool Api::ftpUpload(QString origin, QString destination) {
    qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
    QUrl url("ftp://ftp."+getLSPro("domain")+destination);
    url.setUserName(getLSPro("user"));
    url.setPassword(getLSPro("pwd"));

    //no allocation of the file object;
    //will automatically be destroyed when going out of scope
    //I use readAll() (see further) to fetch the data
    //this is OK, as long as the files are not too big
    //If they are, you should allocate the QFile object
    //and destroy it when the request is finished
    //So, you would need to implement some bookkeeping,
    //which I left out here for simplicity
    QFile file(origin);
    if (file.open(QIODevice::ReadOnly)) {
        QByteArray data = file.readAll();   //Okay, if your files are not too big
        nam->put(QNetworkRequest(url), data);
        return true;

        //the finished() signal will be emitted when this request is finished
        //now you can go on, and start another request
    }
    else {
      return false;
    }
}

void Api::finished(QNetworkReply *reply) {
    reply->deleteLater();  //important!!!!
}
person Kurt Pattyn    schedule 24.08.2013
comment
Спасибо, это ясно. просто чтобы понять это правильно: только в этом сценарии, когда «класс» Api создается один раз для всей жизни приложения, мне нужен полный деструктор? поскольку деструктор никогда не будет вызван.. (Или я совершенно не прав?) Когда приложение закрывается, ОС очищает все, что осталось? Я знаю, что это лучшая практика, но, как я уже сказал, просто чтобы понять поведение ОС... - person Vincent Duprez; 24.08.2013
comment
Если класс API — это просто синглтон, вам не нужно очищать его. Он будет очищен при выходе из приложения. Но, как вы указываете, рекомендуется всегда очищать ресурсы, которые вы выделяете (или файлы, которые вы открываете). Вы никогда не знаете, что в один прекрасный день вы решите динамически создать более 1 объекта, тогда хорошо иметь код очистки. - person Kurt Pattyn; 24.08.2013