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

文章目录

  • Qt
    • 系统相关
      • [1. 多线程](#1. 多线程)
        • [1.1 Qt多线程介绍](#1.1 Qt多线程介绍)
        • [1.2 常用函数](#1.2 常用函数)
        • [1.3 线程安全](#1.3 线程安全)
      • [2. 网络](#2. 网络)
        • [2.1 UDP Socket](#2.1 UDP Socket)
        • [2.2 TCP Socket](#2.2 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

cpp 复制代码
#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

cpp 复制代码
#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

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

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

cpp 复制代码
#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

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

cpp 复制代码
#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

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() 方法 读取接收缓冲区所有数据,返回 QByteArray read
write(const QByteArray&) 方法 将数据写入 socket write
deleteLater 方法 标记 socket 对象无效,在下个事件循环析构释放 无(类似 "半自动垃圾回收")
readyRead 信号 数据到达且准备就绪时触发 无(类似 IO 多路复用通知机制)
disconnected 信号 连接断开时触发 无(类似 IO 多路复用通知机制)

相关推荐
黑客学长-刘备2 分钟前
终于有人把网络安全就业方向一口气讲清了(非常详细)零基础入门到精通,收藏这一篇就够了
java·运维·服务器·网络·python·安全·web安全
夏天vs不热3 分钟前
Kubernetes中的网络模型:Service、Ingress、Pod通信详解
网络·容器·kubernetes
热爱生活热爱你25 分钟前
Qt5 读写共享内存,已验证,支持汉字的正确写入和读取
开发语言·qt
老菜鸟的每一天30 分钟前
Qt Modbus 2 通信实现
qt·modbus·rtu
笨笨聊运维1 小时前
linux离线安装Ollama并完成大模型配置(无网络)
linux·网络·人工智能·php
大白的编程日记.1 小时前
【C++笔记】list结构剖析及其模拟实现
c++·笔记·list
吸油泼面1 小时前
一年期免费HTTPS证书:网络安全新选择
服务器·网络·网络协议·https·ssl
变形金刚卖人寿保险还是汽车保险2 小时前
vue路由配置
网络
苏湘涵2 小时前
HTTP的初步了解
网络·网络协议·http