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();
}

结束。

相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner12 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner13 天前
DicomViewer (目录调整) 2
qt
xcyxiner13 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
通信小呆呆15 天前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
H__Rick15 天前
自动对焦学习-3
人工智能·学习·计算机视觉