Qt 文件操作+多线程+网络

news2025/3/4 9:15:39

文章目录

  • 1. 文件操作
    • 1.1 API
    • 1.2 例子1,简单记事本
    • 1.3 例子2,输出文件的属性
  • 2. Qt 多线程
    • 2.1 常用API
    • 2.2 例子1,自定义定时器
  • 3. 线程安全
    • 3.1 互斥锁
    • 3.2 条件变量
  • 4. 网络编程
    • 4.1 UDP Socket
    • 4.2 UDP Server
    • 4.3 UDP Client
    • 4.4 TCP Socket
    • 4.5 TCP Server
    • 4.6 TCP Client
    • 4.7 HTTP API
    • 4.8 HTTP Client
  • 5. 播放音频

1. 文件操作

继承关系图如下

image-20250218174016898

1.1 API

简单介绍一下文件操作的方法

  • QFile构造:QFile::QFile(const QString &name),通过给定的路径构造

  • 打开文件:使用 [override virtual] bool QFile::open(QIODeviceBase::OpenMode mode) 方法来打开文件。文件可以是文本文件或二进制文件。

    • QIODevice::ReadOnly:以只读模式打开文件。
    • QIODevice::WriteOnly:以只写模式打开文件。
    • QIODevice::ReadWrite:以读写模式打开文件。
  • 读取文件QByteArray QIODevice::readAll()来读取所有的文件内容

  • 编写文件qint64 QIODevice::write(const QByteArray &data)向文件写内容

  • 关闭文件[virtual] void QIODevice::close()来关闭文件

1.2 例子1,简单记事本

下面是一个例子,实现了记事本的两个功能:

#include "mainwindow.h"
#include <QFile>
#include <QFileDialog>
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget* parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setWindowTitle("记事本Demo");
    // 添加菜单栏
    QMenuBar* menuBar = this->menuBar();
    // 添加菜单
    QMenu* menu = new QMenu("文件");
    menuBar->addMenu(menu);
    // 添加菜单项
    QAction* action_read = new QAction("读取");
    QAction* action_save = new QAction("保存");
    menu->addAction(action_read);
    menu->addAction(action_save);
    // 设置文本输入框
    edit = new QPlainTextEdit(this);
    QFont font;
    font.setPixelSize(18);
    edit->setFont(font); // 设置字体大小
    this->setCentralWidget(edit);
    // 设置槽函数
    this->connect(action_read, &QAction::triggered, this, &MainWindow::handle_read_file);
    this->connect(action_save, &QAction::triggered, this, &MainWindow::handle_save_file);
}

void MainWindow::handle_save_file()
{
    // 通过对话打开文件
    QString path = QFileDialog::getSaveFileName(this, "保存文件");
    // 构建QFile对象
    QFile file = QFile(path);
    // 打开文件
    bool ret = file.open(QIODevice::WriteOnly);
    if (!ret) {
        qDebug() << "handle_save_file, 打开文件失败!";
        return;
    }
    // 向文件中写数据
    QString text = edit->toPlainText();
    file.write(text.toUtf8());
    // 显示到状态栏中
    QStatusBar* statusBar = this->statusBar();
    statusBar->showMessage(path + QString(" 写入成功!"), 10000);
    // 关闭文件
    file.close();
}

void MainWindow::handle_read_file()
{
    QString path = QFileDialog::getOpenFileName(this, "读取文件");
    QFile   file = QFile(path);
    bool    ret  = file.open(QIODevice::ReadOnly);
    if (!ret) {
        qDebug() << "handle_read_file, 打开文件失败!";
        return;
    }
    // QByteArray可以转换成QString
    QString text = file.readAll();
    edit->setPlainText(text);
    QStatusBar* statusBar = this->statusBar();
    statusBar->showMessage(path + QString(" 读取成功!"), 10000);
    file.close();
}

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

1.3 例子2,输出文件的属性

使用QFileInfo ,该类用于获取文件或目录的详细信息,例如文件路径、大小、创建时间、修改时间等。下面是一个例子

