Уже не раз и не два я сталкиваюсь с тем, что при попытке создать приложение, обладающее способностью многопоточной работы с сетью, программисты встречаются с определенным рядом проблем. В частности, это может быть падение приложения при его закрытии «в процессе работы», несвоевременное удаление QReply объекта, ошибки локов мьютексов и так далее.
Да, я рассматриваю работу с QNetworkAccessManager и сопуствующими, т.к. QHttp назван устаревшим.
Когда я сам занимался разработкой программы, сутью которой была многопоточная отправка файла по частям на сервер (недавняя статья про POST multipart/form-data), я пришел к решению, означенному выше. Сутью такого решения стали два момента:
Такой подход оказался рабочим. Я предлагаю пример, «урезанный» относительно того приложения где он используется, однако он абсолютно прозрачен и может быть использован для Ваших целей.
Некоторым отличием от «стандартной практики» является подход к «запуску потока» — изначально данные в поток пересылаются через 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() есть тот самый «логинящийся» альфа-менеджер, который содержит в себе правильные печеньки.
И последнее, что хотелось бы добавить к вышесказанному — не забывайте обрабатывать ошибки закачки.
Comments
Привет. Спасибо за решение, но почему-то не заработало, в консоли вываливается следующее:
QObject::connect: Cannot connect (null)::aboutToQuit() to QNativeWifiEngine::clo
seHandle()
QObject::startTimer: QTimer can only be used with threads started with QThread
Сам код можно посмотреть тут: http://dl.dropbox.com/u/15884529/src.zip
Добрый День.
В статье неверно используется многопоточность и система сигналов/слотов. Все слоты в данном примере вызывается в главном потоке, так как сам объект унаследованный от QThread создается в главном потоке.
[img]http://freewey.com/Newlife.jpg[/img]
Yesterday i spent 300 bucks for platinium roulette system , i hope that i will earn my first cash
online