Qt 网络编程与音视频开发实战:从 UDP/TCP 到 HTTP 与多媒体

📑 目录

  • [📡 1. Qt 网络部分](#📡 1. Qt 网络部分)
    • [🎯 1.1 UDP Socket](#🎯 1.1 UDP Socket)
      • [📌 1.1.1 核心 API 概览](#📌 1.1.1 核心 API 概览)
      • [🖥️ 1.1.2 回显服务器](#🖥️ 1.1.2 回显服务器)
      • [💻 1.1.3 回显客户端](#💻 1.1.3 回显客户端)
    • [🔗 1.2 TCP Socket](#🔗 1.2 TCP Socket)
      • [📌 1.2.1 核心 API 概览](#📌 1.2.1 核心 API 概览)
      • [🖥️ 1.2.2 回显服务器](#🖥️ 1.2.2 回显服务器)
      • [💻 1.2.3 回显客户端](#💻 1.2.3 回显客户端)
    • [🌐 1.3 HTTP Client](#🌐 1.3 HTTP Client)
      • [📌 1.3.1 核心 API](#📌 1.3.1 核心 API)
      • [🖥️ 1.3.2 代码示例](#🖥️ 1.3.2 代码示例)
    • [🧩 1.4 其他模块](#🧩 1.4 其他模块)
  • [🎵 2. Qt 音视频](#🎵 2. Qt 音视频)
    • [🔊 2.1 Qt 音频](#🔊 2.1 Qt 音频)
      • [📌 2.1.1 核心 API 概览](#📌 2.1.1 核心 API 概览)
      • [🖥️ 2.1.2 示例](#🖥️ 2.1.2 示例)
    • [🎬 2.2 Qt 视频](#🎬 2.2 Qt 视频)
      • [📌 2.2.1 核心 API 概览](#📌 2.2.1 核心 API 概览)
      • [🖥️ 2.2.2 示例](#🖥️ 2.2.2 示例)
  • [📝 小总结](#📝 小总结)
  • [🎯 经典面试题](#🎯 经典面试题)

📡 1. Qt 网络部分

💡 笔记:网络编程是 Qt 开发中非常核心的部分。实际开发中,HTTP 协议往往比 TCP/UDP 使用更多一些。HTTP 协议本质也就是通过 TCP 实现的,实现一个 HTTP 客户端/服务器,本质就是基于 TCP socket 进行封装。Qt 只是提供了 HTTP 的客户端接口,而没有提供服务器端的接口。


📌写在前面 :使用Qt网络编程的API,需要先在.pro文件中添加network模块,之前的文章中的Qt的各种组件,各种内容,都是包含在QtCore模块中的 - 默认包含。

**此处只需要在.pro文件中的 QT += core gui network上加上network模块即### 🎯 1.1 UDP Socket别#### 📌 1.1.1 核心 API 概览et

📌 1.1.1 核心 API 概览

UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议。在 Qt 中,主要通过 QUdpSocket 类来实现 UDP 通信。

名称 类型 说明 对标原生 API
bind(const QHostAddress&, quint16 port) 方法 绑定指定的地址和端口号 bind
writeDatagram(const QNetworkDatagram&) 方法 发送一个 UDP 数据报 sendto
readyRead 信号 有数据到达并准备就绪时触发 无(类似于 IO 多路复用中的通知机制)
receiveDatagram() 方法 接收一个 UDP 数据报 recvfrom

💡 笔记 :端口号本质就是一个 2 字节的无符号整数,quint16 本质就是一个 unsigned short。

QNetworkDatagram 表⽰⼀个 UDP 数据报.

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

1) 创建界面 :包含一个 QListWidget,用于显示收到的数据。

2) 创建 QUdpSocket 并初始化

修改 widget.h,添加 QUdpSocket 指针成员:

cpp 复制代码
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private:
    Ui::Widget *ui;
    // 创建 socket 成员
    QUdpSocket* socket;
};

修改 widget.cpp,实例化 QUdpSocket 并进行初始化:

✏️注意:⼀般来说, 要先连接信号槽, 再绑定端⼝。

如果顺序反过来, 可能会出现端⼝绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这个请求就有可能错过了。

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
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::readDatagram,this,&Widget::processRequest);
    //4.绑定端口号
    bool ret = socket->bind(QHostAddress::Any,8888);
    if(!ret)
    {
        QMessageBox::critical(nullptr,"服务器启动出错",socket->errorString());
        return;
    }
}

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

3) 实现 processRequest 函数

  • 读取请求并解析
  • 根据请求计算响应
  • 把响应写回到客⼾端
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
#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,8888);
    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)
{

}

4) 实现 process 函数

由于我们此处是实现回显服务器,所以 process 方法中并没有包含实质性的内容:

cpp 复制代码
QString Widget::process(const QString& request)
{
    return request;
}

💡 笔记:"根据请求处理响应"是服务器开发中的最核心的步骤。一个商业服务器程序,这里的逻辑可能是几万行几十万行代码量级的。

此时,服务器程序编写完毕。但是直接运行还看不出效果,还需要搭配客户端来使用。

💻 1.1.3 回显客户端

1) 创建界面 :包含一个 QLineEdit、QPushButton、QListWidget。

  • 先使用水平布局把 QLineEdit 和 QPushButton 放好,并设置这两个控件的垂直方向的 sizePolicy 为 Expanding。
  • 再使用垂直布局把 QListWidget 和上面的水平布局放好。
  • 设置垂直布局的 layoutStretch 为 5, 1。

2) 在 mainwindow.cpp 中,先创建两个全局常量,表示服务器的 IP 和端口

cpp 复制代码
//先定义两个常量,表示服务器的IP和端口号
const QString& IP = "127.0.0.1";
const quint16 PORT = 8888;

3) 创建 QUdpSocket 成员

修改 mainwindow.h,定义成员:

cpp 复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include<QUdpSocket>


QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT


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

private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;

    //创建一个socket
    QUdpSocket* socket;
    //先定义两个常量,表示服务器的IP和端口号
    const QString SERVER_IP = "127.0.0.1";
    const quint16 SERVER_PORT = 8888;
};
#endif // MAINWINDOW_H

修改 mainwindow.cpp,初始化 socket:

cpp 复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"

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

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

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

4) 给发送按钮 slot 函数,实现发送请求

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

5) 再次修改 MainWindow 的构造函数,通过信号槽,来处理服务器的响应

cpp 复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QLineEdit>
#include<QNetworkDatagram>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    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);
    });
}

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


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

最终执行效果 :启动多个客户端都可以正常工作。


🔗 1.2 TCP Socket

📌 1.2.1 核心 API 概览

核心类是两个:QTcpServer 和 QTcpSocket。

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

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

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

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

💡 笔记 :QByteArray 用于表示一个字节数组,可以很方便的和 QString 进行相互转换。例如:使用 QString 的构造函数即可把 QByteArray 转成 QString;使用 QString 的 toUtf8 函数即可把 QString 转成 QByteArray。读写都是使用 QByteArray,与 UDP 不同。
💡 笔记 :事件循环 ,简单理解,可以认为是 Qt 程序内部,带有一个"生物钟"这样的东西,周期性的执行一些逻辑。

deleteLater 函数这个操作,不是立即销毁 clientSocket,而是告诉 Qt,在下一轮事件循环中,再进行上述的销毁操作。槽函数都是在事件循环中执行的,进入下一轮事件循环,意味着上一轮事件循环肯定结束了,也意味着当前的槽函数肯定是执行完毕了。

🖥️ 1.2.2 回显服务器

1) 创建界面 :包含一个 QListWidget,用于显示收到的数据。

2) 创建 QTcpServer 并初始化

修改 widget.h,添加 QTcpServer 指针成员:

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H
#include<QTcpSocket>
#include <QWidget>
#include<QTcpServer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

    void processConnection();

    QString process(QString request);

private:
    Ui::Widget *ui;
    //创建QTcpServer服务器指针
    QTcpServer* tcpServer;
};
#endif // WIDGET_H

修改 widget.cpp,实例化 QTcpServer 并进行后续初始化操作:

  • 设置窗口标题
  • 实例化 TCP server(父元素设为当前控件,会在父元素销毁时被一起销毁)
  • 通过信号槽,处理客户端建立的新连接
  • 监听端口
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QHostAddress>
#include<QMessageBox>
#include<QListWidget>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //1.设置窗口标题
    this->setWindowTitle("服务器");

    //2.实例化TcpServer
    tcpServer = new QTcpServer(this);

    //先绑定消息到来时的处理函数
    //3.通过信号槽,处理客户端建立的新链接
    connect(tcpServer,&QTcpServer::newConnection,this,&Widget::processConnection);

    //4.监听端口 - 相当于bind和listen
    bool ret = tcpServer->listen(QHostAddress::Any,8888);
    if(!ret)
    {
        //消息对话框QMessageBox
        QMessageBox::critical(nullptr,"服务器启动失败!",tcpServer->errorString());
        exit(1);
    }
}

3) 继续修改 widget.cpp,实现处理连接的具体方法 processConnection

  • 获取到新的连接对应的 socket
  • 通过信号槽,处理收到请求的情况
  • 通过信号槽,处理断开连接的情况