void Widget::on_pushButton_clicked()
{
    QString path = QFileDialog::getOpenFileName(this);
    QFileInfo info(path);
    qDebug() << "File path:" << info.filePath();
    qDebug() << "File name:" << info.fileName();
    qDebug() << "Base name:" << info.baseName();
    qDebug() << "Suffix:" << info.suffix();
    qDebug() << "Size:" << info.size() << "bytes";
    qDebug() << "Exists:" << info.exists();
    qDebug() << "Is file:" << info.isFile();
    qDebug() << "Is directory:" << info.isDir();
    qDebug() << "Last modified:" << info.lastModified().toString("yyyy/MM/dd hh:mm:ss");
}

运行结果如下

image-20250218203200549

2. Qt 多线程

使用QThread

2.1 常用API

API接口描述
run()线程的入口函数。开发者需要重写此函数来定义线程执行的任务。
start()通过调用 run() 函数开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行,则此方法不执行任何操作。
currentThread()返回一个指向管理当前执行线程的 QThread 的指针。
isRunning()如果线程正在运行则返回 true;否则返回 false
sleep() / msleep() / usleep()使线程休眠,单位分别为秒、毫秒、微秒。这些函数允许线程暂停执行指定的时间。
wait()阻塞调用它的线程,直到与此 QThread 对象关联的线程完成执行(即从 run() 返回),或者等待时间已过(如果指定了等待时间)。如果线程已完成或尚未启动,则返回 true;如果等待超时,则返回 false
terminate()尝试立即终止线程的执行。但请注意,由于操作系统的调度策略,线程可能不会立即终止。在调用 terminate() 后,应使用 QThread::wait() 来确保线程已真正停止。然而,通常不推荐使用 terminate(),因为它可能会导致资源泄露或其他不可预知的行为。
finished()当线程结束时会发出此信号。可以通过连接此信号来执行清理工作或其他必要的操作。
isFinished() const判断线程中的任务是否处理完毕。
priority() const得到当前线程的优先级。
setPriority(Priority priority)设置线程的优先级。
exit(int returnCode = 0)退出线程,停止底层的事件循环。
quit()退出线程的事件循环,与调用 exit() 效果相同。

创建线程的步骤

  1. 自定义一个类,继承于 QThread,并且只有一个线程处理函数(和主线程不是同一个线程),这个线程处理函数主要就是重写父类中的run()函数。
  2. 线程处理函数里面写入需要执行的复杂数据处理
  3. 启动线程不能直接调用 run()函数,需要使用对象来调用 start()函数实现线程启动
  4. 线程处理函数执行结束后可以定义一个信号来告诉主线程
  5. 最后关闭线程

2.2 例子1,自定义定时器

不使用QTimer,实现定时效果,首先定义一个Thread类继承自QThread

/* thread.h */
#ifndef THREAD_H
#define THREAD_H

#include <QThread>
#include <QWidget>

class Thread : public QThread
{
    Q_OBJECT
public:
    Thread();
    virtual void run();
signals:
    void timeout(); // 自定义信号,每1s发送1次, 一共发送10次
};

#endif // THREAD_H

/* thread.cpp */
#include "thread.h"


Thread::Thread()
{
}

void Thread::run()
{
    for (int i = 1; i <= 10; ++i) {
        sleep(1);
        emit timeout();
    }
}

widget.ui中拖入一个QLcdNumber,下面是Widget类的代码

/* widget.h */
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "thread.h"

QT_BEGIN_NAMESPACE
namespace Ui
{
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr);
    void handlerTime();
    ~Widget();
private:
    Ui::Widget* ui;
    Thread timer;
};
#endif // WIDGET_H

/* widget.cpp */
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    ui->lcdNumber->display(10);
    connect(&timer, &Thread::timeout, this, &Widget::handlerTime); // 连接信号槽
    timer.start();                                                 // 启动该线程
}

void Widget::handlerTime()
{
    int val = ui->lcdNumber->intValue();
    ui->lcdNumber->display(--val);
}

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

3. 线程安全

3.1 互斥锁

使用QMutex,下面是一个例子


不加互斥锁,让两个线程++同一个变量

/* thread.h */
#ifndef THREAD_H
#define THREAD_H

