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); }