cpp 复制代码
void Widget::processConnection()
{
    //获取到新链接对应的socket
    QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
    QString log = QString("[") + clientSocket->peerAddress().toString() +
            ":" + QString::number(clientSocket->peerPort()) + "] 客户端上线!";
    ui->listWidget->addItem(log);

    //2.通过信号槽,处理到请求的情况 - 缓冲区中的数据就绪触发信号
    connect(clientSocket,&QTcpSocket::readyRead,this,[=](){
        //a)读取请求
        QString request = clientSocket->readAll();
        //b)根据请求处理响应
        const QString& response = process(request);
        //c)将响应写回客户端 - 将字符串转化为QByteArray(字节数组)
        clientSocket->write(response.toUtf8());

        QString log = QString("[") +clientSocket->peerAddress().toString() + ":"
                +QString::number(clientSocket->peerPort()) +"] req:"
                + request +",resp:" + response;
        ui->listWidget->addItem(log);

    });

    //3.通过信号槽,处理连接断开的情况
    connect(clientSocket,&QTcpSocket::disconnected,this,[=]()
    {
        QString log = QString("[") + clientSocket->peerAddress().toString()
                + ":" + clientSocket->peerPort() + "] 客户端已经下线!";
        //删除clientSocket - 断开的连接会在下一个时间循环启动时清除掉
        clientSocket->deleteLater();
    });

}