#include <QThread>
#include <QWidget>

class Thread : public QThread
{
    Q_OBJECT
public:
    Thread();
    virtual void run();
    static int num;
};

#endif // THREAD_H

/* thread.cpp */
#include "thread.h"

int Thread::num = 0;

Thread::Thread()
{
}

void Thread::run()
{
    for (int i = 1; i <= 50000; ++i) {
        num++;
    }
}

Widget中创建这两个线程,widget.cpp如下

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

Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    Thread t1, t2;
    t1.start();
    t2.start();
    // 等待t1, t2执行完
    t1.wait();
    t2.wait();
    qDebug() << Thread::num;
}

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

由于++操作并不是原子的,所以结果可能并不全是100000,要想结果一致,需要加互斥锁,修改thread.cpp

#include "thread.h"

int    Thread::num   = 0;
QMutex Thread::mutex = QMutex();

Thread::Thread()
{
}

void Thread::run()
{
    for (int i = 1; i <= 50000; ++i) {
        mutex.lock();
        num++;
        mutex.unlock();
    }
}

这样,结果就能稳定了


RAII风格的锁,使用[explicit noexcept] QMutexLocker::QMutexLocker(Mutex *mutex),将thread.cpp中的代码改为

#include "thread.h"

int    Thread::num   = 0;
QMutex Thread::mutex = QMutex();

Thread::Thread()
{
}

void Thread::run()
{
    for (int i = 1; i <= 50000; ++i) {
        QMutexLocker locker(&mutex);
        num++;
    }
}

仍能起到同样的效果

3.2 条件变量

使用QWaitCondition类,下面是一个例子,仅仅作为演示

/* thread.cpp */
#include "thread.h"

QMutex         Thread::mutex    = QMutex();
QWaitCondition Thread::condition = QWaitCondition();
int            Thread::_cnt      = 0;

Thread::Thread(int num)
    : _num(num) {};

void Thread::run()
{
    qDebug() << "Thread-" << _num << "created done.";
    for (;;) {
        QMutexLocker locker(&Thread::mutex);
        // 阻塞当前线程,等待别的线程使用notify_one()或wakeAll()来唤醒它。
        condition.wait(&mutex);
        _cnt++;
        printf("Thread-%d, cnt: %d\n", _num, _cnt);
        condition.notify_one();
    }
}
/* widget.cpp */
#include "widget.h"
#include <Windows.h>
#include "thread.h"
#include "ui_widget.h"

Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    Thread t1(1);
    Thread t2(2);
    Thread t3(3);
    t1.start();
    t2.start();
    t3.start();
    QThread::msleep(1000);
    qDebug() << "Main thread start control.";
    for(;;) {
        QMutexLocker locker(&Thread::mutex);
        Thread::condition.notify_one(); // 唤醒等待队列中等待的一个线程, 默认是第一个
    }
}

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

运行后会发现,线程以1,2,3的顺序一直在运行

4. 网络编程

4.1 UDP Socket

QUdpSocket表示一个UDP的socket文件

API 接口类型说明对标原生 API
bind(const QHostAddress&, quint16)方法绑定指定的本地地址和端口号,准备接收数据报bind
receiveDatagram()方法接收一个 UDP 数据报并返回 QNetworkDatagram 对象,包含数据报的内容和发送方信息recvfrom
writeDatagram(const QNetworkDatagram&)方法发送一个 UDP 数据报,包含目标地址和端口号sendto
readyRead信号当有新的数据报到达并准备好读取时触发,通知应用程序可以读取数据无(类似于 I/O 多路复用的通知机制)

QNetworkDatagram表示一个UDP数据报

方法/构造函数类型说明对标原生 API
QNetworkDatagram(const QByteArray&, const QHostAddress& ip, quint16 port)构造函数通过 QByteArray 数据、目标 IP 地址和目标端口号构造一个 UDP 数据报。通常用于发送数据时封装数据报内容。
data()方法获取数据报内部持有的数据,返回 QByteArray 类型,包含数据报的原始字节数据。无(在网络编程中,原生 API 通常通过读取缓冲区获得数据)
senderAddress()方法获取数据报中包含的对端的 IP 地址,返回 QHostAddress,表示发送该数据报的远端主机地址。无,但在原生 UDP 编程中,recvfrom 函数包含了获取发送方地址的功能。
senderPort()方法获取数据报中包含的对端的端口号,返回 quint16 类型,表示发送该数据报的远端主机的端口号。无,但在原生 UDP 编程中,recvfrom 函数包含了获取发送方端口号的功能。

