QT6.0.1 多线程+简易TCP客户端+Http请求+日志输出

QT6.0.1 多线程+简易TCP客户端+Http请求+日志输出

1.多线程 

1.1 继承QObject的多线程实现

用QObject来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer、QTcpSocket)。

1.2 创建及销毁线程

继承QObject多线程的方法线程的创建很简单,只要让QThread的start函数运行起来就行,但是需要注意销毁线程的方法 。 在线程创建之后,这个QObject的销毁不应该在主线程里进行,而是通过deleteLater槽进行安全的销毁,因此,继承QObject多线程的方法在创建时有几个槽函数需要特别关注:

  • 一个是QThread的finished信号对接QObject的deleteLater使得线程结束后,继承QObject的那个多线程类会自己销毁

  • 另一个是QThread的finished信号对接QThread自己的deleteLater,这个不是必须,下面官方例子就没这样做

使用QObject创建多线程的方法如下:

  1. 写一个继承QObject的类,对需要进行复杂耗时逻辑的入口函数声明为槽函数

  2. 此类在旧线程new出来,不能给它设置任何父对象

  3. 同时声明一个QThread对象,在官方例子里,QThread并没有new出来,这样在析构时就需要调用QThread::wait(),如果是堆分配的话, 可以通过deleteLater来让线程自杀

  4. 把obj通过moveToThread方法转移到新线程中,此时object已经是在线程中了

  5. 把线程的finished信号和object的deleteLater槽连接,这个信号槽必须连接,否则会内存泄漏

  6. 正常连接其他信号和槽(在连接信号槽之前调用moveToThread,不需要处理connect的第五个参数,否则就显示声明用Qt::QueuedConnection来连接)

  7. 初始化完后调用'QThread::start()'来启动线程

  8. 在逻辑结束后,调用QThread::quit退出线程的事件循环

使用QObject来实现多线程比用继承QThread的方法更加灵活,整个类都是在新的线程中,通过信号槽和主线程传递数据

线程类头文件:

#定义一个线程类继承Object

#ifndef THREADRUNCLIENT_H
#define THREADRUNCLIENT_H

#include <QObject>
#include <QThread>

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

   ThreadRunClient(QString ip, QString port, QString mobile, int count);//主线程构造对象的时候,可以传参数过来

   ~ThreadRunClient();

   //线程处理函数
   void runClient();//线程要执行的槽函数

signals:
   void message(const QString& info);//用信号传递相关信息给主线程那边
};

cpp:

#include "threadrunclient.h"

ThreadRunClient::ThreadRunClient(QObject *parent) : QObject(parent)
{

}

ThreadRunClient::ThreadRunClient(QString ip, QString port, QString mobile,int count)
  :m_ip(ip)
  ,m_port(port)
  ,m_mobile(mobile)
  ,m_count(count)
  ,m_failcount(0)
  ,m_PollingTime(1000*60*5)
{
 
}

ThreadRunClient::~ThreadRunClient()
{

}

void ThreadRunClient::runClient()
{
   //这里写上业务逻辑代码,也就是你线程要干的事情
   
   
   //如果需要传递相关信息给主线程,就发送信号
   emit message("xxxxxxx");
}

主线程代码:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "threadrunclient.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
   Q_OBJECT

public:
   MainWindow(QWidget *parent = nullptr);
   ~MainWindow();

private slots:
   void on_start_bt_clicked();//这个是我这里的启动线程的按钮

   void showText(const QString& info);//关联线程类的message信号的函数,取到那边传过来的数据

signals:
   void runClientThread();


private:
   Ui::MainWindow *ui;

   ThreadRunClient *m_clientThread;//定义线程类对象

   QThread *m_pthread;//定义线程对象
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QThread>

MainWindow::MainWindow(QWidget *parent)
  : QMainWindow(parent)
  , ui(new Ui::MainWindow)
{
   ui->setupUi(this);
}

MainWindow::~MainWindow()
{

   if(m_pthread)
  {
       m_pthread->quit();
  }

   m_pthread->wait();

   delete ui;
}

