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

文章目录


和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文件里

bash 复制代码
QT       += core gui network

服务端

cpp 复制代码
// 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项目即可。界面

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

cpp 复制代码
// 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到界面上

cpp 复制代码
// 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;
}

客户端

cpp 复制代码
// 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 会在客户端收到完整的响应数据之后触发。


cpp 复制代码
// 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来引入音频文件

放一个按钮到界面上

cpp 复制代码
// 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();
}

结束。

相关推荐
lljss202028 分钟前
虚拟机里网络设置-桥接与NAT
服务器·网络·智能路由器
Stanford_11061 小时前
物联网智能项目之——智能家居项目的实现!
物联网·学习·微信小程序·智能家居·微信公众平台·twitter·微信开放平台
纠结哥_Shrek1 小时前
Q学习 (Q-Learning):基于价值函数的强化学习算法
学习·算法
pt10431 小时前
BGP分解实验·15——路由阻尼(抑制/衰减)实验
网络·智能路由器
Danileaf_Guo1 小时前
用BGP的路由聚合功能聚合大陆路由,效果显著不?
运维·网络·智能路由器
程序猿000001号1 小时前
网络安全技术简介
网络·安全·web安全
努力成为头发茂密的程序员2 小时前
(0基础版,无需输入代码爬取)新手小白初步学习八爪鱼采集器
数据库·学习·数据分析
小木_.3 小时前
【分析某音乐网站】分析一款音乐网站,并实现无限制的下载当前网站里所有的音乐
学习·分析·逆向分析
学问小小谢3 小时前
第23节课:前端调试技巧—掌握浏览器开发者工具与性能优化
前端·学习·安全·性能优化·交互·html5
漂亮_大男孩3 小时前
深度学习|表示学习|卷积神经网络|Padding(填充)的用处是什么?|12
深度学习·学习·cnn