0


Qt 系统相关 - 网络与音视频

一、Qt 网络

  • 和多线程类似, Qt 为了支持跨平台, 对网络编程的 API 也进行了重新封装.
  • 在进行网络编程之前, 需要在项目中的 .pro 文件中添加 network 模块.
  • 添加之后要手动编译一下项目, 使 Qt Creator 能够加载对应模块的头文件.

1. UDP Socket

1.1 核心 API 概览

主要的类有两个. **QUdpSocket **和 QNetworkDatagram

  • QUdpSocket 表示一个 UDP 的 socket 文件.
    名称类型说明对标原生 APIbind(const QHostAddress&, quint16)方法绑定指定的端口号.bindreceiveDatagram()方法返回 QNetworkDatagram . 读取⼀个 UDP 数据报.recvfromwriteDatagram(const QNetworkDatagram&)方法发送⼀个 UDP 数据报.sendtoreadyRead信号在收到数据并准备就绪后触发.无 (类似于 IO 多路复用的通知机制)

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

    1.2 回显服务器

1) 创建界面, 包含⼀个 QListWidget 用来显示消息.

2) 创建 QUdpSocket 成员

  • 修改 widget.h(注意:先在 .pro 文件中添加 network 模块.)
#include <QUdpSocket>

class Widget : public QWidget
{
    Q_OBJECT

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

private:
    Ui::Widget *ui;
    
    QUdpSocket* socket;

    void processRequest();
    
    QString process(const QString& request);
};
  • 修改 widget.cpp, 完成 socket 后续的初始化
  1. 一般来说, 要先连接信号槽, 再绑定端口.
  2. 如果顺序反过来, 可能会出现端口绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这个请求就有可能错过了.
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    // 创建出这个对象
    socket = new QUdpSocket(this);
    
    // 设置窗口标题
    this->setWindowTitle("服务器");
    
    // 连接信号槽,处理收到的请求
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
    
    // 绑定端口号
    bool ret = socket->bind(QHostAddress::Any, 9090);
    if (!ret){
        QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());
        return;
    }
}

3) 实现 processRequest , 完成处理请求的过程

  • 读取请求并解析
  • 根据请求计算响应
  • 把响应写回到客户端
#include <QNetworkDatagram>

// 这个函数完成的逻辑,就是服务器的最核心逻辑
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);
    
}

4) 实现 process 函数

  • 由于我们此处是实现回显服务器. 所以 process 方法中并没有包含实质性的内容.
QString Widget::process(const QString &request)
{
    // 由于当前是回显服务器,响应就是和请求完全一样
    // "根据请求处理响应" 是服务器开发中的最核心的步骤.
    //一个商业服务器程序, 这里的逻辑可能是几万行几十万行代码量级的.
    return request;
}

此时, 服务器程序编写完毕.

但是直接运行还看不出效果. 还需要搭配客户端来使用.

1.3 回显客户端

1) 创建界面. 包含⼀个 QLineEdit , QPushButton , QListWidget

  • 先使用水平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的**sizePolicy **为 Expanding
  • 再使用垂直布局把 QListWidget 和上面的水平布局放好.
  • 设置垂直布局的 **layoutStretch **为 5, 1 (尺寸比例根据个人喜好微调).

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

// 提前定义好服务器的 IP 和 端口
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;

**3) 创建 QUdpSocket 成员 **

  • 修改 widget.h, 定义成员
#include <QUdpSocket>

class Widget : public QWidget
{
    Q_OBJECT

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

private:
    Ui::Widget *ui;
    
    // 创建 socket 成员
    QUdpSocket* socket;
};

修改 widget.cpp, 初始化 socket

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    // 1. 设置窗⼝名字
    this->setWindowTitle("客⼾端");
    
    // 2. 实例化 socket
    socket = new QUdpSocket(this);
}

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

#include <QNetworkDatagram>

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

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

// 通过信号槽,来处理服务器返回的数据
connect(socket, &QUdpSocket::readyRead, this, &Widget::processResponse);

void Widget::processResponse()
{
    // 通过这个函数来处理收到的响应
    // 1.读取到响应数据
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    // 2.把响应数据显示到界面上
    ui->listWidget->addItem("服务器说:" + response);
}

6) 最终执行效果(启动多个客户端都可以正常工作.

  • 客户端服务器程序测试时候的基本原则,一定是先启动服务器,后启动客户端

2. TCP Socket

2.1 核心 API 概览

**核心类是两个: ****QTcpServer ****和 **QTcpSocket

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

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

    QByteArray 用于表示一个字节数组. 可以很方便的和 QString 进行相互转换.

    例如:

    • 使用 QString 的构造函数即可把 QByteArray 转成 QString.
    • 使用 QString 的 toUtf8 函数即可把 QString 转成 QByteArray.

2.2 回显服务器

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

2) 创建 QTcpServer 并初始化

  • 修改 widget.h, 添加 QTcpServer 指针成员.
#include <QTcpServer>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    
    void processConnection();
    QString process(const QString& request);

