C++ 如何在Qt中正确中止网络请求?

C++ 如何在Qt中正确中止网络请求?,c++,qt,networking,C++,Qt,Networking,我有一个类,它执行对网络的请求并解析数据。我如何正确地为它实现请求中止 想象一下,我有这样一门课: class-MyClass { 公众: ... void doRequest() { m_reply=m_manager.get(…); QEventLoop等待响应; 连接(m_reply,&QNetworkReply::finished,&waitForResponse,&QEventLoop::quit); waitForResponse.exec(); //检查请求是否已中止(否则,应用程

我有一个类,它执行对网络的请求并解析数据。我如何正确地为它实现请求中止

想象一下,我有这样一门课:

class-MyClass
{
公众:
...
void doRequest()
{
m_reply=m_manager.get(…);
QEventLoop等待响应;
连接(m_reply,&QNetworkReply::finished,&waitForResponse,&QEventLoop::quit);
waitForResponse.exec();
//检查请求是否已中止(否则,应用程序将崩溃)
if(m_reply==nullptr)
返回;
//检查网络错误,将结果写入m_数据并删除m_回复;
...
}
无效中止()
{
如果(m_reply!=nullptr)
m_reply->abort();
}
QString数据()
{
返回m_数据;
}
...
私人:
QNetworkAccessManager*m_manager;
QPiinter m_回复;
QString m_数据;
}
以下是按下按钮使用的示例:

class主窗口:公共QMainWindow
{
...
专用插槽:
主窗口::在按下按钮时()
{
m_myClass->abort();
m_myClass->doRequest();
ui->myTextEdit->setText(m_myClass->data());
}
私人:
MyClass m_MyClass;
}
按下该按钮时,如果上一个请求未完成,则该请求将被取消。这很有效。但在这种情况下,在引擎盖下,一个新请求将数据写入
QTextEdit
并退出该函数,然后旧请求从其自身循环返回,并将相同的
m_数据再次写入
QTextEdit

是正确的吗?也许有更正确的方法来实现这一点?

嵌套的事件循环非常有用。编写像
doRequest
这样的函数要简单得多,而不必向用户假装它是一个同步函数。似乎您已经跟踪了调用
abort()
时发生的复杂控制流,并且您了解了对
doRequest()
的后续调用如何由于重新进入事件循环而成为嵌套调用。如果多次重新启动请求,堆栈将类似(堆栈向下增长):

当嵌套事件循环退出时,对
main window::on_myButton_pressed()
的每个调用都需要调用
ui->myTextEdit->setText()

另一种方法是使函数完全异步,如果需要在特定操作完成时执行某些操作,则依赖于信号/插槽。以下是您试图实现的最低限度的实现:

#include <QtNetwork>
#include <QtWidgets>

/// A class responsible for communication with the web backend
class Backend : public QObject {
  Q_OBJECT
public:
  explicit Backend(QObject *parent = nullptr)
      : QObject(parent), m_reply(nullptr) {}
public slots:
  void doRequest() {
    // abort and delete ongoing request (if any)
    if (m_reply) {
      m_reply->abort();
      delete m_reply;
      m_reply = nullptr;
    }
    emit textUpdated(QString());
    // send request
    QUrl url("http://wtfismyip.com/text");
    QNetworkRequest request(url);
    m_reply = m_manager.get(request);
    // when the request is finished,
    QObject::connect(m_reply, &QNetworkReply::finished, [this] {
      // if the request ended successfully, read the received ip into m_lastData
      if (m_reply->error() == QNetworkReply::NoError)
        m_lastData = QString::fromUtf8(m_reply->readAll());
      // otherwise, emit errorOccured() signal (if the request has not been
      // actively canceled)
      else if (m_reply->error() != QNetworkReply::OperationCanceledError)
        emit errorOccured(m_reply->errorString());
      // in all cases, emit updateText and do cleanup
      emit textUpdated(m_lastData);
      m_reply->deleteLater();
      m_reply = nullptr;
    });
  }

  void abort() {
    if (m_reply != nullptr)
      m_reply->abort();
  }

signals:
  void textUpdated(const QString &);
  void errorOccured(const QString &);

private:
  QNetworkAccessManager m_manager;
  QNetworkReply *m_reply;
  QString m_lastData;
};

/// A minimal widget that contains a QPushButton and a QLabel
class Widget : public QWidget {
  Q_OBJECT
public:
  explicit Widget(QWidget *parent = nullptr) : QWidget(parent) {
    m_layout.addWidget(&m_pushButton);
    m_layout.addWidget(&m_label);

    connect(&m_pushButton, &QPushButton::clicked, this, &Widget::buttonClicked);
  }

signals:
  void buttonClicked();

public slots:
  void updateText(const QString &text) { m_label.setText(text); }
  void showError(const QString &error) {
    QMessageBox::warning(this, tr("Error"), error);
  }

private:
  QVBoxLayout m_layout{this};
  QPushButton m_pushButton{"Retrieve Name"};
  QLabel m_label;
};

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

  Backend backend;
  Widget widget;

  // connect components
  QObject::connect(&backend, &Backend::textUpdated, &widget,
                   &Widget::updateText);
  QObject::connect(&backend, &Backend::errorOccured, &widget,
                   &Widget::showError);
  QObject::connect(&widget, &Widget::buttonClicked, &backend,
                   &Backend::doRequest);
  widget.show();

  return a.exec();
}

#include "main.moc"
#包括
#包括
///负责与web后端通信的类
类后端:公共QObject{
Q_对象
公众:
显式后端(QObject*parent=nullptr)
:QObject(父对象),m_reply(nullptr){}
公众时段:
void doRequest(){
//中止并删除正在进行的请求(如果有)
如果(请回答){
m_reply->abort();
删除m_回复;
m_reply=nullptr;
}
发出文本更新(QString());
//发送请求
QUrl url(“http://wtfismyip.com/text");
QNetworkRequest请求(url);
m_reply=m_manager.get(请求);
//当请求完成时,
QObject::connect(m_reply,&QNetworkReply::finished,[this]{
//如果请求成功结束,请将收到的ip读入m_lastData
如果(m_reply->error()==QNetworkReply::NoError)
m_lastData=QString::fromUtf8(m_reply->readAll());
//否则,发出ErrorOccursed()信号(如果请求未被执行
//(主动取消)
否则如果(m_reply->error()!=QNetworkReply::OperationCanceledError)
发出错误(m_reply->errorString());
//在所有情况下,发出updateText并进行清理
发射文本更新(m_lastData);
m_reply->deleteLater();
m_reply=nullptr;
});
}
无效中止(){
如果(m_reply!=nullptr)
m_reply->abort();
}
信号:
void textUpdated(常量QString&);
发生无效错误(常量QString&);
私人:
QNetworkAccessManager m_manager;
QNetworkReply*m_reply;
QString m_lastData;
};
///包含QPushButton和QLabel的最小小部件
类Widget:publicqwidget{
Q_对象
公众:
显式小部件(QWidget*parent=nullptr):QWidget(parent){
m_layout.addWidget(&m_按钮);
m_layout.addWidget(&m_标签);
连接(&m_按钮,&QPushButton::clicked,this,&Widget::buttonClicked);
}
信号:
void按钮单击();
公众时段:
void updateText(const QString&text){m_label.setText(text);}
无效淋浴ROR(常量字符串和错误){
QMessageBox::警告(this,tr(“错误”),Error);
}
私人:
QVBoxLayout m_layout{this};
QPushButton m_按钮{“检索名称”};
QLabel m_标签;
};
int main(int argc,char*argv[]){
质量保证申请a(argc、argv);
后端;
小部件;
//连接组件
QObject::connect(&后端,&后端::文本更新,&小部件,
&小部件::updateText);
QObject::connect(&backend,&backend::ErrorOccursed,&widget),
&窗口小部件(ROR);
QObject::connect(&widget,&widget::buttonClicked,&backend),
&后端::doRequest);
widget.show();
返回a.exec();
}
#包括“main.moc”

等一下。。。抱歉,没有弄好:您是否设置了两次
QTextEdit
文本?但是为什么呢?在设置
m_data
之前,您不能检查
m_reply
是否被中止吗?@MasterAler,是的,我有两次
setText
。我有
error()
方法,它返回在
doRequest()中发生的错误枚举。我可以设置取消后的错误,它会工作,但它看起来很奇怪。。。在本例中,我将使MyClass具有有效数据,并从
Error()
中中止
。这看起来不太清楚,对我来说…作为一个类设计问题,这看起来不清楚,这是真的。我会使用类似于
QtConcurrent::run
+
QFuture的东西#include <QtNetwork>
#include <QtWidgets>

/// A class responsible for communication with the web backend
class Backend : public QObject {
  Q_OBJECT
public:
  explicit Backend(QObject *parent = nullptr)
      : QObject(parent), m_reply(nullptr) {}
public slots:
  void doRequest() {
    // abort and delete ongoing request (if any)
    if (m_reply) {
      m_reply->abort();
      delete m_reply;
      m_reply = nullptr;
    }
    emit textUpdated(QString());
    // send request
    QUrl url("http://wtfismyip.com/text");
    QNetworkRequest request(url);
    m_reply = m_manager.get(request);
    // when the request is finished,
    QObject::connect(m_reply, &QNetworkReply::finished, [this] {
      // if the request ended successfully, read the received ip into m_lastData
      if (m_reply->error() == QNetworkReply::NoError)
        m_lastData = QString::fromUtf8(m_reply->readAll());
      // otherwise, emit errorOccured() signal (if the request has not been
      // actively canceled)
      else if (m_reply->error() != QNetworkReply::OperationCanceledError)
        emit errorOccured(m_reply->errorString());
      // in all cases, emit updateText and do cleanup
      emit textUpdated(m_lastData);
      m_reply->deleteLater();
      m_reply = nullptr;
    });
  }

  void abort() {
    if (m_reply != nullptr)
      m_reply->abort();
  }

signals:
  void textUpdated(const QString &);
  void errorOccured(const QString &);

private:
  QNetworkAccessManager m_manager;
  QNetworkReply *m_reply;
  QString m_lastData;
};

/// A minimal widget that contains a QPushButton and a QLabel
class Widget : public QWidget {
  Q_OBJECT
public:
  explicit Widget(QWidget *parent = nullptr) : QWidget(parent) {
    m_layout.addWidget(&m_pushButton);
    m_layout.addWidget(&m_label);

    connect(&m_pushButton, &QPushButton::clicked, this, &Widget::buttonClicked);
  }

signals:
  void buttonClicked();

public slots:
  void updateText(const QString &text) { m_label.setText(text); }
  void showError(const QString &error) {
    QMessageBox::warning(this, tr("Error"), error);
  }

private:
  QVBoxLayout m_layout{this};
  QPushButton m_pushButton{"Retrieve Name"};
  QLabel m_label;
};

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

  Backend backend;
  Widget widget;

  // connect components
  QObject::connect(&backend, &Backend::textUpdated, &widget,
                   &Widget::updateText);
  QObject::connect(&backend, &Backend::errorOccured, &widget,
                   &Widget::showError);
  QObject::connect(&widget, &Widget::buttonClicked, &backend,
                   &Backend::doRequest);
  widget.show();

  return a.exec();
}

#include "main.moc"