

目录
一:🔥 Qt 网络概述
和多线程类似, Qt 为了支持跨平台,对网络编程的 API 也进行了重新封装.
💡 注意:
实际 Qt 开发中进行网络编程,也不一定使用 Qt 封装的网络 API,也有一定可能使用的是系统原生 API 或者其他第三方框架的 API.
在进行网络编程之前,需要在项目中的 .pro 文件
中添加 network
模块,
- 添加之后要手动编译一下项目,使 Qt Creator 能够加载对应模块的头文件
二:🔥 UDP Socket
主要的类就两个:QUdpSocket
和 QNetworkDatagram
QUdpSocket
表示一个 UDP 的 socket 文件
名称 | 类型 | 说明 | 对标原生 API |
---|---|---|---|
bind(const QHostAddress&, quint16) | 方法 | 绑定指定端口号 | bind |
receiveDatagram() | 方法 | 返回 QNetworkDatagram ,读取一个 UDP 数据报 | recvfrom |
writeDatagram(const QNetworkDatagram&) | 方法 | 发送一个 UDP 数据报 | sendto |
readyRead | 信号 | 在收到数据并准备就绪后触发 | 无(类似于 IO 多路复用的通知机制) |
readyRead
:当 socket 收到请求的时候,QUdpSocket
就会触发这个信号,此时就可以在槽函数中完成读取请求的操作了
基于信号槽,就天然达成了 事件驱动 的效果
QNetworkDatagram
表示一个 UDP 数据报
名称 | 类型 | 说明 | 对标原生 API |
---|---|---|---|
QNetworkDatagram(const QByteArray&, const QHostAddress&, quint16) | 构造函数 | 通过 QByteArray ,目标 IP 地址,目标端口号 构造一个 UDP 数据报,通常用于发送数据时 | 无 |
data() | 方法 | 获取数据报内部持有的数据,返回 QByteArray | 无 |
senderAddress() | 方法 | 获取数据报中包含的对端的 IP 地址 | 无,recvfrom 包含了该功能 |
senderPort() | 方法 | 获取数据报中包含的对端的端口号 | 无,recvfrom 包含了该功能 |
🦋 代码示例
回显服务器
1)创建界面,包含一个 QListWidget
用来显示消息
2)创建 QUdpSocket
成员
修改 widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
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);
// 3. 连接信号槽, 处理收到的请求
connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
// 4. 绑定端⼝
bool ret = socket->bind(QHostAddress::Any, 9090);
if (!ret) {
QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());
return;
}
}
3)实现 processRequest
,完成处理请求的过程
- 读取请求并解析
- 根据请求计算响应
- 把响应写回到客户端
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)创建界面.包含一个 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,定义成员
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 创建 socket 成员
QUdpSocket* socket;
};
4)给发送按钮 slot 函数,实现发送请求
void Widget::on_pushButton_clicked()
{
// 1. 获取到输⼊框的内容
const QString& text = ui->lineEdit->text();
// 2. 构造请求数据
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, [=]() {
const QNetworkDatagram responseDatagram = socket->receiveDatagram();
QString response = responseDatagram.data();
ui->listWidget->addItem(QString("服务器说: ") + response);
});
结果如下:
三:🔥 TCP Socket
主要的类就两个:QTcpSocket
和 QTcpServer
QTcpSocket
用于监听端口,获取客户端连接
名称 | 类型 | 说明 | 对标原生 API |
---|---|---|---|
listen(const QHostAddress&, quint16 port) | 方法 | 绑定指定地址和端口号,并开始监听 | bind 和 listen |
nextPendingConnection() | 方法 | 从系统中获取到⼀个已经建立好的 tcp 连接.返回⼀个 QTcpSocket , 表示这个客户端的连接.通过这个socket对象完成和客户端 之间的通信. | accept |
newConnection | 信号 | 有新的客户端端建立连接好之后触发 | 无(但类似于 IO 多路复用中的通知机制) |
QTcpSocket
用于 客户端 和 服务器之间的数据交互
名称 | 类型 | 说明 | 对标原生 API |
---|---|---|---|
readAll() | 构造函数 | 读取当前接收缓冲区中的所有数据 返回 QByteArray 对象 | read |
write(const QByteArray&) | 方法 | 把数据写入 socket 中 | write |
deleteLater | 方法 | 暂时把 socket 对象标记为无效.Qt 会在下个事件循环中析构释放该对 象. | 无(但类似于 “半自动化的垃圾回收”) |
readyRead() | 信号 | 有数据到达并准备就绪时触发 | 无(但类似于 IO 多路复用中的通知机制) |
🦋 代码示例
回显服务器
1)创建界面,包含一个 QListWidget
用来显示消息
2)创建 QTcpSocket
并且初始化
修改 widget.h
,添加 QTcpServer
指针成员
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QTcpSocket* socket;
};
修改 widget.cpp,实例化 QTcpServer 并进行后续初始化操作,
- 设置窗口标题
- 实例化 TCP server.(父元素设为当前控件,会在父元素销毁时被一起销毁),
- 通过信号槽,处理客户端建立的新连接
- 监听端口
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 设置窗⼝标题
this->setWindowTitle("服务器");
// 2. 实例化 Tcp socket
tcpsocket = new TcpSocket(this);
// 3. 通过信号槽, 处理客⼾端建⽴的新连接.
connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);
// 4. 监听端⼝
bool ret = tcpServer->listen(QHostAddress::Any, 9090);
if (!ret) {
QMessageBox::critical(nullptr, "服务器启动失败!", tcpServer->errorString());
exit(1);
}
}
3)继续修改 widget.cpp,实现处理连接的具体方法 processconnection
- 获取到新的连接对应的 socket.
- 通过信号槽,处理收到请求的情况
- 通过信号槽,处理断开连接的情况
void Widget::processConnection()
{
// 1. 获取到新的连接对应的 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());
QString log = QString("[") + clientSocket->peerAddress().toString()
+ ":" + QString::number(clientSocket->peerPort()) + "] req: " +
request + ", resp: " + response;
ui->listWidget->addItem(log);
});
// 3. 通过信号槽, 处理断开连接的情况
connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
QString log = QString("[") + clientSocket->peerAddress().toString()
+ ":" + QString::number(clientSocket->peerPort()) + "] 客⼾端下线!";
ui->listWidget->addItem(log);
// 删除 clientSocket
clientSocket->deleteLater();
});
}
4)实现 process
函数
由于 我们此处是实现回显服务器,所以 process 方法中并没有包含实质性的内容
QString Widget::process(const QString& request)
{
return request;
}
此时,服务器程序编写完毕。但是直接运行还看不出效果,还需要搭配客户端来使用
回显客户端
1)创建界面.包含一个 QLineEdit,QPushButton,QListwidget
- 先使用水平布局把
QLineEdit
和QPushButton
放好,并设置这两个控件的垂直方向的sizePolicy
为Expanding
- 再使用垂直布局把
QListwidget
和上面的水平布局放好 - 设置垂直布局的
layoutStretch
为5,1(当然这个尺寸比例根据个人喜好微调)
2)创建QTcpSocket 并实例化,修改widget.h创建成员
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 创建 QTcpsocket
QTcpSocket* socket;
};
修改 widget.cpp,对 QTcpSocket 进行实例化,
- 设置窗口标题
- 实例化 socket 对象(父元素设为当前控件,会在父元素销毁时被一起销毁)
- 和服务器建立连接
- 等待并确认连接是否出错
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();
// 清空输⼊框内容
ui->lineEdit->setText("");
// 把消息显⽰到界⾯上
ui->listWidget->addItem(QString("客⼾端说: ") + text);
// 发送消息给服务器
socket->write(text.toUtf8());
}
4)再次修改 Widget 的构造函数,通过信号槽处理服务器响应
// 处理服务器返回的响应.
connect(socket, &QTcpSocket::readyRead, this, [=]() {
QString response = socket->readAll();
qDebug() << response;
ui->listWidget->addItem(QString("服务器说: ") + response);
});
先启动服务器,再启动客⼾端(可以启动多个),最终执行效果:
- 由于我们使用信号槽处理同⼀个客户端的多个请求,不涉及到循环,也就不会使客户端之间相互影响 了.
三:🔥 Http Client
进行 Qt 开发时,和服务器之间的通信很多时候也会用到 HTTP 协议.
- 通过 HTTP 从服务器获取数据
- 通过 HTTP 向服务器提交数据,
🦋 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::KnowHeaders header, const QVariant &value) | 设置请求头 |
其中的 QNetworkRequest::KnownHeaders
是⼀个枚举类型,常用取值:
取值 | 说明 |
---|---|
ContentTypeHeader | 描述 body 类型 |
ContentLengthHeader | 描述 body 长度 |
LocationHeader | 用于重定向报文中指定重定向地址(响应中使用,请求用不到) |
CookieHeader | 设置 Cookie |
UserAgenHeader | 设置 User-Agent |
QNetworkReply
表示⼀个HTTP响应。这个类同时也是 QIODevice
的⼦类
方法 | 说明 |
---|---|
error() | 获取出错状态 |
errorString() | 获取出错原因的文本 |
readAll() | 读取响应 body |
header(QNetworkRequest::KnownHeaders header) | 读取响应指定 header 的值 |
此外:QNetworkReply
还有⼀个重要的信号 finished
会在客户端收到完整的响应数据之后触发.
🦋 代码示例
1)创建界面:包含一个 QLineEdit,QPushButton、QPlainTextEidt
- 先使用水平布局把
QLineEdit
和QPushButton
放好,并设置这两个控件的垂直方向的sizePolicy
为Expanding
- 再使用垂直布局把
QPlainTextEidt
放好(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:
Ui::Widget *ui;
QNetworkAccessManager* manager;
};
3)修改 widget.cpp 创建实例
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 实例化属性
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);
}
else{
ui->plainTextEdit->setPlainText(response->errorString());
}
response->deleteLater();
});
}
执行程序,观察效果
四:🔥 【Qt】音视频
🔥 在Qt中,视频播放的功能主要是通过 QMediaPlayer
类和 QVideoWidget
类来实现。在使用这两个类时要添加对应的模块 multimedia
和 multimediawidgets
核心 API 概述
名称 | 作用 |
---|---|
setMedia() | 设置当前媒体源 |
setVideoOutput() | 将 QVideoWidget 视频输出附加到媒体播放器,如果媒体播放器已经添加了视频输出,将更换一个新的 |
代码示例
首先在 .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】网络
的理解, 觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~ 😉