private:
    Ui::Widget *ui;

    // 创建 QTcpServer
    QTcpServer* tcpServer;
};

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

  • 设置窗口标题
  • 实例化 TCP server. (父元素设为当前控件, 会在父元素销毁时被一起销毁).
  • 通过信号槽, 处理客户端建立的新连接.
  • 监听端口
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    // 1.修改窗口标题
    this->setWindowTitle("服务器");
    
    // 2.创建 QTcpServer 的实例
    tcpServer = new QTcpServer(this);
    
    // 3.通过信号槽,指定如何处理连接
    connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);
    
    // 4.绑定并监听端口号
    bool ret = tcpServer->listen(QHostAddress::Any, 9090);
    if (!ret){
        QMessageBox::critical(this, "服务器启动失败", tcpServer->errorString());
        exit(1);
    }
}

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

  • 获取到新的连接对应的 socket.
  • 通过信号槽, 处理收到请求的情况
  • 通过信号槽, 处理断开连接的情况
void Widget::processConnection()
{
    // 1. 通过 tcpServer 拿到一个 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) 把响应写回客户端
        clientSocket->write(response.toUtf8());
        // d) 把上述信息记录到日志中
        QString log = QString("[") + clientSocket->peerAddress().toString()
                + ":" + QString::number(clientSocket->peerPort()) + "] req: " +
                request + ", resp: " + response;
        ui->listWidget->addItem(log);
    });
    
    // 3. 通过信号槽, 处理断开连接的情况
    connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
        // a) 把断开连接的信息通过日志显示出来
        QString log = QString("[") + clientSocket->peerAddress().toString()
                + ":" + QString::number(clientSocket->peerPort()) + "] 客户端下线!";
        ui->listWidget->addItem(log);
        // b) 手动释放 clientSocket。直接使用 delete 是下策,使用 deleteLater 更加合适 
        clientSocket->deleteLater();
    });        
}

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

  • 由于我们此处是实现回显服务器. 所以 process 方法中并没有包含实质性的内容.
// 此处写的是回显服务器
QString Widget::process(const QString &request)
{
    return  request;
}

此时, 服务器程序编写完毕.

但是直接运行还看不出效果. 还需要搭配客户端来使用.

2.3 回显客户端

1) 创建界面. 包含一个 QLineEdit , QPushButton , QListWidget

  • 先使用水平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的sizePolicyExpanding
  • 再使用垂直布局把 QListWidget 和上面的水平布局放好.
  • 设置垂直布局的 layoutStretch 为 5, 1 (尺寸比例根据个人喜好微调).

2) 创建 QTcpSocket 并实例化

  • 修改 widget.h, 创建成员.
class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;

    // 新增 QTcpSocket
    QTcpSocket* socket;
};

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

  • 设置窗口标题
  • 实例化 socket 对象 (父元素设为当前控件, 会在父元素销毁时被一起销毁).
  • 和服务器建立连接.
  • 等待并确认连接是否出错.
#include <QMessageBox>

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

    // 1. 设置窗口标题.
    this->setWindowTitle("客户端");
    // 2. 实例化 socket 对象.
    socket = new QTcpSocket(this);
    // 3. 和服务器建⽴连接.
    socket->connectToHost("127.0.0.1", 9090);
    // 4. 等待并确认连接是否出错.
    if (!socket->waitForConnected()) {
        QMessageBox::critical(nullptr, "连接服务器出错!", socket->errorString());
        exit(1);
    }
}

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

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

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

// 连接信号槽,处理响应
connect(socket, &QTcpSocket::readyRead, this, [=](){
    // a) 读取出响应内容
    QString response = socket->readAll();
    // b) 把响应内容显示到界面上
    ui->listWidget->addItem("服务器说:" + response);
});

6) 最终执行效果(先启动服务器, 再启动客户端,可以启动多个)

  • 由于我们使用信号槽处理同一个客户端的多个请求, 不涉及到循环, 也就不会使客户端之间相互影响了.

3. HTTP Client

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

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

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).

如果需要发送一个带有 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设置 cookieUserAgentHeader设置 User-Agent

  • QNetworkReply 表示一个 HTTP 响应. 这个类同时也是 QIODevice 的子类.
    方法说明error()获取出错状态.errorString()获取出错原因的文本.readAll()读取响应 body.header(QNetworkRequest::KnownHeaders header)读取响应指定 header 的值.
    此外, **QNetworkReply **还有一个重要的信号 **finished **会在客户端收到完整的响应数据之后触发.

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 属性

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, 创建实例

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

    this->setWindowTitle("客户端");

    // 实例化属性
    manager = new QNetworkAccessManager(this);
}

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

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 进行释放
        response->deleteLater();
    });
}

5) 执行程序, 观察效果

发送 POST 请求代码也是类似. 使用 manager->post() 即可.

二、Qt 音视频

1. Qt 音频

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

通过帮助手册查看 QSound 类如下:

注意:

  • 使用 QSound 类时,需要添加模块:multimedia

1.1 核心API概览

play()开始或继续播放当前源。

1.2 示例

/********************************* SoundTest.pro
*********************************/

QT       += core gui multimedia //添加⾳频模块
        
/********************************* 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(":/1.wav",this);
    
    connect(ui->btn,&QPushButton::clicked,[=](){
        sound->play(); //播放
    });
}

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

2. Qt 视频

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

2.1 核心API概览

setMedia()设置当前媒体源。setVideoOutput()将QVideoWidget视频输出附加到媒体播放器。 如果媒体播放器已经附加了视频输出,将更换⼀个新的。

2.2 示例

首先在 .pro 文件中添加 multimedia 和 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
    
/********************************* 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 Qt 网络 Qt 视频

本文转载自: https://blog.csdn.net/weixin_65931202/article/details/141230324
版权归原作者 南风与鱼 所有, 如有侵权,请联系我们删除。

“Qt 系统相关 - 网络与音视频”的评论:

还没有评论