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