Запрос с QNetworkAccessManager

У меня странная проблема с получением ответа на запрос get с помощью QNetworkAccessManager.

Это код класса:

requester.h

#ifndef REQUESTER_H
#define REQUESTER_H

#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtCore/QtCore>
#include <QVector>
#include <QObject>
#include <QMessageBox>

class Requester : public QObject
{
Q_OBJECT
public:
explicit Requester(QObject *parent = 0);
~Requester();
QString get_last_reply();
void send_request();
private:
QNetworkAccessManager *manager;
QVector<QString> replies;
private slots:
void get_reply(QNetworkReply *reply);
void get_reply_error(QNetworkReply::NetworkError err);
};

#endif // REQUESTER_H

requester.cpp

#include "requester.h"
Requester::Requester(QObject *p)
: QObject(p)
, manager(new QNetworkAccessManager)
{
QObject::connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(get_reply(QNetworkReply*)));
}

Requester::~Requester() {
delete manager;
}

void Requester::get_reply(QNetworkReply *reply) {
QObject::connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(get_reply_error(QNetworkReply::NetworkError)));
QByteArray res = reply->readAll();
QString data = res.data();
replies.push_back(data);
QObject::disconnect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this,  SLOT(get_reply_error(QNetworkReply::NetworkError)));
reply->deleteLater();
}

void Requester::get_reply_error(QNetworkReply::NetworkError err) {
QMessageBox msg;
msg.setText(QString::number(err));
msg.setStandardButtons(QMessageBox::Discard);
msg.exec();
}

QString Requester::get_last_reply() {
if(!(replies.isEmpty())) {
QString res =  replies.back();
replies.pop_back();
return res;
}
return "";
}

void Requester::send_request() {
QNetworkRequest request;
request.setUrl(QUrl("http://127.0.0.1"));
request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17");
manager->get(request);
//QMessageBox *msg = new QMessageBox;
//msg->exec();
}

Ответ написан в textEdit этой функцией

void MainWindow::ret_out(QString str) {
ui->out->setText(str);
}

Теперь main.cpp

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
Requester req;
req.send_request();
//QMessageBox *msg = new QMessageBox;
//msg->exec();
QString buf = req.get_last_reply();
w.show();
w.ret_out(buf);
return a.exec();
}

Используя этот код у меня есть пустой textEdit. Но если раскомментировать

QMessageBox *msg = new QMessageBox;
msg->exec();

в Requester :: send_request или в main.cpp, тогда textEdit содержит ответ сервера.

Новое обновление

Теперь у меня есть этот код, и он работает. Возможно, это не самый лучший вариант, но я с большим удовольствием выслушаю ваши советы 🙂

void Requester::send_request(QUrl url) {
QEventLoop loop;
loop.connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(get_reply(QNetworkReply*)));
loop.connect(this, SIGNAL(done()), &loop, SLOT(quit()));
QNetworkRequest request;
request.setUrl(url);
request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17");
manager->get(request);
loop.exec(QEventLoop::AllEvents);
loop.disconnect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(get_reply(QNetworkReply*)));
loop.disconnect(this, SIGNAL(done()), &loop, SLOT(quit()));
}

2

Решение

Как я писал ранее в своем комментарии, есть несколько проблем с этим кодом, но главная проблема — последняя, ​​поэтому, если вам не нужны другие предложения, перейдите к этому.

1) QVector отвечает;

Вы должны рассмотреть возможность использования QStringList Вот.

2) QObject :: connect (ответ, SIGNAL (ошибка (QNetworkReply :: NetworkError)), this, SLOT (get_reply_error (QNetworkReply :: NetworkError)));
QByteArray res = reply-> readAll ();

Вы должны сделать это в конструкторе вместе с законченным управлением сигналами.

3) QNetworkAccessManager * manager;

Нет необходимости размещать этот объект в куче, и он также имеет дополнительную нагрузку, такую ​​как создание вручную в конструкторе класса и удаление его вручную в деструкторе класса. Вы можете просто разместить этот объект в стеке без проблем, и это приведет к несколько более простому коду.

4) QByteArray res = reply-> readAll ();

Вам, вероятно, здесь не нужна временная переменная.

5) QString data = res.data ();

Вы должны убедиться в кодировке, поэтому я бы предложил написать что-то вроде этого:

QString data = QString :: fromUtf8 (reply-> readAll ());

или же

QString data = QString :: fromLatin1 (reply-> readAll ());

или же

QString data = QString :: fromLocal8Bit (reply-> readAll ());

6) replies.push_back (data);

Это не стиль Qt. Вы могли бы рассмотреть это:

replies.append (данные);

7) w.ret_out (buf);

Вы просите об этом перед входом в цикл событий Qt. Вы должны написать элемент пользовательского интерфейса с помощью setText (), когда ваш обработчик слота вызывается для завершения. Попробуй это:

void Requester::get_reply(QNetworkReply *reply) {
QObject::connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(get_reply_error(QNetworkReply::NetworkError)));
QByteArray res = reply->readAll();
QString data = res.data();
replies.push_back(data);
QObject::disconnect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this,  SLOT(get_reply_error(QNetworkReply::NetworkError)));
reply->deleteLater();

ui->out->setText(data); // You will need access the "ui" here obviously, or the main window needs access to the Requester object, and the signal-slot has to be connected in there.
}

Вы также можете настроить «местный» QEventLoop путем его создания, но лучше оставить его в главном цикле событий приложения, а именно return a.exec();,

Обратите внимание, что API QtNetwork является асинхронным, и для его работы необходим цикл обработки событий. Это не блокирование, потому что это было бы неудачно для приложений пользовательского интерфейса без дополнительного управления рабочими потоками для конечных пользователей. Вот причина, почему таинственный QDialog::exec() Код исправляет ваш код, потому что это цикл событий прямо там. Однако в этом особом случае это кажется не очень хорошим решением, поэтому я бы предложил использовать вышеупомянутый основной цикл событий приложения, как, вероятно, подавляющее большинство приложений в таких сценариях, подобных этому.

В вашем недавно загруженном коде есть две проблемы:

…MainWindow, SLOT (MainWindow :: ret_out (QString)) …

1) У вас, кажется, нет объекта MainWindow в куче где-либо доступного для вашего запрашивающего класса.

2) MainWindow :: ret_out (QString) не так … точно так же, как вы не используете другие слоты, подобные этому. Вы должны опустить MainWindow :: scope там …

2

Другие решения

Вы используете сигналы и слоты неправильно. QObject :: подключить Метод по умолчанию имеет тип соединения Qt :: AutoConnection, что означает, что когда сигнал выводится из другого потока, он отправляется в очередь сообщений потока объекта и выполняется, когда сообщение перекачивается. QNetworkRequest является асинхронным и работает в другом потоке, так что, похоже, это ваш случай.

В первом случае у вас не работает цикл обработки сообщений и, следовательно, нет шансов на обработку сигнала. Но QMessageBox :: exec запускает свой собственный цикл сообщений, и в этом случае сигнал доставляется.

Это означает, что вы можете отправить запрос после QApplication :: exec или использовать флаг Qt :: DirectConnection в QObject :: connect (). Во втором случае вы должны позаботиться о том, чтобы сделать ваш реквестер потокобезопасным.

И последний вопрос — сбой в напряженном цикле. Я понятия не имею, почему это происходит без стека вызовов, но я уверен, что он исчезает после того, как вы исправите свой код.

0

По вопросам рекламы [email protected]