QT实现后台HTTP请求(不卡主线程,界面不卡死)

62 篇文章 8 订阅

QT自带的HTTP请求类,如果直接使用会卡死界面,体验非常不好,于是我封装了一个http请求类,同时支持同步和异步的方式访问,最主要的好处是异步的时候使用了多线程,不卡主界面!

而使用同步的时候,虽然理论上也不卡主线程,但是实际测试界面仍然会卡死,原因可能和QEventloop有关,暂时不知道有什么解决办法。

直接上代码:

//author:autumoon
//联系QQ:4589968
//日期:2020-12-04
#ifndef HTTPREQUEST_H
#define HTTPREQUEST_H

#include <QNetworkReply>
#include <QNetworkRequest>
#include <QThread>
#include <QEventLoop>

//请求方式,之所以使用宏定义是因为方便信号传输
#define     HTTP_POST         1
#define     HTTP_GET            2
#define     HTTP_PUT            3
#define     HTTP_DELETE       4

class HttpRequest : public QObject
{
    Q_OBJECT
public:
    explicit HttpRequest(QObject *parent = nullptr);

    bool RequestHttpUrl(const QString& strUrl, int eHm = HTTP_POST, const QByteArray& baData = QByteArray(),  int msec = 2000);

    //判断主机是否在线
    static bool IsHostOnline(QString strHostName, int nTimeoutmSeconds = 2000);
    //判断是否通外网,能连接百度IP说明可以通外网
    static bool IsWebOk(){return IsHostOnline("202.108.22.5", 2000);}

Q_SIGNALS:
    void reply_data(bool bSuccess, const QByteArray& ba);

private:
    static bool parseReplyData(QNetworkReply* reply, QByteArray& data);

    static void setRequestHeader(QNetworkRequest &request)
    {
        request.setRawHeader("Content-Type", "charset='utf-8'");
        request.setRawHeader("Content-Type", "application/json");
    }
    void sleep(int msec = 5000);
};

#endif // HTTPREQUEST_H

//启用线程调用下载
class requestWorker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString& strUrl, int eHm, const QByteArray& baData, int msec = 2000)
    {
        /* ... here is the expensive or blocking operation ... */
        HttpRequest *hr = new HttpRequest(this);
        connect(hr, SIGNAL(reply_data(bool, const QByteArray&)), this, SIGNAL(reply_data(bool, const QByteArray&)));
        hr->RequestHttpUrl(strUrl, eHm, baData, msec);
    }

Q_SIGNALS:
    void reply_data(bool bSuccess, const QByteArray& ba);
};

class ThreadHttpRequest : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    ThreadHttpRequest()
    {
        requestWorker *worker = new requestWorker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &ThreadHttpRequest::request_data, worker, &requestWorker::doWork);
        connect(worker, &requestWorker::reply_data, this, &ThreadHttpRequest::get_result);
        connect(worker, &requestWorker::reply_data, this, &ThreadHttpRequest::reply_data);
        workerThread.start();
    }
    ~ThreadHttpRequest()
    {
        workerThread.quit();
        workerThread.wait();
    }

    //异步,信号返回结果
    void RequestHttpUrl(const QString& strUrl, int eHm, const QByteArray& baData, int msec = 2000)
    {
        emit request_data(strUrl, eHm, baData, msec);
    }

    //同步,直接返回结果
    bool RequestHttpUrl(const QString& strUrl, int eHm, const QByteArray& baData, QByteArray& baRes, int msec = 2000)
    {
        QEventLoop el;
        connect(this, SIGNAL(reply_data(bool, const QByteArray&)), &el, SLOT(quit()));

        emit request_data(strUrl, eHm, baData, msec);

        el.exec(QEventLoop::ExcludeUserInputEvents);

        //获取结果
        baRes = m_baRes;

        return m_bRes;
    }

Q_SIGNALS:
    void reply_data(bool bSuccess, const QByteArray& ba);
    void request_data(const QString& strUrl, int eHm, const QByteArray& baData, int msec);