//启动
void MainWindow::on_start_bt_clicked()
{
   //启动单个线程
   m_clientThread = new ThreadRunClient(ip, port, mobile, count);

   m_pthread = new QThread();

   m_clientThread->moveToThread(m_pthread);
   connect(m_pthread,&QThread::finished,m_pthread,&QObject::deleteLater);//线程结束后,销毁线程
   connect(m_pthread,&QThread::finished,m_clientThread,&QObject::deleteLater);//同上
   connect(this,&MainWindow::runClientThread,m_clientThread,&ThreadRunClient::runClient);//关系线程类的槽函数
   connect(m_clientThread,&ThreadRunClient::message,this,&MainWindow::showText);//关联线程类的信号和自身的槽函数
   m_pthread->start();//启动子线程

   //启动多个线程
   //   for (int i = 0; i < 6; ++i) {
//       m_clientThread = new ThreadRunClient(ip, port, mobile, count);

//       m_pthread = new QThread();

//       m_clientThread->moveToThread(m_pthread);
//       connect(m_pthread,&QThread::finished,m_pthread,&QObject::deleteLater);
//       connect(m_pthread,&QThread::finished,m_clientThread,&QObject::deleteLater);
//       connect(this,&MainWindow::runClientThread,m_clientThread,&ThreadRunClient::runClient);
//       connect(m_clientThread,&ThreadRunClient::message,this,&MainWindow::showText);
//       m_pthread->start();
//   }

   emit runClientThread();//主线程通过信号唤起子线程的槽函数 (如果是启动多个线程,也是要括号外面这才发生信号)
}

//如果线程类那边触发了message信号,这边就会接收到相关数据
void MainWindow::showText(const QString& info)
{
   qDebug()<<info;
}

参考:https://blog.csdn.net/czyt1988/article/details/71194457

 

2.简易TCP客户端

创建客户端很简单,添加头文件#include <QTcpSocket>和#include <QHostAddress>后,一般有以下步骤:

  1. 创建socket

    clientSocket = new QTcpSocket(this);
  2. 连接服务器

    clientSocket->connectToHost(QHostAddress(ip), port);
  3. 发送数据给服务器

    clientSocket->write(data);
  4. 读取服务器发过来的数据

    QByteArray buffer = clientSocket->readAll();
    //有数据可读的,会触发readRead()信号,可关联该信号进行数据读取
    connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
       QByteArray buffer = clientSocket->readAll();
    });
  5. 断开与服务器的连接

    clientSocket->disconnectFromHost();

     

    关于如何判断连接服务器的时候,是否连接成功。有三种方法:

    1. 主动调用waitForConnected(),设置时间,等待直到建立socket连接,超时就认为连接失败,但实际可能socket可能一直处在ConnectingState状态。如下例子:

      if(!clientSocket->waitForConnected(3000))//等待3秒
      {
      	//连接失败
      }
      else
      {
          //连接成功
      }
    2. 通过connected的信号触发判断,connectToHost()成功连接到服务器后,会发射此信号,推荐这种。参考如下:

      connect(clientSocket, &QTcpSocket::connected, this, [=](){
      	qDebug()<<"连接服务器成功";
      });
    3. 通过stateChanged(QAbstractSocket::SocketState SocketState )信号判断,当socket的状态变化时,发射此信号,参数SocketState 表示了socket当前的状态。当为ConnectedState,表示连接成功。监控socket状态示例:

      //状态变化
      connect(m_TcpSocket, &QTcpSocket::stateChanged, this, [=](QAbstractSocket::SocketState socketstate){
              switch(socketstate)
              {
              case QAbstractSocket::UnconnectedState:
                  qDebug()<< "The socket is not connected.";
                  break;
              case QAbstractSocket::HostLookupState:
                  qDebug()<< "The socket is performing a host name lookup.";
                  break;
              case QAbstractSocket::ConnectingState:
                  qDebug()<< "The socket has started establishing a connection.";
                  break;
              case QAbstractSocket::ConnectedState:
                  qDebug()<< "A connection is established.";
                  break;
              case QAbstractSocket::BoundState:
                  qDebug()<< "The socket is bound to an address and port.";
                  break;
              case QAbstractSocket::ClosingState:
                  qDebug()<< "The socket is about to close (data may still be waiting to be written).";
                  break;
              default:
                  break;
              }
      });

    关于如何判断连接服务器的时候,是否连接失败,有两种方法:

    1. 第一种同成功的第一种方法判断

    2. 使用errorOccurred信号判断(我用的是QT6.0.1),信号列表如下图

    我使用的判断是

    connect(m_TcpSocket, &QTcpSocket::errorOccurred, this, [=](QAbstractSocket::SocketError error){
            switch(error)
            {
            case QAbstractSocket::NetworkError:
            case QAbstractSocket::ConnectionRefusedError:
            case QAbstractSocket::HostNotFoundError:
            case QAbstractSocket::SocketAccessError:
            case QAbstractSocket::SocketTimeoutError:
                qDebug()<< "连接服务器失败";
                break;
            default:
                break;
            }
    });

    注意事项,其他版本我不是很清楚,在QT6.0.1中,要使用该信号,需要注册QAbstractSocket。官方说明如下 

