当前位置: 首页 > article >正文

Qt 5.14.2 学习记录 —— 이십일 Qt网络和音频

文章目录

  • 1、UDP
    • 带有界面的Udp服务器(回显服务器)
  • 2、TCP
    • 回显服务器
  • 3、HTTP客户端
  • 4、音频


和Linux的网络一样,Qt封装了Linux的网络API,即Socket API。网络编程是在应用层写,需要传输层支持,传输层有UDP和TCP,Qt对此有两套API。

Qt网络编程时需要在.pro文件添加network模块。

1、UDP

两个类QUdpSocket,QNetworkDatagram,后者就是数据报。

QUdpSocket

在这里插入图片描述

不过Qt不是用堵塞的,而是利用信号槽。当socket收到请求时,QUdpSocket就会触发这个readyRead信号,此时槽函数就能进行处理逻辑了,整体也就形成了事件驱动的网络编程。

QNetworkDatagram

在这里插入图片描述

带有界面的Udp服务器(回显服务器)

创建一个QWidget项目,放一个List Widget到界面中。

pro文件里

QT       += core gui network

服务端

// widget.h

#include <QWidget>
#include <QUdpSocket>
#include <QNetworkDatagram>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

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

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

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

    // 加上this就挂上了对象树, 自动析构
    // 不加就在析构函数里写上delete socket
    socket = new QUdpSocket(this);

    this->setWindowTitle("服务器");

    // 先连接信号槽再绑定端口号
    // 绑定好端口号那么请求就可以收到了
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);

    // Any表示任何IP地址都可以识别到
    bool flag = socket->bind(QHostAddress::Any, 9090);
    if (!flag)
    {
        QMessageBox::critical(this, "服务器启动出错", socket->errorString());
        return ;
    }
}

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

void Widget::processRequest()
{
    // 读取请求并解析
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data();
    // data()返回一个QByteArray对象, 这个类型可以赋值给QString

    // 根据请求计算响应 (由于是回显服务器, 响应就是请求自身, 不需要计算)
    const QString& response = process(request);

    // 响应写回给客户端
    // toUtf8()取出QString内部的字节数组
    // 后面两个参数表明要发送到哪个主机的哪个端口号
    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;
}

客户端,在同一目录下再创建一个QWidget项目即可。界面

在这里插入图片描述

如何做出这个界面就不写了,重点在客户端代码上

// 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();
    void processResponse();

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

// widget.cpp

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

const QString& SERVER_IP = "127.0.0.1";
// 端口号本质是一个2字节的无符号整数
// quint16是Qt的unsigned short, 其大小是2个字节
const quint16 SERVER_PORT = 9090;

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::processResponse);
}

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

void Widget::on_pushButton_clicked()
{
    // 获取输入框内容
    const QString& text = ui->lineEdit->text();

    // 构造UDP请求数据
    QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);
    
    // 发送请求数据
    socket->writeDatagram(requestDatagram);
    
    // 发送的请求添加到列表框中
    ui->listWidget->addItem("客户端内容: " + text);
    
    // 输入框清空
    ui->lineEdit->setText("");
}

void Widget::processResponse()
{
    // 读取响应
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    
    // 显示
    ui->listWidget->addItem("服务器响应: " + response);
}

2、TCP

QTcpServer

在这里插入图片描述
在这里插入图片描述

QTcpScket

在这里插入图片描述

回显服务器

服务端,放一个List Widget到界面上

// widget.h

#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(const QString request);

private:
    Ui::Widget *ui;
    QTcpServer* tcpServer;
};

// widget.cpp

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

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

    this->setWindowTitle("服务器");

    //创建QTcpServer实例
    tcpServer = new QTcpServer(this);

    // 接收响应的处理函数
    connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);

    // 绑定并监听端口号
    bool flag = tcpServer->listen(QHostAddress::Any, 9090);
    if (!flag)
    {
        QMessageBox::critical(this, "服务器启动失败!", tcpServer->errorString());
        exit(1);
    }
}

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

void Widget::processConnection()
{
    // 通过tcpServer拿到socket对象, 用于和客户端进行通信
    QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
    QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] 客户端上线!";
    ui->listWidget->addItem(log);

    // 处理客户端的请求
    connect(clientSocket, &QTcpSocket::readyRead, this, [=]() {
        // 返回的是QByteArray, 通过赋值转为QString
        QString request = clientSocket->readAll();
        // 由于是回显服务器, 请求就是响应
        const QString& response = process(request);
        clientSocket->write(response.toUtf8());
        QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] " + " req: " + request + ", resp: " + response;
        ui->listWidget->addItem(log);
    });
    
    // 客户端断开连接
    connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
        QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] 客户端下线!";
        ui->listWidget->addItem(log);
        
        // 释放clientSocket, 防止文件描述符泄漏
        // 非立即销毁, 而是通知Qt在下一个事件循环中销毁
        clientSocket->deleteLater();
    });
}