4) 实现 process 方法,实现根据请求处理响应

由于我们此处是实现回显服务器,所以 process 方法中并没有包含实质性的内容:

cpp 复制代码
QString Widget::process(const QString &request)
{
    return request;
}

💡 笔记:"根据请求处理响应"是服务器开发中的最核心的步骤。一个商业服务器程序,这里的逻辑可能是几万行几十万行代码量级的。

💻 1.2.3 回显客户端

1) 创建界面 :包含一个 QLineEdit、QPushButton、QListWidget。

  • 先使用水平布局把 QLineEdit 和 QPushButton 放好,并设置这两个控件的垂直方向的 sizePolicy 为 Expanding。
  • 再使用垂直布局把 QListWidget 和上面的水平布局放好。
  • 设置垂直布局的 layoutStretch 为 5, 1。

2) 创建 QTcpSocket 并实例化

修改 widget.h,创建成员:

cpp 复制代码
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private:
    Ui::Widget *ui;
    // 新增 QTcpSocket
    QTcpSocket* socket;
};

修改 widget.cpp,对 QTcpSocket 进行实例化:

  • 设置窗口标题
  • 实例化 socket 对象(父元素设为当前控件,会在父元素销毁时被一起销毁)
  • 和服务器建立连接
  • 等待并确认连接是否出错
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //1.设置窗口标题
    this->setWindowTitle("客户端");

    //2.实例化tcp客户端
    tcpClient = new QTcpSocket(this);

    //3.和tcp服务器建立连接
    tcpClient->connectToHost("127.0.0.1",8888);

    //4.等待并确认连接是否出错
    if(!tcpClient->waitForConnected())
    {
        QMessageBox::critical(nullptr,"连接服务器出错!",tcpClient->errorString());
        exit(1);
    }
}

3) 修改 widget.cpp,给按钮增加点击的 slot 函数,实现发送请求给服务器

cpp 复制代码
void Widget::on_pushButton_clicked()
{
    // 获取输入框的内容
    const QString& text = ui->lineEdit->text();
    // 清空输入框内容
    ui->lineEdit->setText("");
    // 把消息显示到界面上
    ui->listWidget->addItem(QString("客户端说: ") + text);
    // 发送消息给服务器
    socket->write(text.toUtf8());
}