我的使用:

//用qRegisterMetaType注册QAbstractSocket,如果需要用到QTcpSocket::errorOccurred,QAbstractSocket::SocketError
//Q_DECLARE_METATYPE(QAbstractSocket::SocketError);  //这个在我这里用不到,因为QT已经检测到QAbstractSocket已声明
qRegisterMetaType<QAbstractSocket>();

3.Http请求

  1. get请求

    //URL
    QString uri = QString("https://www.baidu.com/helloworld?hahaha=0&xixixi=1");
    
    //构造请求
    QNetworkRequest request;
    request.setUrl(QUrl(uri));
    
    QNetworkAccessManager *manager = new QNetworkAccessManager(this);
    //发送get请求
    QNetworkReply *pReplay = manager->get(request);
    connect(manager,&QNetworkAccessManager::finished,this,[=](){
    	// 获取响应信息
        QByteArray bytes = pReplay->readAll();
    	qDebug() << bytes;
    }
  2. post请求

    // URL
    QString baseUrl = "https://www.baidu.com/helloworld";
    QUrl url(baseUrl);
    
    // 表单数据
    QByteArray dataArray;
    dataArray.append("hahaha=0&");
    dataArray.append("xixixi=1");
    
    // 构造请求
    QNetworkRequest request;
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
    request.setUrl(url);
    
    QNetworkAccessManager *manager = new QNetworkAccessManager(this);
    
    //发送post请求
    QNetworkReply *pReplay = manager->post(request, dataArray);
    
    connect(manager,&QNetworkAccessManager::finished,this,[=](){
    	//获取响应信息
    	QByteArray bytes = pReplay->readAll();
    	qDebug() << bytes;
    
    });

4.日志输出

使用qInstallMessageHandler(重定向至文件)

//自己自定义一个日志函数

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    // 加锁
   static QMutex mutex;
   mutex.lock();

   QByteArray localMsg = msg.toUtf8();//这里建议转成utf-8格式,同时在QT上设置生成的文件默认是utf8

   QString strMsg("");
   switch(type)
  {
   case QtDebugMsg:
       strMsg = QString("Debug:");
       break;
   case QtInfoMsg:
       strMsg = QString("Info:");
       break;
   case QtWarningMsg:
       strMsg = QString("Warning:");
       break;
   case QtCriticalMsg:
       strMsg = QString("Critical:");
       break;
   case QtFatalMsg:
       strMsg = QString("Fatal:");
       break;
  }

   // 设置输出信息格式
   QString strDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
//   QString strMessage = QString("Message:%1 File:%2 Line:%3 Function:%4 DateTime:%5")
//           .arg(localMsg.constData()).arg(context.file).arg(context.line).arg(context.function).arg(strDateTime);

   QString strMessage = QString("Message:%1")
          .arg(localMsg.constData());

   QString runPth = QDir::currentPath();
   runPth += "/logs";
   QDir dir(runPth);
   if(!dir.exists())//判断文件夹是否存在,不存在创建文件夹
  {
       dir.mkdir(runPth);
  }

   // 输出信息至文件中(读写、追加形式)
   QString namefile = QString("./logs/%1_%2.log").arg("log").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd"));
   QFile file(namefile);
   file.open(QIODevice::ReadWrite | QIODevice::Append);
   QTextStream stream(&file);
   stream << strMessage << "\r\n";
   file.flush();
   file.close();

   // 解锁
   mutex.unlock();
}
//使用,我的场景使用,是用一次恢复一次,不恢复的,注册后就一直把调试信息输出到文件
//注册消息处理程序
qInstallMessageHandler(myMessageOutput);

qInfo()<<info;

//恢复消息处理程序
qInstallMessageHandler(0);


参考:https://blog.csdn.net/liang19890820/article/details/51838379

 

posted @ 2021-03-09 23:11  DarkH  阅读(1949)  评论(0编辑  收藏  举报