Qt 后台多线程http请求例子分享

一般来说,对于http请求。Qt制作的程序通过本身信号槽的异步工作,就可以满足异步需要。

但是在一些特定环境中,比如某些事件来自于别的进程/系统的事件。本身不通过Qt本身主循环进入的子线程,再想通过http请求发送一个post请求,就要使用QNetworkAccessManager在子线程中创建,并绑定在一个函数过程的生命周期中,如果需要处理返回结果,还需要使用同步的QEventLoop去同步执行,等待执行并返回结果。但是这样做,会增加该函数过程的执行等待时间。在只是需要发送一个log情况下,这种会大幅降低本次请求的其他业务操作整体函数的等待时间(因服务端日志这种业务本身与实际业务无关,但又与本身子线程设计逻辑相左)。故需要一个能多线程、能自动执行并清理,并继续处理http结果的方式去处理LOG请求。

下面则是我目前用到处理这种需求的典型例子,是一个能分叉继续执行Http请求的服务端日志业务的例子。

头文件:

#ifndef HTTPPOSTLOGTHREAD_H
#define HTTPPOSTLOGTHREAD_H

#include <QMap>
#include <QMutex>
#include <QThread>
#include <QNetworkRequest>

class QNetworkAccessManager;
class HTTPPostLogThread : public QThread
{
    Q_OBJECT
    
public:
    enum BodyType{
        JSONType = 0,
        FileType = 1
    } ;


    HTTPPostLogThread();
    virtual ~HTTPPostLogThread();

Q_SIGNALS:
    void PostDone(const QString& url,int httpcode);

public:

    void StartPostLogEvent(const QMap<QString,QString>& pCookite, const QString& url,const QByteArray& ctx,BodyType type = FileType);

    void RequestPostJSON(const QMap<QString, QString>& otherCookies, const QString &uri, QByteArray &data, bool hasAccessInfo=true);

    void RequestPostFILE(const QMap<QString, QString>& otherCookies, const QString &uri, const QByteArray &data, bool hasAccessInfo=true);

    QString url;
    QByteArray ctx;
    QMap<QString,QString> privateCookie;
protected Q_SLOTS:

    void OnHttpFinished(QNetworkReply* m_reply);
protected:
    void requestPostAsync(const QMap<QString, QString>& otherCookies,QNetworkRequest& req, const QByteArray &data, bool hasAccessInfo=true, bool enc=true, bool isFileServer=false);
    
    void run();
    static long startThreadNumber;
    static QMutex mutex;
    QNetworkAccessManager* http; //必须是与线程类实例生命周期一致。
    BodyType ctxType;
    
};

#endif // HTTPPOSTLOGTHREAD_H

 

类文件:

#include "HTTPPostLogThread.h"
。。。。。。 //隐去

#include <QNetworkRequest>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QEventLoop>

#define POSTLOG_THREAD_MAX 40  //最大线程数量
#define POSTLOG_THREAD_SLEEP 50 //msleep
long HTTPPostLogThread::startThreadNumber = 0;
QMutex HTTPPostLogThread::mutex;


HTTPPostLogThread::HTTPPostLogThread(QObject* parent)
	: QThread(parent), url(QString()) ,ctx(QByteArray()) , privateCookie(QMap<QString,QString>())
	,http(nullptr),ctxType(FileType)
{
	this->connect(this, SIGNAL(finished()),this ,SLOT(deleteLater())); //自动清理
}

HTTPPostLogThread::~HTTPPostLogThread()
{
	if(http){
		delete http; //不能用delete_later,因为托管线程已退出
		http = nullptr;
	}

}

//设置线程参数,启动线程
void HTTPPostLogThread::StartPostLogEvent(const QMap<QString, QString> &pCookite, const QString &url, const QByteArray &ctx,BodyType ctxType)
{
    privateCookie.clear();
    QMapIterator<QString,QString> iter(pCookite);
    while(iter.hasNext()){
        QString newKey = iter.key();
        QString newValue = iter.value();
        privateCookie.insert(newKey,newValue);
    }
    
    this->url.clear();
    this->url.append(url);
    this->ctx.clear();
    this->ctx.append(ctx);
    this->ctxType = ctxType;

    bool canDoStart = false;


	while(!canDoStart){
		mutex.lock();
		if(startThreadNumber > POSTLOG_THREAD_MAX){ //线程太多时需要限制。
			qWarning() << " PostLogEvent Thread Stack is too big sleep "<< POSTLOG_THREAD_SLEEP <<"ms thread number: " << startThreadNumber;
			canDoStart = false;
		}else{
			qInfo() << " PostLogEvent Thread ready to start thread number: " << startThreadNumber;
			canDoStart = true;
		}
		mutex.unlock();
		if(!canDoStart)
			QThread::currentThread()->msleep(POSTLOG_THREAD_SLEEP);
	}

	mutex.lock();
	startThreadNumber++; //统计线程执行数量 开始
	mutex.unlock();
    this->start();

}

