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

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,简单记事本
下面是一个例子,实现了记事本的两个功能:
            
            
              cpp
              
              
            
          
          #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 ,该类用于获取文件或目录的详细信息,例如文件路径、大小、创建时间、修改时间等。下面是一个例子
            
            
              cpp
              
              
            
          
          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");
}运行结果如下

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()效果相同。 | 
创建线程的步骤
- 自定义一个类,继承于 QThread,并且只有一个线程处理函数(和主线程不是同一个线程),这个线程处理函数主要就是重写父类中的run()函数。
- 线程处理函数里面写入需要执行的复杂数据处理
- 启动线程不能直接调用 run()函数,需要使用对象来调用 start()函数实现线程启动
- 线程处理函数执行结束后可以定义一个信号来告诉主线程
- 最后关闭线程
2.2 例子1,自定义定时器
不使用QTimer,实现定时效果,首先定义一个Thread类继承自QThread
            
            
              cpp
              
              
            
          
          /* 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类的代码
            
            
              cpp
              
              
            
          
          /* 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,下面是一个例子
不加互斥锁,让两个线程++同一个变量
            
            
              cpp
              
              
            
          
          /* 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如下
            
            
              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
            
            
              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中的代码改为
            
            
              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类,下面是一个例子,仅仅作为演示
            
            
              cpp
              
              
            
          
          /* 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();
    }
}
            
            
              cpp
              
              
            
          
          /* 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模块
            
            
              cpp
              
              
            
          
          QT       += core gui network在widget.ui中铺上一个QListWidget
下面是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);
    void handlerRequest();
    QString resolutionRequest(const QString& request);
    ~Widget();
private:
    QUdpSocket* socket;
    Ui::Widget* ui;
};
#endif // WIDGET_H下面是wigdet.cpp的代码
            
            
              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中设置基本框架

下面是widget.h的代码
            
            
              cpp
              
              
            
          
          #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的代码
            
            
              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("");                                               // 清空
}同时运行客户端与服务端,结果如下

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如下
            
            
              cpp
              
              
            
          
          #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_Hwidget.cpp
            
            
              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如下

wigdet.h
            
            
              cpp
              
              
            
          
          #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_Hwidget.cpp
            
            
              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("");                               // 清空
}运行结果如下

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),会在QNetworkAccessManager的post方法中通过单独的参数来传入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中设置基本框架

widget.cpp如下
            
            
              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);
    }
}运行结果如下

5. 播放音频
使用QSoundEffect类,下面是一个例子
首先需要在pro文件中加上multimedia模块
widget.cpp如下
            
            
              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暂停音频