4) 修改 widget.cpp 中的 Widget 构造函数,通过信号槽,处理收到的服务器的响应

cpp 复制代码
// 处理服务器返回的响应.
connect(socket, &QTcpSocket::readyRead, this, [=]() {
    QString response = socket->readAll();
    qDebug() << response;
    ui->listWidget->addItem(QString("服务器说: ") + response);
});

先启动服务器,再启动客户端(可以启动多个),最终执行效果:由于我们使用信号槽处理同一个客户端的多个请求,不涉及到循环,也就不会使客户端之间相互影响了。


🌐 1.3 HTTP Client

进行 Qt 开发时,和服务器之间的通信很多时候也会用到 HTTP 协议。

  • 通过 HTTP 从服务器获取数据。
  • 通过 HTTP 向服务器提交数据。

💡 笔记:实际开发过程中,HTTP 获取到的数据,也不一定是 HTML,更大的可能是客户端和服务器开发约定好交互的数据格式。按照约定的格式,客户端拿到之后,进行解析,并展示到界面上。

📌 1.3.1 核心 API

关键类主要是三个:QNetworkAccessManager、QNetworkRequest、QNetworkReply。

QNetworkAccessManager 提供了 HTTP 的核心操作。

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

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

如果需要发送一个带有 bodQNetworkAccessManager 的 post 方法中通过单独的参数来传入 body。

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

💡 笔记 :QVariant 表示一个类型可变的值,类似于 C 语言中的 void*。

其中的 QNetworkRequest::KnownHeaders 是一个枚举类型,常用取值:

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

QNetworkReply 表示一个 HTTP 响应。这个类同时也是 QIODevice 的子类。

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

此外,QNetworkReply 还有一个重要的信号 finished,会在客户端收到完整的响应数据之后触发。

🖥️ 1.3.2 代码示例

给服务器发送一个 GET 请求。

1) 创建界面 :包含一个 QLineEdit、QPushButton。

  • 先使用水平布局把 QLineEdit 和 QPushButton 放好,并设置这两个控件的垂直方向的 sizePolicy 为 Expanding。
  • 再使用垂直布局把 QPlainTextEdit 和上面的水平布局放好。(QPlainTextEdit 的 readOnly 设为 true)
  • 设置垂直布局的 layoutStretch 为 5, 1。

💡 笔记 :此处建议使用 QPlainTextEdit 而不是 QTextEdit。主要因为 QTextEdit 要进行富文本解析,如果得到的 HTTP 响应体积很大,就会导致界面渲染缓慢甚至被卡住。

2) 修改 widget.h,创建 QNetworkAccessManager 属性

cpp 复制代码
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private slots:
    void on_pushButton_clicked();
private:
    Ui::Widget *ui;
    // 新增属性
    QNetworkAccessManager* manager;
};

3) 修改 widget.cpp,创建实例

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 实例化属性
    manager = new QNetworkAccessManager(this);
}

4) 编写按钮的 slot 函数,实现发送 HTTP 请求功能

cpp 复制代码
void Widget::on_pushButton_clicked()
{
    // 1. 获取到输入框中的 URL, 构造 QUrl 对象
    QUrl url(ui->lineEdit->text());
    // 2. 构造 HTTP 请求对象
    QNetworkRequest request(url);
    // 3. 发送 GET 请求
    QNetworkReply* response = manager->get(request);
    // 4. 通过信号槽来处理响应
    connect(response, &QNetworkReply::finished, this, [=]() {
        if (response->error() == QNetworkReply::NoError) {
            // 响应正确
            QString html(response->readAll());
            ui->plainTextEdit->setPlainText(html);
            // qDebug() << html;
        } else {
            // 响应出错
            ui->plainTextEdit->setPlainText(response->errorString());
        }
        response->deleteLater();
    });
}

执行程序,观察效果。

发送 POST 请求代码也是类似,使用 manager->post() 即可。此处不再演示。


🧩 1.4 其他模块

Qt 中还提供了 FTP、DNS、SSL 等网络相关的组件工具,感兴趣的读者可以自行查看Qt中的参考手册或者借助于AI进行学习。


🎵 2. Qt 音视频

🔊 2.1 Qt 音频

在 Qt 中,音频主要是通过 QSound 类来实现。但是需要注意的是 QSound 类只支持播放 wav 格式的音频文件。也就是说如果想要添加音频效果,那么首先需要将非 wav 格式的音频文件转换为 wav 格式。