private slots:
    void get_result(bool bSuccess, const QByteArray& ba)
    {
        m_bRes = bSuccess;
        m_baRes = ba;
    }

private:
    bool m_bRes;
    QByteArray m_baRes;
};

实现文件:

//author:autumoon
//联系QQ:4589968
//日期:2020-12-04
#include "HttpRequest.h"
#include <QEventLoop>
#include <QTimer>
#include "JsonParser.h"

HttpRequest::HttpRequest(QObject *parent) : QObject(parent)
{

}

bool HttpRequest::RequestHttpUrl(const QString &strUrl, int eHm, const QByteArray &baData, int msec)
{
    QNetworkRequest request(strUrl);
    setRequestHeader(request);

    QNetworkAccessManager* naManager = new QNetworkAccessManager(this);

    QEventLoop eventloop;
    QTimer timer;
    timer.singleShot(msec, &eventloop, SLOT(quit()));
    timer.start();

    QNetworkReply* reply = nullptr;

    switch (eHm)
    {
    case HTTP_POST:
        reply = naManager->post(request, baData);
        break;
    case HTTP_PUT:
        reply = naManager->put(request, baData);
        break;
    case HTTP_DELETE:
        reply = naManager->deleteResource(request);
        break;

    case HTTP_GET:
    default:
        reply = naManager->get(request);
        break;
    }

    QMetaObject::Connection conRet = QObject::connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
    Q_ASSERT(conRet);

    eventloop.exec(QEventLoop::ExcludeUserInputEvents);

    QByteArray baReply;
    if (timer.isActive() && parseReplyData(reply, baReply))
    {
        reply->deleteLater();
        emit reply_data(true, baReply);

        return true;
    }

    //超时,未知状态
    disconnect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
    reply->abort();
    reply->deleteLater();
    emit reply_data(false, baReply);

    return false;
}

bool HttpRequest::IsHostOnline(QString strHostName, int nTimeoutmSeconds)
{
    QNetworkRequest request(strHostName);
    setRequestHeader(request);
    QNetworkAccessManager* naManager = new QNetworkAccessManager;

    QEventLoop eventloop;
    QTimer timer;
    timer.singleShot(nTimeoutmSeconds, &eventloop, SLOT(quit()));
    timer.start();

    QNetworkReply* reply = naManager->get(request);
    QMetaObject::Connection conRet = QObject::connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
    Q_ASSERT(conRet);

    eventloop.exec(QEventLoop::ExcludeUserInputEvents);

    if (!timer.isActive())
    {
        //超时,未知状态
        disconnect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
        reply->abort();
        reply->deleteLater();

        return false;
    }

    if (reply->error() != QNetworkReply::NoError)
    {
        reply->abort();
        reply->deleteLater();
        return false;
    }

    bool bRes = reply->readAll().length() > 0;
    reply->abort();
    reply->deleteLater();

    return bRes;
}

bool HttpRequest::parseReplyData(QNetworkReply *reply, QByteArray &data)
{
    if (reply == nullptr)
    {
        data = QString("Unbelievable! The reply pointer is nullptr!").toUtf8();

        return false;
    }

    QString strResult;

    // 获取http状态码
    QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
    if(statusCode.isValid())
        strResult += "status code=" + statusCode.toString() + '\n';

    QVariant reason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
    if(reason.isValid())
        strResult += "reason=" + reason.toString() + '\n';

    QNetworkReply::NetworkError err = reply->error();

    if(err != QNetworkReply::NoError)
    {
        strResult += "The err code is " + QString::number(err) + ";";
        data = strResult.toUtf8();

        return false;
    }
    else
    {
        //输出内容
        data = reply->readAll();
    }

    return true;
}

void HttpRequest::sleep(int msec)
{
    QEventLoop loop;//定义一个新的事件循环
    QTimer::singleShot(msec, &loop, SLOT(quit()));//创建单次定时器,槽函数为事件循环的退出函数
    loop.exec();//事件循环开始执行,程序会卡在这里,直到定时时间到,本循环被退出
}

欢迎交流与讨论。

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值