目录
[1. Qt 网络](#1. Qt 网络)
[1.1 UDP Socket](#1.1 UDP Socket)
[1.2 TCP Socket](#1.2 TCP Socket)
[1.3 HTTP Client](#1.3 HTTP Client)
[2. Qt 音视频](#2. Qt 音视频)
[2.1 Qt 音频](#2.1 Qt 音频)
[2.2 Qt 视频](#2.2 Qt 视频)
1. Qt 网络
和多线程类似,Qt 为了支持跨平台, 对网络编程的 API 也进行了重新封装。
实际 Qt 开发中进行网络编程,也不一定使用 Qt 封装的网络 API,也有一定可能使用的是系统原生 API 或者其他第三方框架的 API。
在进行网络编程之前,需要在项目中的 .pro 文件中添加 network 模块。添加之后要手动编译一下项目,使 Qt Creator 能够加载对应模块的头文件。
为什么 Qt 要划分出这些模块呢?
因为Qt 本身是一个非常庞大,包罗万象的框架。如果把所有的 Qt 的功能都放到一起,既是我们就只是写一个简单的 hello world,那此时生成的可执行程序也会非常庞大(就包含了大量并没有使用的功能)。
模块化处理:
- 其它的功能分别封装成不同的模块。
- 默认情况下,这些额外的模块不会参与编译。
- 需要在 .pro 文件中引入对应的模块,才能把对于功能给编译加载进来。
1.1 UDP Socket
(1)核心 API 概览
主要的类有两个:QUdpSocket 和 QNetworkDatagram
QUdpSocket 表示一个 UDP 的 socket 文件。

QNetworkDatagram 表示一个 UDP 数据报。

(2)回显服务器
- 创建界面,包含一个 QListWidget 用来显示消息

- 创建 QUdpSocket 成员
不能直接添加头文件,否则无法编译通过,需要先添加网络模块。

修改 widget.h

修改 widget.cpp,完成 socket 后续的初始化
一般来说,要先连接信号槽,再绑定端口。如果顺序反过来,可能会出现端口绑定好了之后,请求就过来了,此时还没来得及连接信号槽,那么这个请求就有可能错过了。

实现 processRequest,完成处理请求的过程
- 读取请求并解析
- 根据请求计算响应
- 把响应写回到客户端

实现 process 函数
由于我们此处是实现回显服务器,所以 process 方法中并没有包含实质性的内容。

此时,服务器程序编写完毕,但是直接运行还看不出效果,还需要搭配客户端来使用。
(3)回显客户端
创建界面,包含一个 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 方法中并没有包含实质性的内容。

此时,服务器程序编写完毕,但是直接运行还看不出效果,还需要搭配客户端来使用。
(3)回显客户端
创建界面,包含一个 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 向服务器提交数据
(1)核心 API
关键类主要是三个:QNetworkAccessManager、QNetworkRequest、QNetworkReply。
QNetworkAccessManager 提供了 HTTP 的核心操作:

QNetworkRequest表示一个 HTTP 请求(不含 body)。
如果需要发送一个带有 body 的请求(比如 post),会在 QNetworkAccessManager 的 post 方法中通过单独的参数来传入 body。

其中的 QNetworkRequest::KnownHeaders 是一个枚举类型,常用取值:

QNetworkReply表示一个 HTTP 响应,这个类同时也是 QIODevice 的子类。

此外,QNetworkReply 还有一个重要的信号 finished 会在客户端收到完整的响应数据之后触发。
(2)给服务器发送一个 GET 请求
创建界面,包含一个 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 。
(1)核心 API 概览

(2)示例

2.2 Qt 视频
在 Qt 中,视频播放的功能主要是通过 QMediaPlayer 类和 QVideoWidget类来实现。在使用这两个类时要添加对应的模块 multimedia 和 multimediawidgets。
(1)核心 API 概览

(2)示例
首先在 .pro 文件中添加 multimedia 和 multimediawidgets 两个模块。
如下图示:

widget.h:
cpp
#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 :
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。