Bootstrap

【Qt】系统相关——多线程、Qt多线程介绍、常用函数、线程安全、网络、UDP Socket、TCP Socket

Qt

在这里插入图片描述
  

系统相关

1. 多线程

1.1 Qt多线程介绍

  QThread 代表一个在应用程序中可以独立控制的线程,它还可以和进程中的其他线程共享数据。QThread 对象管理程序中的一个控制线程。

  

1.2 常用函数
函数 / 信号名功能描述
run()线程的入口函数,包含线程执行的代码逻辑
start()调用 run () 开始执行线程,根据优先级调度,若线程已运行则不做任何事
currentThread()返回指向管理当前执行线程的 QThread 指针
isRunning()线程正在运行则返回 true,否则返回 false
sleep()使线程休眠,单位为秒
msleep()使线程休眠,单位为毫秒
usleep()使线程休眠,单位为微秒
wait()阻塞线程,直到关联线程完成执行(从 run () 返回,若已完成或未启动则返回 true)或达到指定时间(超时返回 false,默认 ULONG_MAX 表示永远等待),类似 POSIX pthread_join () 功能
terminate()终止线程执行,是否立即终止取决于操作系统调度策略,之后建议用 wait ()
finished()线程结束时发出此信号,可用于线程清理工作

  

timethread.h

#ifndef TIMETHREAD_H
#define TIMETHREAD_H

#include <QThread>

class TimeThread : public QThread
{
    Q_OBJECT

public:
    TimeThread();

    void run(); //线程任务函数

signals:
    void sendTime(QString Time); //声明信号函数
};

#endif // TIMETHREAD_H

  

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <timethread.h> //添加头文件
#include <QLabel>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

    void on_btn_clicked();
    void showTime(QString Time);

private:
    QLabel* label;
    Ui::Widget *ui;
    TimeThread t; //定义线程对象
};
#endif // WIDGET_H

  

timethread.cpp

#include "timethread.h"

#include <QTime>
#include <QDebug>
#include <QString>

TimeThread::TimeThread() {}

void TimeThread::run()
{
    while(1)
    {
        QString time = QTime::currentTime().toString("hh:mm:ss");
        qDebug() << time;
        emit sendTime(time); //发送信号
        sleep(1);
    }
}

  

widget.cpp

#include "widget.h"
#include "ui_widget.h"

#include "timethread.h"
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include <QSpacerItem>

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

    QGridLayout* layout=new QGridLayout();

    label=new QLabel(this);
    label->setFixedSize(500,300);
    label->setFrameShape(QFrame::Box);
    label->setText("SSSSSSSSSSSSSSSSSSSS");

    QPushButton* btn=new QPushButton(this);
    btn->setFixedSize(100,50);
    btn->setText("确定");

    layout->addWidget(label,0,0,1,1);
    layout->addWidget(btn,1,0,1,1);
    this->setLayout(layout);

    connect(&t,&TimeThread::sendTime,this,&Widget::showTime);
    connect(btn,&QPushButton::clicked,this,&Widget::on_btn_clicked);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_btn_clicked()
{
    t.start(); //开启线程
}

void Widget::showTime(QString Time)
{
    label->setText(Time);
}

  

在这里插入图片描述

  
在这里插入图片描述

  

连接类型说明适用场景注意事项
Qt::AutoConnection根据信号和槽所在线程自动选择连接类型,同线程用 Qt::DirectConnection,不同线程用 Qt::QueuedConnection通用场景
Qt::DirectConnection信号发出时,槽函数立即在同线程执行信号和槽在同一线程注意线程安全性
Qt::QueuedConnection信号发出时,槽函数插入接收对象所属线程事件队列,下次事件循环执行信号和槽在不同线程确保线程安全
Qt::BlockingQueuedConnection与 Qt::QueuedConnection 类似,但发送信号线程会阻塞至槽函数执行完毕需等待槽函数执行完再继续的场景注意可能引起线程死锁
Qt::UniqueConnection可与其他连接类型位或组合使用用于保证相同信号和槽之间只有一个连接

  

1.3 线程安全

  实现线程互斥和同步常用的类有:
  • 互斥锁:QMutex、QMutexLocker
  • 条件变量:QWaitCondition
  • 信号量:QSemaphore
  • 读写锁:QReadLocker、QWriteLocker、QReadWriteLock

  

QMutex(互斥锁)

函数功能描述
QMutex()创建互斥锁。
~QMutex()销毁互斥锁。
lock()获取锁,阻塞等待。
tryLock()尝试获取锁,不阻塞。
unlock()释放锁。
recursiveLock()(递归锁)获取递归锁。
tryRecursiveLock()尝试获取递归锁。
unlockRecursive()释放递归锁。

  

QMutexLocker(互斥锁辅助类)

函数功能描述
QMutexLocker(QMutex * mutex)构造时尝试获取互斥锁。
~QMutexLocker()析构时释放互斥锁。
relock()重新获取互斥锁。
unlock()手动释放互斥锁。

  

QWaitCondition(条件变量)

函数功能描述
QWaitCondition()创建条件变量。
~QWaitCondition()销毁条件变量。
wait(QMutex * mutex)线程等待,释放互斥锁,被唤醒后重新获取。
wait(QMutex * mutex, ulong time)等待指定时间,超时返回 false。
wakeOne()唤醒一个等待线程。
wakeAll()唤醒所有等待线程。

  

QSemaphore(信号量)

函数功能描述
QSemaphore(int n = 0)创建信号量,设置初始值。
~QSemaphore()销毁信号量。
acquire(int n = 1)获取 n 个资源,阻塞等待。
tryAcquire(int n = 1)尝试获取 n 个资源,不阻塞。
release(int n = 1)释放 n 个资源。

  