4.2 UDP Server

下面是一个UDP回显服务器

首先,要在.pro文件中加上network模块

QT       += core gui network

widget.ui中铺上一个QListWidget

下面是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);
    void handlerRequest();
    QString resolutionRequest(const QString& request);
    ~Widget();

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

下面是wigdet.cpp的代码

#include "widget.h"
#include <QMessageBox>
#include <QNetworkDatagram>
#include "ui_widget.h"

Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    socket = new QUdpSocket(this);
    this->setWindowTitle("服务端");
    // 连接信号槽,用于处理来自客户端的请求
    this->connect(socket, &QUdpSocket::readyRead, this, &Widget::handlerRequest);
    // 绑定端口号和IP(该套接字将会监听所有本地网络接口)
    bool ret = socket->bind(QHostAddress::Any, 9000);
    if (!ret) {
        QMessageBox::critical(this, "绑定出错!", socket->errorString());
        return;
    }
    qDebug() << "绑定端口和IP成功";
}

void Widget::handlerRequest()
{
    // 读取请求
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString                 requet          = requestDatagram.data();
    // 获取客户端的IP和端口号
    QHostAddress peerIp   = requestDatagram.senderAddress();
    qint16       peerPort = requestDatagram.senderPort();
    // 解析请求, 得到响应
    QString response = resolutionRequest(requet);
    // 构建数据包,将数据发送给客户端
    QNetworkDatagram sendDatagram = QNetworkDatagram(response.toUtf8(), peerIp, peerPort);
    socket->writeDatagram(sendDatagram);
    // 自己这里要显示数据
    QString log = "[" + peerIp.toString() + ":" + QString::number(peerPort) + "] request: " +
                  requet + " response: " + response;
    ui->listWidget->addItem(log);
}

QString Widget::resolutionRequest(const QString& request)
{
    // 用于解析请求, 这里仅做简单的字符串处理(将字符串逆转)
    QString res;
    for (int i = request.size() - 1; i >= 0; --i) {
        res += request[i];
    }
    return res;
}

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

4.3 UDP Client

wiget.ui中设置基本框架

image-20250222220256639

下面是widget.h的代码

#ifndef WIDGET_H
#define WIDGET_H

#include <QUdpSocket>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui
{
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

private:
    const static QHostAddress SERVER_IP;
    const static qint16       SERVER_PORT;
    QUdpSocket*               socket;
    Ui::Widget*               ui;
};
#endif // WIDGET_H

下面是widget.cpp的代码

#include "widget.h"
#include <QNetworkDatagram>
#include <QShortcut>
#include "ui_widget.h"

const QHostAddress Widget::SERVER_IP   = QHostAddress("127.0.0.1");
const qint16       Widget::SERVER_PORT = 9000;

Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    socket = new QUdpSocket(this);
    this->setWindowTitle("客户端");
    this->connect(ui->lineEdit, &QLineEdit::editingFinished, this, &Widget::on_pushButton_clicked); // 按回车发送
    this->connect(socket, &QUdpSocket::readyRead, this, &Widget::handlerResponse);
    qDebug() << "连接服务端成功!";
}

void Widget::handlerResponse()
{
    QNetworkDatagram responseDatagram = socket->receiveDatagram();              // 接受数据报
    ui->listWidget->addItem(QString("Server say: ") + responseDatagram.data()); // 显示
}

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

void Widget::on_pushButton_clicked()
{
    const QString& text = ui->lineEdit->text();
    if (text == "") {
        qDebug() << "LineEdit Empty";
        return;
    }
    QNetworkDatagram requestDatagram(text.toUtf8(), SERVER_IP, SERVER_PORT); // 构建数据报
    socket->writeDatagram(requestDatagram);                                  // 发送数据报
    ui->listWidget->addItem(QString("Client say: ") + text);                 // 显示
    ui->lineEdit->setText("");                                               // 清空
}