//异步发送请求
void HTTPPostLogThread::requestPostAsync(const QMap<QString, QString> &otherCookies, QNetworkRequest& req, const QByteArray &data, bool hasAccessInfo, bool enc, bool isFileServer)
{

    if (isFileServer) {
        QString user = Storage::getInstance()->getFileServerUser(); //请自定义
        QString passwd = Storage::getInstance()->getFileServerPasswd(); //请自定义
        auto verify = QString("%1:%2").arg(user, passwd);
        QString header = "Basic " + verify.toLocal8Bit().toBase64();
        req.setRawHeader("Authorization", header.toLocal8Bit());
    }
    
    auto s = Storage::getInstance();
    
    req.setRawHeader("Accept-Language", get_language_is_chinese() ? "zh-CN" : "en-US");
    
    
    QSslConfiguration config = req.sslConfiguration();
    config.setPeerVerifyMode(QSslSocket::VerifyNone);
    config.setProtocol(QSsl::AnyProtocol);
    req.setSslConfiguration(config);
    
    http->setStrictTransportSecurityEnabled(false);
    
    if (hasAccessInfo) {
        req.setRawHeader("Cookie", cookies.join(";").toUtf8());
    }

    connect(http,&QNetworkAccessManager::finished,this,&HTTPPostLogThread::OnHttpFinished,Qt::DirectConnection); //同进程可以强制推送信号

    if (enc) {
        http->post(req, EncryptPostRequest(data, hasAccessInfo)); //请自定义
    }
    else {
        http->post(req, data);
    }

}

//发送JSON Http Post(application/json)
void HTTPPostLogThread::RequestPostJSON(const QMap<QString, QString> &otherCookies, const QString &uri, QByteArray &data, bool hasAccessInfo)
{
    if(!http){
        http = new QNetworkAccessManager();
        http->moveToThread(this); //移动到该线程,否则还在qt主循环中执行,只是异步处理了(macox平台时,QNetworkAccessManager会有单独的线程)
    }
    QNetworkRequest req;
    
    req.setUrl(uri);
    req.setRawHeader("Accept", "application/json, text/plain, */*");
    req.setRawHeader("Content-Type", "application/json;charset=UTF-8");
    
    requestPostAsync(otherCookies, req , data, hasAccessInfo, false);
}


//发送文件 Http Post(multipart/form-data)
void HTTPPostLogThread::RequestPostFILE(const QMap<QString, QString> &otherCookies,const QString &uri, const QByteArray &data, bool hasAccessInfo)
{

    if(!http){
        http = new QNetworkAccessManager();
        http->moveToThread(this); //移动到该线程(托管),否则还在qt主循环中执行,只是异步处理了(macox平台时,QNetworkAccessManager会有单独的线程)
    }


    QNetworkRequest req;

#define BIN_BOUNDARY "-----------------------post" //请自定义

    qInfo() << "[LOG] " << data;
    auto cT = QString("multipart/form-data;boundary=%1").arg(BIN_BOUNDARY);
    QByteArray dataBin = "--";
    dataBin.append(BIN_BOUNDARY);
    dataBin.append("\r\n");
    dataBin.append("Content-Disposition: form-data;name=\"loglist\";filename=\"log.log\"\r\n"); //请自定义
    dataBin.append("Content-Type: application/octet-stream\r\n");
    dataBin.append("Content-Transfer-Encoding: binary\r\n");
    dataBin.append("\r\n");
    dataBin.append(data);
    dataBin.append("\r\n");
    dataBin.append("--");
    dataBin.append(BIN_BOUNDARY); //请自定义
    dataBin.append("--\r\n");

    req.setUrl(uri);
    req.setRawHeader("Accept", "application/json, text/plain, */*");
    req.setRawHeader("Content-Type", cT.toUtf8());
    req.setRawHeader("Connection", "Keep-Alive");

    requestPostAsync(otherCookies,req , dataBin, hasAccessInfo,false);
}


void HTTPPostLogThread::OnHttpFinished(QNetworkReply *m_reply)
{
	if ( !m_reply ){
		qWarning() << " reply is null ";
	}else{
		if ( QNetworkReply::NoError == m_reply->error()) {
			QByteArray resp = m_reply->readAll();
			qDebug() << "Done request " << resp;
		}
		else {
			qWarning() << "HTTP PostLog Async error: " << m_reply->errorString();
		}

		QVariant statusCode = m_reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
		Q_EMIT PostDone(this->url,m_reply->error()==QNetworkReply::NoError? 200 : statusCode.toInt());
	}


	mutex.lock();
	startThreadNumber--; //统计线程执行数量 结束
	mutex.unlock();
	this->exit();

}

void HTTPPostLogThread::run(){
    
    qDebug() << " PostLogEvent Thread Start";
    
    switch (ctxType) {
    case JSONType:
        RequestPostJSON(privateCookie, url,ctx);
        break;
    case FileType:
        RequestPostFILE(privateCookie, url,ctx);
        break;
    default:
        break;
    }
     
    qDebug() << " PostLogEvent Thread Done and wait HTTP Reply";

    this->exec(); //等待http exit 信号,并执行结束操作

}

 

做好后,在外部这样简单使用即可。无需手动delete。也请不要用deleteLater(),否则可能在不同平台触发重复析构Double Free

    
    HTTPPostLogThread* selfCleanThread = new HTTPPostLogThread();
    selfCleanThread->StartPostLogEvent(mCookies,url,ctx); //此处即可分叉到另一个线程执行http post请求
     

注意,如果线程太多,并发量太大容易触发系统资源限制(macos可能翻倍,也可能是qt bug)。可以在初始化时配置,使用下面代码。

	struct rlimit limit;
	if (getrlimit(RLIMIT_NOFILE, &limit) == 0) {
		limit.rlim_cur = INT_MAX;
		setrlimit(RLIMIT_NOFILE, &limit);
	}