QReadLocker(读写锁 - 读操作辅助类)

函数功能描述
QReadLocker(QReadWriteLock * lock)构造时获取读锁。
~QReadLocker()析构时释放读锁。
relock()重新获取读锁。
unlock()手动释放读锁。

  

QWriteLocker(读写锁 - 写操作辅助类)

函数功能描述
QWriteLocker(QReadWriteLock * lock)构造时获取写锁。
~QWriteLocker()析构时释放写锁。
relock()重新获取写锁。
unlock()手动释放写锁。

  

QReadWriteLock(读写锁)

函数功能描述
QReadWriteLock()创建读写锁。
~QReadWriteLock()销毁读写锁。
lockForRead()获取读锁,阻塞。
tryLockForRead()尝试获取读锁,不阻塞。
unlock()释放锁。
lockForWrite()获取写锁,阻塞。
tryLockForWrite()尝试获取写锁,不阻塞。

  

2. 网络

2.1 UDP Socket

QUdpSocket

名称类型说明对标原生 API
bind(const QHostAddress&, quint16)方法绑定指定的端口号bind
receiveDatagram()方法返回 QNetworkDatagram,读取一个 UDP 数据报recvfrom
writeDatagram(const QNetworkDatagram&)方法发送一个 UDP 数据报sendto
readyRead信号在收到数据并准备就绪后触发无(类似 IO 多路复用的通知机制)

  

QNetworkDatagram

名称类型说明对标原生 API
QNetworkDatagram(const QByteArray&, const QHostAddress&, quint16)构造函数通过 QByteArray、目标 IP 地址、目标端口号构造一个 UDP 数据报,通常用于发送数据时
data()方法获取数据报内部持有的数据,返回 QByteArray
senderAddress()方法获取数据报中包含的对端的 IP 地址无(recvfrom 包含该功能)
senderPort()方法获取数据报中包含的对端的端无(recvfrom 包含该功能)

  

服务端:
  
widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QUdpSocket>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

    QUdpSocket* socket;
    void processRequest();
    QString process(const QString&);

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

  

widget.cpp

#include "widget.h"
#include "ui_widget.h"

#include <QMessageBox>
#include <QUdpSocket>
#include <QNetworkDatagram>

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

    // 1. 设置窗⼝标题
    this->setWindowTitle("服务器");

    // 2. 实例化 socket
    socket = new QUdpSocket(this);

    // 3. 连接信号槽, 处理收到的请求
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);

    // 4. 绑定端⼝
    bool ret = socket->bind(QHostAddress::Any, 9090);
    if (!ret) {
        QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());
        return;
    }
}

Widget::~Widget()
{
    delete ui;
}

void Widget::processRequest()
{
    // 1. 读取请求
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data();
    // 2. 根据请求计算响应
    const QString& response = process(request);
    // 3. 把响应写回到客户端
    QNetworkDatagram responseDatagram(response.toUtf8(),
                                      requestDatagram.senderAddress(), requestDatagram.senderPort());
    socket->writeDatagram(responseDatagram);
    // 显示打印日志
    QString log = "[" + requestDatagram.senderAddress().toString() + ":" +
                  QString::number(requestDatagram.senderPort())
                  + "] req: " + request + ", resp: " + response;
    ui->listWidget->addItem(log);
}

QString Widget::process(const QString& request)
{
    return request;
}

  

客户端:
  
widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QUdpSocket>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;

    // 创建 socket 成员
    QUdpSocket* socket;
};
#endif // WIDGET_H

  

widget.cpp

#include "widget.h"
#include "ui_widget.h"

#include <QNetworkDatagram>

// 提前定义好服务器的 IP 和 端⼝
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;

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

    // 1. 设置窗口名字
    this->setWindowTitle("客户端");
    // 2. 实例化 socket
    socket = new QUdpSocket(this);

    connect(socket, &QUdpSocket::readyRead, this, [=]() {
        const QNetworkDatagram responseDatagram = socket->receiveDatagram();
        QString response = responseDatagram.data();
        ui->listWidget->addItem(QString("服务器说: ") + response);
    });
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    // 1. 获取到输⼊框的内容
    const QString& text = ui->lineEdit->text();
    // 2. 构造请求数据
    QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP),
                                     SERVER_PORT);
    // 3. 发送请求
    socket->writeDatagram(requestDatagram);
    // 4. 消息添加到列表框中
    ui->listWidget->addItem("客⼾端说: " + text);
    // 5. 清空输⼊框
    ui->lineEdit->setText("");
}


  
在这里插入图片描述

2.2 TCP Socket

QTcpServer

名称类型说明对标原生 API
listen(const QHostAddress&, quint16 port)方法绑定指定地址和端口号并开始监听bind 和 listen
nextPendingConnection()方法从系统获取已建立好的 tcp 连接,返回 QTcpSocket 用于和客户端通信accept
newConnection信号新客户端连接建立好后触发无(类似 IO 多路复用通知机制)

  

QTcpSocket

名称类型说明对标原生 API
readAll()方法读取接收缓冲区所有数据,返回 QByteArrayread
write(const QByteArray&)方法将数据写入 socketwrite
deleteLater方法标记 socket 对象无效,在下个事件循环析构释放无(类似 “半自动垃圾回收”)
readyRead信号数据到达且准备就绪时触发无(类似 IO 多路复用通知机制)
disconnected信号连接断开时触发无(类似 IO 多路复用通知机制)

            

;