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