同时运行客户端与服务端,结果如下

image-20250222220455394

4.4 TCP Socket

QTcpServer用于监听端口和获取客户端连接

名称类型说明对标原生 API
listen(const QHostAddress&, quint16 port)方法绑定指定的地址和端口号,并开始监听。bind()和listen()
nextPendingConnection()方法从系统中获取一个已经建立好的 TCP 连接。返回一个 QTcpSocket,表示这个客户端的连接。通过这个 socket 对象完成和客户端之间的通信。accept()
newConnection信号有新的客户端建立连接后触发。类似于 IO 多路复用中的通知机制。无(但类似于 IO 多路复用中的通知机制)

QTcpSocket用于客户端和服务器之间的数据交互

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

4.5 TCP Server

下面是一个TCP回显服务器,不要忘记在pro文件中加上network,首先在wigdet.ui中添加一个QListWidget

wiget.h如下

#ifndef WIDGET_H
#define WIDGET_H

#include <QTcpSocket>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui
{
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

private:
    const static QHostAddress SERVER_IP;
    const static qint16       SERVER_PORT;
    Ui::Widget*               ui;
    QTcpSocket*               socket;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include <QMessageBox>
#include "ui_widget.h"

const QHostAddress Widget::SERVER_IP   = QHostAddress("127.0.0.1");
const qint16       Widget::SERVER_PORT = 9000;

Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("客户端");
    socket = new QTcpSocket(this);
    socket->connectToHost(Widget::SERVER_IP, Widget::SERVER_PORT); // 连接客户端,这是一个非阻塞的函数
    bool ret = socket->waitForConnected();                         // 等待连接服务器, 若ret为0表示三次握手成功
    if (!ret) {
        QMessageBox::critical(this, "等待连接失败", socket->errorString());
        return;
    }
    this->connect(ui->lineEdit, &QLineEdit::editingFinished, this, &Widget::handleResponse); // 按回车发送
    this->connect(socket, &QTcpSocket::readyRead, this, &Widget::handleResponse);            // 设置信号槽, 当有数据来时执行
}

void Widget::handleResponse()
{
    QString text = socket->readAll();                        // 读取
    ui->listWidget->addItem(QString("Server say: ") + text); // 显示
}

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

void Widget::on_pushButton_clicked()
{
    QString text = ui->lineEdit->text();
    if (text == "") {
        qDebug() << "Text empty!";
        return;
    }
    ui->listWidget->addItem(QString("Client say: ") + text); // 显示
    socket->write(text.toUtf8());                            // 写给客户端
    ui->lineEdit->setText("");                               // 清空
}

4.6 TCP Client

widget.ui如下

image-20250223200921286

wigdet.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QTcpSocket>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui
{
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

private:
    const static QHostAddress SERVER_IP;
    const static qint16       SERVER_PORT;
    Ui::Widget*               ui;
    QTcpSocket*               socket;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include <QMessageBox>
#include "ui_widget.h"

const QHostAddress Widget::SERVER_IP   = QHostAddress("127.0.0.1");
const qint16       Widget::SERVER_PORT = 9000;

Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("客户端");
    socket = new QTcpSocket(this);
    socket->connectToHost(Widget::SERVER_IP, Widget::SERVER_PORT); // 连接客户端,这是一个非阻塞的函数
    bool ret = socket->waitForConnected();                         // 等待连接服务器, 若ret为0表示三次握手成功
    if (!ret) {
        QMessageBox::critical(this, "等待连接失败", socket->errorString());
        return;
    }
    this->connect(ui->lineEdit, &QLineEdit::editingFinished, this, &Widget::on_pushButton_clicked); // 按回车发送
    this->connect(socket, &QTcpSocket::readyRead, this, &Widget::handleResponse);                   // 设置信号槽, 当有数据来时执行
}

void Widget::handleResponse()
{
    QString text = socket->readAll();                        // 读取
    ui->listWidget->addItem(QString("Server say: ") + text); // 显示
}

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

