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())); //自动清理 ///macos并不能清理 , linux可以清理成功
}
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();
this->deleteLater(); //macos平台实际测试在此清理有效。参考https://forum.qt.io/topic/126592/should-deletelater-be-called-from-inside-qthread-run
}
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);
}