💡 笔记 :使用 QSound 类时,需要添加模块:multimedia。

📌 2.1.1 核心 API 概览
方法 说明
play() 开始或继续播放当前源。
🖥️ 2.1.2 示例
cpp 复制代码
/********************************* SoundTest.pro *********************************/
QT       += core gui multimedia   //添加音频模块
cpp 复制代码
/********************************* widget.cpp *********************************/
#include "widget.h"
#include "ui_widget.h"
#include <QSound>  //添加音频头文件

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //实例化对象
    QSound *sound = new QSound(":/sound.wav",this);
    connect(ui->btn,&QPushButton::clicked,[=](){
        sound->play(); //播放
    });
}

Widget::~Widget()
{
    delete ui;
}
🎬 2.2 Qt 视频

在 Qt 中,视频播放的功能主要是通过 QMediaPlayer 类和 QVideoWidget 类来实现。在使用这两个类时要添加对应的模块 multimedia 和 multimediawidgets。

📌 2.2.1 核心 API 概览
方法 说明
setMedia() 设置当前媒体源。
setVideoOutput() 将 QVideoWidget 视频输出附加到媒体播放器。如果媒体播放器已经附加了视频输出,将更换一个新的。
🖥️ 2.2.2 示例

首先在 .pro 文件中添加 multimedia 和 multimediawidgets 两个模块。

cpp 复制代码
/************************player.pro*******************************/
QT       += core ui
# 添加视频模块
QT += multimedia
QT += multimediawidgets
/********************************* widget.h *********************************/
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QHBoxLayout>  //水平布局
#include <QVBoxLayout>  //垂直布局
#include <QVideoWidget> //显示视频
#include <QMediaPlayer> //播放声音
#include <QPushButton> //按钮
#include <QStyle> //设置图标
#include <QFileDialog> //选择文件/文件夹

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
public slots:
    void chooseVideo();
private:
    QMediaPlayer *mediaPlayer;
    QVideoWidget *videoWidget;
    QVBoxLayout *vbox;
    //创建两个按钮:选择视频按钮和开播放按钮
    QPushButton *chooseBtn,*playBtn;
};
#endif // WIDGET_H
cpp 复制代码
/********************************* widget.cpp *********************************/
#include "widget.h"
#include <QMediaPlayer>
#include <QSlider>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    //对象实例化
    mediaPlayer = new QMediaPlayer(this);
    videoWidget = new QVideoWidget(this);
    //设置播放画面的窗口
    videoWidget->setMinimumSize(600,600);
    //实例化窗口布局---垂直布局
    this->vbox = new QVBoxLayout(this);
    this->setLayout(this->vbox);
    //实例化选择视频按钮
    chooseBtn = new QPushButton("选择视频",this);
    //实例化播放按钮
    playBtn = new QPushButton(this);
    //设置图标代替文件
    playBtn->setIcon(this->style()->standardIcon(QStyle::SP_MediaPlay));
    //实例化一个水平布局,将以上控件放入水平布局中
    QHBoxLayout *hbox = new QHBoxLayout;
    //添加控件
    hbox->addWidget(chooseBtn);
    hbox->addWidget(playBtn);
    //将播放窗口和水平布局都添加到垂直布局中
    vbox->addWidget(videoWidget);
    //布局中添加布局
    vbox->addLayout(hbox);
    //将选择视频对应的按钮和槽函数进行关联
    connect(chooseBtn,&QPushButton::clicked,this,&Widget::chooseVideo);
}

void Widget::chooseVideo()
{
    //选择视频,返回一个播放视频的名字
    QString name = QFileDialog::getSaveFileName(this,"选择视频",".","WMV(*.wmv)");
    //设置媒体声音
    mediaPlayer->setMedia(QUrl(name));
    //输出视频画面
    mediaPlayer->setVideoOutput(videoWidget);
    //播放
    mediaPlayer->play();
}

Widget::~Widget()
{
}

📝 小总结

