目录
1. Qt 网络
和多线程类似,Qt 为了支持跨平台, 对网络编程的 API 也进行了重新封装。
实际 Qt 开发中进行网络编程,也不一定使用 Qt 封装的网络 API,也有一定可能使用的是系统原生 API 或者其他第三方框架的 API。
在进行网络编程之前,需要在项目中的 .pro 文件中添加 network 模块。添加之后要手动编译一下项目,使 Qt Creator 能够加载对应模块的头文件。
为什么 Qt 要划分出这些模块呢?
因为Qt 本身是一个非常庞大,包罗万象的框架。如果把所有的 Qt 的功能都放到一起,既是我们就只是写一个简单的 hello world,那此时生成的可执行程序也会非常庞大(就包含了大量并没有使用的功能)。
模块化处理:
- 其它的功能分别封装成不同的模块。
- 默认情况下,这些额外的模块不会参与编译。
- 需要在 .pro 文件中引入对应的模块,才能把对于功能给编译加载进来。
1.1 UDP Socket
主要的类有两个:QUdpSocket 和 QNetworkDatagram
QUdpSocket 表示一个 UDP 的 socket 文件。
QNetworkDatagram 表示一个 UDP 数据报。
1. 创建界面,包含一个 QListWidget 用来显示消息
2. 创建 QUdpSocket 成员
不能直接添加头文件,否则无法编译通过,需要先添加网络模块。
修改 widget.h
修改 widget.cpp,完成 socket 后续的初始化
一般来说,要先连接信号槽,再绑定端口。如果顺序反过来,可能会出现端口绑定好了之后,请求就过来了,此时还没来得及连接信号槽,那么这个请求就有可能错过了。
实现 processRequest,完成处理请求的过程
- 读取请求并解析
- 根据请求计算响应
- 把响应写回到客户端
实现 process 函数
由于我们此处是实现回显服务器,所以 process 方法中并没有包含实质性的内容。
此时,服务器程序编写完毕,但是直接运行还看不出效果,还需要搭配客户端来使用。
创建界面,包含一个 QLineEdit、QPushButton、QListWidget
- 先使用水平布局把 QLineEdit 和 QPushButton 放好,并设置这两个控件的垂直方向的 sizePolicy 为 Expanding。
- 再使用垂直布局把 QListWidget 和上面的水平布局放好。
- 设置垂直布局的 layoutStretch 为 5,1(这个尺寸比例可以根据个人喜好微调)。
在 widget.cpp 中,先创建两个全局常量,表示服务器的 IP 和端口
端口到本质上是一个 2 字节的无符号整数。
quint16:本质上就是一个 unsigned short(虽然 short 通常都是 2 个字节,但是 C++ 标准中没有明确规定这一点,只是说 short 不应该少于 2 个字节)。
创建 QUdpSocket 成员:
修改 widget.h,定义成员
修改 widget.cpp,初始化 socket
给发送按钮 slot 函数,实现发送请求
再次修改 Widget 的构造函数,通过信号槽来处理服务器的响应
最终执行效果
客户端服务器测试的基本原则:一定是先启动服务器,后启动客户端。
启动多个客户端都可以正常工作,但是不能在界面选择直接运行,否则会覆盖上一个客户端。
1.2 TCP Socket
(1)核心 API 概览
核心类是两个:QTcpServer 和 QTcpSocket。
QTcpServer 用于监听端口,和获取客户端连接。
QTcpSocket 用户客户端和服务器之间的数据交互。
QByteArray 用于表示一个字节数组,可以很方便的和 QString 进行相互转换。
例如:
- 使用 QString 的构造函数即可把 QByteArray 转成 QString
- 使用 QString 的 toUtf8 函数即可把 QString 转成 QByteArray
(2)回显服务器
创建界面,包含一个 QListWidget,用于显示收到的数据
创建 QTcpServer 并初始化
修改 widget.h,添加 QTcpServer 指针成员
修改 widget.cpp,实例化 QTcpServer 并进行后续初始化操作
- 设置窗口标题
- 实例化 TCP server(父元素设为当前控件,会在父元素销毁时被一起销毁)
- 通过信号槽,处理客户端建立的新连接
- 监听端口
继续修改 widget.cpp,实现处理连接的具体方法 processConnection
- 获取到新的连接对应的 socket
- 通过信号槽,处理收到请求的情况
- 通过信号槽,处理断开连接的情况
上述代码其实不够严谨,但在这里作为回显服务器已经够了。实际在使用 TCP 的过程中,TCP 是面向字节流的,一个完整的请求可能会分成多段字节数组进行传输。虽然 TCP 已经帮我们处理了很多棘手的问题,但是 TCP 本身并不负责区分从哪里到哪里是一个完整的应用层数据(粘包问题)。
更严谨的做法:每次收到的数据都给它放到一个字节数组缓冲区中,并且提前约定好应用层协议的格式(分隔符 / 长度 / 其他办法),再按照协议格式对缓冲区数据进行更细致的解析处理。
实现 process 方法,实现根据请求处理响应
由于此处是实现回显服务器,所以 process 方法中并没有包含实质性的内容。
此时,服务器程序编写完毕,但是直接运行还看不出效果,还需要搭配客户端来使用。
创建界面,包含一个 QLineEdit、QPushButton、QListWidget
- 先使用水平布局把 QLineEdit 和 QPushButton 放好,并设置这两个控件的垂直方向的 sizePolicy 为 Expanding
- 再使用垂直布局把 QListWidget 和上面的水平布局放好
- 设置垂直布局的 layoutStretch 为 5,1(这个尺寸比例可以根据个人喜好微调)
创建 QTcpSocket 并实例化
修改 widget.h,创建成员
修改 widget.cpp,对 QTcpSocket 进行实例化
- 设置窗口标题
- 实例化 socket 对象(父元素设为当前控件,会在父元素销毁时被一起销毁)
- 和服务器建立连接
- 等待并确认连接是否出错
修改 widget.cpp,给按钮增加点击的 slot 函数,实现发送请求给服务器
修改 widget.cpp 中的 Widget 构造函数,通过信号槽处理收到的服务器的响应
先启动服务器,再启动客户端(可以启动多个),最终执行效果:
由于我们使用信号槽处理同一个客户端的多个请求,不涉及到循环,也就不会使客户端之间相互影响了。
1.3 HTTP Client
进行 Qt 开发时,和服务器之间的通信很多时候也会用到 HTTP 协议。
- 通过 HTTP 从服务器获取数据
- 通过 HTTP 向服务器提交数据
关键类主要是三个:QNetworkAccessManager、QNetworkRequest、QNetworkReply。
QNetworkAccessManager 提供了 HTTP 的核心操作:
QNetworkRequest 表示一个 HTTP 请求(不含 body)。
如果需要发送一个带有 body 的请求(比如 post),会在 QNetworkAccessManager 的 post 方法中通过单独的参数来传入 body。
其中的 QNetworkRequest::KnownHeaders 是一个枚举类型,常用取值:
QNetworkReply 表示一个 HTTP 响应,这个类同时也是 QIODevice 的子类。
此外,QNetworkReply 还有一个重要的信号 finished 会在客户端收到完整的响应数据之后触发。
创建界面,包含一个 QLineEdit、QPushButton
- 先使用水平布局把 QLineEdit 和 QPushButton 放好,并设置这两个控件的垂直方向的 sizePolicy 为 Expanding。
- 再使用垂直布局把 QPlainTextEdit 和上面的水平布局放好(QPlainTextEdit 的 readOnly 设为 true)。
- 设置垂直布局的 layoutStretch 为 5,1(这个尺寸比例可以根据个人喜好微调)。
注意:此处建议使用 QPlainTextEdit,而不是 QTextEdit。主要是因为 QTextEdit 要进行富文本解析,最终显示的结果就不是原始的 HTML 了,如果得到的 HTTP 响应体积很大,会导致界面渲染缓慢甚至被卡住。
修改 widget.h,创建 QNetworkAccessManager 属性
修改 widget.cpp,创建实例
编写按钮的 slot 函数,实现发送 HTTP 请求功能
执行程序,观察效果:
发送 POST 请求代码也是类似,使用 manager->post() 即可。
实际开发中,HTTP Client 获取到的的数据也不一定非得是 HTML,更大的可能性是客户端开发和服务器开发约定好交互的数据格式。按照约定的格式,客户端拿到之后进行解析,并显示到界面上。
Qt 中还提供了 FTP、DNS、SSL 等网络相关的组件工具,这里不做过多介绍。
2. Qt 音视频
2.1 Qt 音频
在 Qt 中,音频主要是通过 QSound 类来实现。但是需要注意的是 QSound 类只⽀持播放 wav 格式的音频文件。也就是说如果想要添加音频效果,那么首先需要将非 wav 格式的音频文件转换为 wav 格式。
通过帮助手册查看 QSound 类如下:
注意:使用 QSound 类时,需要添加模块: multimedia 。
2.2 Qt 视频
在 Qt 中,视频播放的功能主要是通过 QMediaPlayer 类和 QVideoWidget 类来实现。在使用这两个类时要添加对应的模块 multimedia 和 multimediawidgets。
(1)核心 API 概览
首先在 .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开发⑫Qt界面优化之CSS。