void Widget::on_pushButton_clicked()
{
    QString text = ui->lineEdit->text();
    if (text == "") {
        qDebug() << "Text empty!";
        return;
    }
    ui->listWidget->addItem(QString("Client say: ") + text); // 显示
    socket->write(text.toUtf8());                            // 写给客户端
    ui->lineEdit->setText("");                               // 清空
}

运行结果如下

image-20250223201219842

4.7 HTTP API

QNetworkAccessManager提供了HTTP的核心操作

方法说明
get(const QNetworkRequest&)发起一个 HTTP GET 请求。返回 QNetworkReply 对象。
post(const QNetworkRequest&, const QByteArray&)发起一个 HTTP POST 请求。返回 QNetworkReply 对象。

QNetworkRequest表示一个HTTP请求(不含body)

如果需要发送一个带有 body 的请求(比如 post),会在 QNetworkAccessManagerpost 方法中通过单独的参数来传入 body

方法说明
QNetworkRequest(const QUrl& )通过 URL 构造一个 HTTP 请求。
setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value)设置请求头。

QNetworkRequest::KnownHeaders是一个枚举请求,下面是常用类型

取值说明
ContentTypeHeader描述 body 的类型。
ContentLengthHeader描述 body 的长度。
LocationHeader用于重定向报文中指定重定向地址(响应中使用,请求用不到)。
CookieHeader设置 cookie
UserAgentHeader设置 User-Agent

QNetworkReply表示一个HTTP响应

方法说明
error()获取出错状态。
errorString()获取出错原因的文本。
readAll()读取响应 body。
header(QNetworkRequest::KnownHeaders header)读取响应指定 header 的值。

QNetworkReply还有一个信号finished会在客户端收到完整的响应数据触发

在读取完后调用deleteLater()来释放该响应

4.8 HTTP Client

下面是一个简单的HTTP Client

widget.ui中设置基本框架

image-20250223233224665

widget.cpp如下

#include "widget.h"
#include <QMessageBox>
#include <QNetworkReply>
#include "ui_widget.h"

Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("HTTP客户端");
    manager = new QNetworkAccessManager(this);
    this->connect(ui->lineEdit, &QLineEdit::editingFinished, this, &Widget::on_pushButton_clicked);
}

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

void Widget::on_pushButton_clicked()
{
    QString text = ui->lineEdit->text();
    if (text.isEmpty()) {
        qDebug() << "Text Empty!";
        QMessageBox::warning(this, "警告", "输入框中没有内容!");
        return;
    }
    // 构建请求
    QUrl            url(text);
    QNetworkRequest request(url);
    // 发送请求
    QNetworkReply* response = manager->get(request);
    if (response->error() == QNetworkReply::NoError) {
        // 没有错误
        connect(response, &QNetworkReply::finished, this, [=]() { // 当在客户端收到完整的响应数据触发
            qDebug() << "读取到了数据";
            QString resultHtml = response->readAll();             // 读取数据
            ui->extEdit->setPlainText(resultHtml);
        });
    } else {
        // 有错误
        QString errorStr = response->errorString();
        qDebug() << errorStr;
        QMessageBox::warning(this, "警告", errorStr);
    }
}

运行结果如下

image-20250223233337393

5. 播放音频

使用QSoundEffect类,下面是一个例子

首先需要在pro文件中加上multimedia模块

widget.cpp如下

#include "widget.h"
#include <error.h>
#include <QFile>
#include "ui_widget.h"

Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    sound            = new QSoundEffect(this);
    // QString filePath = "D:/bit/QT/QTPro/25_2_24QSound_1/666.wav"; // 使用正斜杠
    QString filePath = ":/sound/666.wav";

    if (!QFile::exists(filePath)) {
        qDebug() << "音频文件不存在!";
        return;
    }

    // 正确转换为QUrl(必须使用QUrl,使用QSting会加载失败
    const QUrl path = QUrl::fromLocalFile(filePath);
    // const QUrl path = QUrl(filePath); err
    sound->setSource(path);
    sound->setLoopCount(QSoundEffect::Infinite);

    connect(sound, &QSoundEffect::statusChanged, this, [this]() {
        if (sound->status() == QSoundEffect::Ready) {
            qDebug() << "音频加载成功!";
        } else if (sound->status() == QSoundEffect::Error) {
            qDebug() << "音频加载失败!原因: 文件格式不支持或路径错误";
        }
    });
}
Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    sound->play();
}