本章围绕 Qt 网络编程与音视频模块展开,核心要点如下:

  • 事件机制 :事件是信号槽的底层机制,信号槽是事件的进一步封装。绝大多数交互通过信号槽完成,特殊场景需要重写事件处理函数(如 enterEvent、mousePressEvent、keyPressEvent 等)。
  • 事件分发器与事件过滤器 :事件分发器通过重写 bool event(QEvent* e) 实现拦截;事件过滤器则是在事件分发之前做更高级的拦截,通过 installEventFilter 安装、重写 eventFilter 实现。
  • 文件操作 :QFile 负责文件读写,QFileInfo 获取文件信息,QSaveFile 安全保存文件,QTemporaryFile 创建临时文件。
  • 多线程 :通过继承 QThread 并重写 run() 实现,启动线程调用 start(),通过信号与主线程通信。
  • UDP 通信 :无连接协议,使用 QUdpSocket,通过 bind 绑定端口、writeDatagram 发送、receiveDatagram 接收。
  • TCP 通信 :面向连接的可靠协议,服务器用 QTcpServer 监听、nextPendingConnection 获取连接;客户端用 QTcpSocket 连接服务器,通过 readyRead 信号读取数据。
  • HTTP 通信 :基于 TCP 封装,Qt 提供客户端接口(QNetworkAccessManager、QNetworkRequest、QNetworkReply),不提供服务器端接口。
  • 音视频 :QSound 播放 wav 音频;QMediaPlayer + QVideoWidget 播放视频。

🎯 经典面试题

1. 信号槽与事件的区别是什么?

信号槽是事件的进一步封装,事件是信号槽的底层机制。用户操作会产生事件,Qt 将操作系统的事件机制封装后进一步封装为信号槽。绝大多数用户交互通过信号槽完成,特殊情况下需要重写事件处理函数。

2. 事件分发器和事件过滤器有什么区别?

事件分发器通过重写 bool event(QEvent* e) 函数实现,所有事件都会进入该函数,根据事件类型分发到对应的事件处理函数,返回 true 表示拦截。事件过滤器则是在事件分发之前做更高级的拦截,通过 installEventFilter 安装,重写 eventFilter 函数实现,适合多个组件统一拦截的场景。

3. UDP 和 TCP 在 Qt 中的使用有何不同?

UDP 是无连接协议,使用 QUdpSocket,通过 bind 绑定端口、writeDatagram 发送数据报、receiveDatagram 接收数据报。TCP 是面向连接的可靠协议,服务器使用 QTcpServer 监听端口、nextPendingConnection 获取连接;客户端使用 QTcpSocket 的 connectToHost 建立连接,读写都使用 QByteArray。

4. deleteLater() 的作用是什么?为什么使用它?

deleteLater() 不是立即销毁对象,而是将对象标记为无效,Qt 会在下一轮事件循环中析构释放该对象。这样可以避免在槽函数执行过程中直接删除对象导致的问题,确保当前槽函数执行完毕后再销毁。

5. Qt 中如何实现多线程?

自定义一个类继承 QThread,重写 run() 函数作为线程处理函数,通过 start() 启动线程(不能直接调用 run()),线程执行结束后通过信号通知主线程,最后关闭线程。

相关推荐
Eric.461 小时前
2025 深度实战:本地部署 AI 漫剧与 AI 视频全量产方案,彻底解决云端画质抖动、角色漂移、成本超标
大数据·人工智能·音视频·comfyui·ai漫剧
盟接之桥12 小时前
当大模型遇见线束制造:不是通用AI,而是行业AI
大数据·网络·人工智能·安全·制造
Android系统攻城狮13 小时前
Linux Gstreamer深度解析之gst_audio_encoder_set_frame_max调用流程与实战(六十一)
android·linux·运维·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
91刘仁德14 小时前
IP协议详解:从IP协议头到网段划分、路由与NAT
linux·服务器·网络·网络协议·tcp/ip
可乐鸡翅yeah_15 小时前
业务中 M3U8 水印相关坑,硬水印和动态水印区别
前端·网络·数据库·ffmpeg·m3u8在线
酣大智16 小时前
H3C MSR3600 交换机配置 SSH
网络·ssh·智能路由器
沐浴露z17 小时前
Agent 响应延迟过高?从工具治理角度提供两条思路
前端·网络
马六六i18 小时前
市面上正规的IP驱动产业新场景新工具有哪些
大数据·网络·python·tcp/ip
可乐ea19 小时前
多模型编排的三层:框架、模型路由与提供商路由
前端·网络·人工智能·ai智能体·多智能体协作·多模型编排·大模型路由