Уже не раз и не два я сталкиваюсь с тем, что при попытке создать приложение, обладающее способностью многопоточной работы с сетью, программисты встречаются с определенным рядом проблем. В частности, это может быть падение приложения при его закрытии «в процессе работы», несвоевременное удаление QReply объекта, ошибки локов мьютексов и так далее.
Да, я рассматриваю работу с QNetworkAccessManager и сопуствующими, т.к. QHttp назван устаревшим.
Когда я сам занимался разработкой программы, сутью которой была многопоточная отправка файла по частям на сервер (недавняя статья про POST multipart/form-data), я пришел к решению, означенному выше. Сутью такого решения стали два момента:
- Для каждого потока — свой менеджер
- Менеджер и reply’и должны создаваться вне функции run() потока
Такой подход оказался рабочим. Я предлагаю пример, «урезанный» относительно того приложения где он используется, однако он абсолютно прозрачен и может быть использован для Ваших целей.
Некоторым отличием от «стандартной практики» является подход к «запуску потока» — изначально данные в поток пересылаются через processTask(…), и далее сам поток запускается уже только через playTask(). Это было сделано ради удобства в конкретно моем приложении, поэтому в принципе эти функции можно объединить.
Заголовок класса:
//----------------------------------------------------------------------------- // File: a_utest_uploadthread.h // // Desc: //----------------------------------------------------------------------------- #ifndef A_UTEST_UPLOADTHREAD_H #define A_UTEST_UPLOADTHREAD_H #include <QThread> #include <QMutex> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QNetworkRequest> //-------------------------------------------------------------------------------------- // Name: cUUploadThread // Desc: //-------------------------------------------------------------------------------------- class cUUploadThread : public QThread { Q_OBJECT public: // Constructor //---------------------------------------------------------------------------------- cUUploadThread(QObject *parent); ~cUUploadThread(); // Control //---------------------------------------------------------------------------------- void processTask(/* your data */); private: // Internal data //---------------------------------------------------------------------------------- bool m_abort; QMutex mutex; QNetworkAccessManager *pManager; // External data //---------------------------------------------------------------------------------- signals: // Send data //---------------------------------------------------------------------------------- void sendPermit(); public slots: // Internal control //---------------------------------------------------------------------------------- void sendPart(); // Control thread //---------------------------------------------------------------------------------- void playTask(); void stopProcess(); // Upload control //---------------------------------------------------------------------------------- void slotReadyRead(); void slotError(QNetworkReply::NetworkError); void slotFinished(); void slotUploadProgress( qint64 bytesSent, qint64 bytesTotal); //================================================================================== protected: // Start it //---------------------------------------------------------------------------------- void run(); }; #endif // A_UTEST_UPLOADTHREAD_H
Реализация:
//----------------------------------------------------------------------------- // File: a_utest_uploadthread.cpp // // Desc: //----------------------------------------------------------------------------- #include "a_utest_uploadthread.h" #include <QDebug> //-------------------------------------------------------------------------------------- // cUUploadThread class constructor //-------------------------------------------------------------------------------------- cUUploadThread::cUUploadThread(QObject *parent) : QThread(parent) { // Nullifing data m_abort = false; pManager = 0; pCookie = 0; } // cUUploadThread class destructor //-------------------------------------------------------------------------------------- cUUploadThread::~cUUploadThread() { qDebug() << "cUUploadThread::~cUUploadThread()"; mutex.lock(); m_abort = true; mutex.unlock(); wait(); } //============================================================================= // MAIN //============================================================================= // Processing the dot //-------------------------------------------------------------------------------------- void cUUploadThread::processTask(/* your data */) { qDebug() << "cUUploadThread::processTask()"; mutex.lock(); m_abort = false; mutex.unlock(); /* ADD YOUR DATA FOR TASK */ } // Run thread //-------------------------------------------------------------------------------------- void cUUploadThread::run() { qDebug() << "cUUploadThread::run()"; mutex.lock(); m_abort = false; mutex.unlock(); /// IT IS THE MAIN IDEA OF MULTITHREADED APP WITH QNETWORK // first part sending connect(this, SIGNAL(sendPermit()), SLOT(sendPart())); emit(sendPermit()); // enter main loop exec(); qDebug() << "cUUploadThread::run() end"; } //-------------------------------------------------------------------------------------- void cUUploadThread::sendPart() { qDebug() << "cUUploadThread::sendPart()" << nfileID << npartID; if( !pManager ) pManager = new QNetworkAccessManager(); QNetworkRequest request; request.setUrl(QUrl("http://yoursite.com/uploadgate/")); QByteArray bytes; /* MAKE TRUE REQUEST */ QNetworkReply *reply = pManager->post(request, bytes); connect(reply, SIGNAL(readyRead()), this, SLOT(slotReadyRead())); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(slotError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(slotUploadProgress(qint64,qint64))); connect(reply, SIGNAL(finished()), this, SLOT(slotFinished())); qDebug() << "cUUploadThread::sendPart() OK"; } //============================================================================= // PUBLIC SLOTS //============================================================================= //-------------------------------------------------------------------------------------- void cUUploadThread::playTask() { qDebug() << "cUUploadThread::playTask()"; mutex.lock(); m_abort = true; mutex.unlock(); start(); } // Stop thread if needable //-------------------------------------------------------------------------------------- void cUUploadThread::stopProcess() { qDebug() << "cUUploadThread::stopProcess()"; mutex.lock(); m_abort = true; mutex.unlock(); exit(0); wait(); qDebug() << "PROCESS STOPPED"; } //-------------------------------------------------------------------------------------- void cUUploadThread::slotReadyRead() { qDebug() << "cUUploadThread::slotReadyRead()"; } //-------------------------------------------------------------------------------------- void cUUploadThread::slotError(QNetworkReply::NetworkError error) { qDebug() << "cUUploadThread::slotError(" << error << ")"; stopProcess(); } //-------------------------------------------------------------------------------------- void cUUploadThread::slotFinished() { qDebug() << "cUUploadThread::slotFinished()"; QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); reply->deleteLater(); if( reply->bytesAvailable() > 0 ) { QString reply_str(reply->readAll()); qDebug() << reply_str; } else { qDebug() << "NO DATA TO READ"; stopProcess(); } qDebug() << "DATA OK"; } //-------------------------------------------------------------------------------------- void cUUploadThread::slotUploadProgress( qint64 bytesSent, qint64 bytesTotal) { qDebug() << bytesSent << bytesTotal; }
Кроме того, работая над реальным приложением, я также столкнулся с рядом проблем, связанных с необходимостью использования Cookies сайта для обеспечения «логина» для всех объектов менеджеров сети, т.к. загрузка на сайт происходила именно через такую вот систему.
Решением стал подход, при котором после того, как произошел логин (у меня этим занимается отдельный класс, следящий кроме того еще и за списком фотоальбомов и т.п. вещами), мы либо записываем массив «печенек» из менеджера, либо храним «залогинившийся» менеджер, и из него всегда можем взять данные.
Т.к. мое приложение закачивало многопоточно файлы, но части файла закачивались в одном потоке — это было ограничение системы склейки файлов — то относительно примера выше дополнения таковы:
1. В заголовок класса добавляем:
QNetworkCookieJar *pCookie; QList<QNetworkCookie> l_cookies;
В реализации класса в функции sendPart() после проверки существования менеджера:
if( !pCookie ) pCookie = new QNetworkCookieJar(); if( l_cookies != ENGINE_STATE.networkManager()->cookieJar()->cookiesForUrl( QUrl("http://yoursite.com/") ) ) { qDebug() << "Update cookies"; l_cookies = ENGINE_STATE.networkManager()->cookieJar()->cookiesForUrl(QUrl("http://yoursite.com/") ); pManager->cookieJar()->setCookiesFromUrl(l_cookies, QUrl("http://yoursite.com/")); }
Объект ENGINE_STATE.networkManager() есть тот самый «логинящийся» альфа-менеджер, который содержит в себе правильные печеньки.
И последнее, что хотелось бы добавить к вышесказанному — не забывайте обрабатывать ошибки закачки.