void Widget::on_pushButton_2_clicked()
{
    sound->stop();
}

点击按钮1播放音频,按钮2暂停音频

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2309417.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《基于Hadoop的青岛市旅游景点游客行为分析系统设计与实现》开题报告

目录 一、选题依据 1.选题背景 2.国内外研究现状 &#xff08;1&#xff09;国内研究现状 &#xff08;2&#xff09;国外研究现状 3.发展趋势 4.应用价值 二、研究内容 1.学术构想与思路 2. 拟解决的关键问题 3. 拟采取的研究方法 4. 技术路线 (1)旅游前准备阶段 …

pycharm debug卡住

pycharm debug时一直出现 collecting data, 然后点击下一行就卡住。 勾选 Gevent compatible解决 https://stackoverflow.com/questions/39371676/debugger-times-out-at-collecting-data

ISP 常见流程

1.sensor输出&#xff1a;一般为raw-OBpedestal。加pedestal避免减OB出现负值&#xff0c;同时保证信号超过ADC最小电压阈值&#xff0c;使信号落在ADC正常工作范围。 2. pedestal correction&#xff1a;移除sensor加的基底&#xff0c;确保后续处理信号起点正确。 3. Linea…

java数据结构_Map和Set(一文理解哈希表)_9.3

目录 5. 哈希表 5.1 概念 5.2 冲突-概念 5.3 冲突-避免 5.4 冲突-避免-哈希函数的设计 5.5 冲突-避免-负载因子调节 5.6 冲突-解决 5.7 冲突-解决-闭散列 5.8 冲突-解决-开散列 / 哈希桶 5.9 冲突严重时的解决办法 5. 哈希表 5.1 概念 顺序结构以及平衡树中&#x…

基于SpringBoot的“数据驱动的资产管理系统站”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“数据驱动的资产管理系统站”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 局部E-R图 系统登录界…

excel 斜向拆分单元格

右键-合并单元格 右键-设置单元格格式-边框 在设置好分割线后&#xff0c;你可以开始输入文字。 需要注意的是&#xff0c;文字并不会自动分成上下两行。 为了达到你期望的效果&#xff0c;你可以通过 同过左对齐、上对齐 空格键或使用【AltEnter】组合键来调整单元格中内容的…

深入理解推理语言模型(RLM)

大语言模型从通用走向推理&#xff0c;万字长文解析推理语言模型&#xff0c;建议收藏后食用。 本文基于苏黎世联邦理工学院的论文《Reasoning Language Models: A Blueprint》进行整理&#xff0c;你将会了解到&#xff1a; 1、RLM的演进与基础&#xff1a;RLM融合LLM的知识广…

2025年具有百度特色的软件测试面试题

百度业务场景 如何测试一个高并发的搜索系统(如百度搜索)?如何测试一个在线地图服务(如百度地图)?如何测试一个大型推荐系统(如百度推荐)的性能?百度技术栈 你对百度的 PaddlePaddle 框架有了解吗?如何测试基于 PaddlePaddle 的服务?如何测试百度云的 API 服务?你对…

Lua | 每日一练 (5)

&#x1f4a2;欢迎来到张胤尘的技术站 &#x1f4a5;技术如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 Lua | 每日一练 (5)题目参考答案浅拷贝深拷贝使用场景…

C# Unity 唐老狮 No.5 模拟面试题

本文章不作任何商业用途 仅作学习与交流 安利唐老狮与其他老师合作的网站,内有大量免费资源和优质付费资源,我入门就是看唐老师的课程 打好坚实的基础非常非常重要: 全部 - 游习堂 - 唐老狮创立的游戏开发在线学习平台 - Powered By EduSoho 如果你发现了文章内特殊的字体格式,…

云原生事件驱动架构:构建实时响应的数字化神经系统