QString Widget::process(const QString request)
{
    return request;
}

客户端

在这里插入图片描述

// widget.h

#include <QWidget>
#include <QTcpSocket>

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;
    QTcpSocket* socket;
};

// widget.cpp

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

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

    this->setWindowTitle("客户端");
    socket = new QTcpSocket(this);
    // 和服务器建立连接
    // 这个函数非阻塞
    socket->connectToHost("127.0.0.1", 9090);
    connect(socket, &QTcpSocket::readyRead, this, [=]() {
        QString response = socket->readAll();
        ui->listWidget->addItem("服务器内容: " + response);
    });
    // 阻塞等待, 确认连接成功
    bool flag = socket->waitForConnected();
    if (!flag)
    {
        QMessageBox::critical(this, "连接服务器失败", socket->errorString());
        exit(1);
    }
}

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

void Widget::on_pushButton_clicked()
{
    const QString& text = ui->lineEdit->text();

    // 发送数据给服务器
    socket->write(text.toUtf8());

    // 显示
    ui->listWidget->addItem("客户端内容: " + text);

    // 清空输入框
    ui->lineEdit->setText("");
}

3、HTTP客户端

Qt只提供HTTP客户端。核心API:QNetworkAccessManager , QNetworkRequest , QNetworkReply。

QNetworkAccessManager

在这里插入图片描述

发起HTTP GET和POST请求,皆返回QNetworkReply 对象。

QNetworkRequest表示一个HTTP请求协议,不包含body。如果需要发送一个带有 body 的请求(比如 post),会在 QNetworkAccessManager 的 post 方法中通过单独的参数来传入body。

在这里插入图片描述

QVariant是一个类型可变的值。

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

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

QNetworkReply 信号 finished 会在客户端收到完整的响应数据之后触发。

在这里插入图片描述
在这里插入图片描述

// widget.h

#include <QWidget>
#include <QNetworkAccessManager>

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;
    QNetworkAccessManager* manager;
};

// widget.cpp

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

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

    this->setWindowTitle("客户端");
    manager = new QNetworkAccessManager(this);
}

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

void Widget::on_pushButton_clicked()
{
    // 获取输入框的url
    QUrl url(ui->lineEdit->text());

    // 构造HTTP请求对象
    QNetworkRequest request(url);

    // 发送请求
    // 如果是post, 第二个参数就是body
    // get不阻塞, 因为它不负责等待响应
    QNetworkReply* response = manager->get(request);

    // 信号槽处理响应
    connect(response, &QNetworkReply::finished, this, [=]() {
        if(response->error() == QNetworkReply::NoError)
        {
            QString html = response->readAll();
            ui->plainTextEdit->setPlainText(html);
        }
        else
            ui->plainTextEdit->setPlainText(response->errorString());
        response->deleteLater();
    });
}

4、音频

关于Qt的音视频,需要先引入multimedia多媒体模块才能正常运行。主要的类是QSound,只能播放.wav格式的音频文件

创建一个QWidget项目,使用qrc来引入音频文件

在这里插入图片描述

放一个按钮到界面上

// widget.h

#include <QWidget>
#include <QSound>

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;
    QSound* sound;
};

// widget.cpp

#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    sound = new QSound(":/audio.wav", this);
}

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

结束。


http://www.kler.cn/a/521557.html

相关文章:

  • 572. 另一棵树的子树
  • buu-pwn1_sctf_2016-好久不见29
  • 【C++动态规划 离散化】1626. 无矛盾的最佳球队|2027
  • Go反射指南
  • concurrent.futures.Future对象详解:利用线程池与进程池实现异步操作
  • IME关于输入法横屏全屏显示问题-Android14
  • mamba论文学习
  • HDFS安全模式
  • 「蓝桥杯题解」蜗牛(Java)
  • 全志开发板 视频输入框架
  • Rust:如何动态调用字符串定义的 Rhai 函数?
  • 基于Django的豆瓣影视剧推荐系统的设计与实现
  • MacOS 如何映射快捷键
  • 以太网详解(六)OSI 七层模型
  • MVCC底层原理实现
  • Java 注解与元数据
  • WPF基础 | 深入 WPF 事件机制:路由事件与自定义事件处理
  • LiteFlow Spring boot使用方式
  • qt-C++笔记之QLine、QRect、QPainterPath、和自定义QGraphicsPathItem、QGraphicsRectItem的区别
  • D. Unique Median【Codeforces Round 997 (Div. 2)】
  • 基于GS(Gaussian Splatting)的机器人Sim2Real2Sim仿真平台
  • 七种RAG架构cheat sheet!
  • BGP边界网关协议(Border Gateway Protocol)Community属性
  • RLHF技术演进:从理论突破到工程实践
  • 探索与创新:DeepSeek R1与Ollama在深度研究中的应用
  • 【PySide6快速入门】QListWidget 列表控件