引言&#xff1a;重塑企业实时决策能力 Uber实现事件驱动架构升级后&#xff0c;实时供需匹配延迟降至8ms&#xff0c;动态定价策略响应速度提升1200倍。Netflix通过事件流处理实现个性化推荐&#xff0c;用户点击率提高34%&#xff0c;事件处理吞吐量达2000万/秒。Confluent基…

Metasploit multi/handler 模块高级选项解析

multi/handler 是 Metasploit 框架中至关重要的模块&#xff0c;主要用于监听目标机的连接并处理来自目标的反向 shell 或会话。它可以灵活地适应不同渗透测试场景&#xff0c;提供高度的自定义选项以优化监听器的行为。 在 Metasploit msf6 框架中&#xff0c;当使用 exploit…

WPF高级 | WPF 应用程序部署与发布:确保顺利交付到用户手中

WPF高级 | WPF 应用程序部署与发布&#xff1a;确保顺利交付到用户手中 一、前言二、部署与发布基础概念2.1 部署的定义与目的2.2 发布的方式与渠道2.3 部署与发布的关键要素 三、WPF 应用程序打包3.1 使用 Visual Studio 自带的打包工具3.2 使用第三方打包工具 四、发布到不同…

Spring MVC 程序开发(1)

目录 1、什么是 SpringMVC2、返回数据2.1、返回 JSON 对象2.2、请求转发2.3、请求重定向2.4、自定义返回的内容 1、什么是 SpringMVC 1、Tomcat 和 Servlet 分别是什么&#xff1f;有什么关系&#xff1f; Servlet 是 java 官方定义的 web 开发的标准规范&#xff1b;Tomcat 是…

C# Unity 唐老狮 No.4 模拟面试题

本文章不作任何商业用途 仅作学习与交流 安利唐老狮与其他老师合作的网站,内有大量免费资源和优质付费资源,我入门就是看唐老师的课程 打好坚实的基础非常非常重要: 全部 - 游习堂 - 唐老狮创立的游戏开发在线学习平台 - Powered By EduSoho 如果你发现了文章内特殊的字体格式,…

Protocol Buffers在MCU上的nanopb介绍及使用详解

在嵌入式系统和资源受限的环境中&#xff0c;传统的Protocol Buffers 可能显得过于庞大。因此&#xff0c;nanopb 应运而生&#xff0c;它是一个轻量级的 Protocol Buffers 生成器&#xff0c;专为嵌入式系统设计c语言设计。本文将介绍如何安装和使用 nanopb&#xff0c;以及通…

(十 二)趣学设计模式 之 享元模式!

目录 一、 啥是享元模式&#xff1f;二、 为什么要用享元模式&#xff1f;三、 享元模式的实现方式四、 享元模式的优缺点五、 享元模式的应用场景六、 总结 &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢博主的讲解方式&#xff0c;可以多多支…

Trae:国内首款AI原生IDE,编程效率大提升

今年一月&#xff0c;在新闻上看到字节跳动面向海外市场推出了一款名为Trae的AI集成开发环境&#xff08;IDE&#xff09;。起初&#xff0c;我并未给予过多关注&#xff0c;因为市面上已有不少IDE集成了AI插件&#xff0c;功能也非常全面&#xff0c;而字节跳动自家的MarsCode…

RocketMQ定时/延时消息实现机制

RocketMQ 的延迟消息是其核心特性之一&#xff0c;允许消息在指定延迟时间后才被消费者消费。 定时消息生命周期 一、延迟消息的核心机制 RocketMQ&#xff08;5.0之前&#xff09; 不支持任意时间精度的延迟&#xff0c;而是通过预定义的 延迟级别&#xff08;Delay Level&a…

基于SpringBoot的校园二手交易平台(源码+论文+部署教程)

运行环境 校园二手交易平台运行环境如下&#xff1a; • 前端&#xff1a;Vue • 后端&#xff1a;Java • IDE工具&#xff1a;IntelliJ IDEA&#xff08;可自行更换&#xff09; • 技术栈&#xff1a;SpringBoot Vue MySQL 主要功能 校园二手交易平台主要包含前台和…