文章目录
-
- [1. Qt 事件](#1. Qt 事件)
- [1.1 事件介绍](#1.1 事件介绍)
- [1.2 事件的处理](#1.2 事件的处理)
- [1.3 按键事件](#1.3 按键事件)
-
- [1.3.1 单个按键](#1.3.1 单个按键)
- [1.4 ⿏标事件](#1.4 ⿏标事件)
-
- [1.4.1 ⿏标单击事件](#1.4.1 ⿏标单击事件)
- [1.4.2 ⿏标释放事件](#1.4.2 ⿏标释放事件)
- [1.4.3 ⿏标双击事件](#1.4.3 ⿏标双击事件)
- [.4.4 ⿏标移动事件](#.4.4 ⿏标移动事件)
- [1.4.5 滚轮事件](#1.4.5 滚轮事件)
- [1.5 定时器](#1.5 定时器)
-
- [1.5.1 QTimerEvent 类](#1.5.1 QTimerEvent 类)
- [1.5.2 QTimer 类](#1.5.2 QTimer 类)
- [1.5.3 获取系统⽇期及时间](#1.5.3 获取系统⽇期及时间)
- [1.6 事件分发器](#1.6 事件分发器)
-
- [1.6.1 概述](#1.6.1 概述)
- [1.6.2 事件分发器⼯作原理](#1.6.2 事件分发器⼯作原理)
- [1.7 事件过滤器](#1.7 事件过滤器)
- 扩展
- [2. Qt ⽂件](#2. Qt ⽂件)
-
- [2.1 Qt ⽂件概述](#2.1 Qt ⽂件概述)
- [2.2 输⼊输出设备类](#2.2 输⼊输出设备类)
- [2.3 ⽂件读写类](#2.3 ⽂件读写类)
- [2.4 ⽂件和⽬录信息类](#2.4 ⽂件和⽬录信息类)
- [3. Qt 多线程](#3. Qt 多线程)
-
- [3.1 Qt 多线程概述](#3.1 Qt 多线程概述)
- [3.2 QThread 常⽤ API](#3.2 QThread 常⽤ API)
- [3.3 使⽤线程](#3.3 使⽤线程)
- [3.4 线程安全](#3.4 线程安全)
-
- [3.4.1 互斥锁](#3.4.1 互斥锁)
- [3.4.2 条件变量](#3.4.2 条件变量)
- [3.4.3 信号量](#3.4.3 信号量)
- [4. Qt ⽹络](#4. Qt ⽹络)
- [4.1 UDP Socket](#4.1 UDP Socket)
-
- [4.1.2 回显服务器](#4.1.2 回显服务器)
- [4.1.3 回显客⼾端](#4.1.3 回显客⼾端)
- [4.2 TCP Socket](#4.2 TCP Socket)
-
- [4.2.1 核⼼ API 概览](#4.2.1 核⼼ API 概览)
- [4.2.3 回显客⼾端](#4.2.3 回显客⼾端)
- [4.3 HTTP Client](#4.3 HTTP Client)
-
- [4.3.2 代码⽰例](#4.3.2 代码⽰例)
- [4.4 其他模块](#4.4 其他模块)
- [5. Qt ⾳视频](#5. Qt ⾳视频)
-
- [5.1 Qt ⾳频](#5.1 Qt ⾳频)
-
- [5.1.1 核⼼API概览](#5.1.1 核⼼API概览)
- [5.1.2 ⽰例](#5.1.2 ⽰例)
- [5.2 Qt 视频](#5.2 Qt 视频)
-
- [5.2.1 核⼼API概览](#5.2.1 核⼼API概览)
- [5.2.2 ⽰例](#5.2.2 ⽰例)
- 总结
1. Qt 事件
类比
信号槽进行的各种操作,就可能产生出信号,可以给某个信号指定槽函数,当信号触发时,就能自动执行到对应的槽函数。
用户进行的各种操作,也会产生事件,程序员同样可以给事件关联上处理函数(处理的逻辑),当事件触发的时候,就能够执行到对应的代码。
信号槽就是对于事件的进一步封装。
事件是信号槽的底层机制。
实际开发程序过程中,绝大部分和用户进行的交互都是通过"信号槽"来完成的,有些特殊情况下,信号槽不一定能搞定,(某个用户的动作行为,Qt没有提供对应的信号...)此时需要通过重写事件处理函数的形式,来手动处理事件的响应逻辑。
开发事件机制给咱们程序员,咱们程序员就可以根据实际的需要进行更深度的定制化DIY操作。
用户进行了很多操作,就会产生很多的事件(当然也会产生很多信号)。
1.1 事件介绍
**事件是应⽤程序内部或者外部产⽣的事情或者动作的统称。**在 Qt 中使⽤⼀个对象来表⽰⼀个事件。所有的 Qt 事件均继承于抽象类 QEvent。事件是由系统或者 Qt 平台本⾝在不同的时刻发出的。当⽤⼾按下⿏标、敲下键盘,或者是窗⼝需要重新绘制的时候,都会发出⼀个相应的事件。⼀些事件是在⽤⼾操作时发出,如键盘事件、⿏标事件等,另⼀些事件则是由系统本⾝⾃动发出,如定时器事件。常⻅的 Qt 事件如下:

由上图可知,不同场景下,需要关注的点是不一样的。这些事件的子类中就会包含一些对应的不同属性。
常⻅事件描述:
事件名称 描述
⿏标事件 ⿏标左键、⿏标右键、⿏标滚轮,⿏标的移动,⿏标按键的按下和松开
键盘事件 按键类型、按键按下、按键松开
定时器事件 定时时间到达 
进⼊离开事件 ⿏标的进⼊和离开
滚轮事件 ⿏标滚轮滚动
绘屏事件 重绘屏幕的某些部分

显⽰隐藏事件 窗⼝的显⽰和隐藏
移动事件 窗⼝位置的变化
窗⼝事件 是否为当前窗⼝
⼤⼩改变事件 窗⼝⼤⼩改变
焦点事件 键盘焦点移动
拽事件 ⽤⿏标进⾏拖拽

1.2 事件的处理
事件处理⼀般常⽤的⽅法为:重写相关的 Event 函数。
让一段代码和某个事件关联起来。当事件触发的时候,就能指定到这段代码。
事件的处理 :让当前的类,重写某个事件处理函数。
某个事件处理函数:这里运用的是"多态"机制创建子类,继承自Qt已有的类。在子类中重写父类的事件处理函数;后续事件触发过程中,就会通过多态这样的机制,执行到咱们自己写的子类的函数中。
在 Qt 中,⼏乎所有的 Event 函数都是虚函数,所以可以重新实现。如:在实现⿏标的进⼊和离开事件时,直接重新实现 enterEvent() 和 leaveEvent() 即可。enterEvent() 和 leaveEvent() 函数原型如下


⽰例1:
1、新建 Qt 项⽬,基类选择 QWidget,同时勾选 UI 界⾯⽂件,如下图⽰;

2、设计 UI ⽂件,如下图⽰;

3、在项⽬中新添加⼀个类:MyLabel;
先选中项⽬名称 QEvent,点击⿏标右键,选择 add new ... ,弹出如下对话框:

4、选择:Choose ... ,弹出如下界⾯:

5、此时项⽬中会新添加以下两个⽂件:

6、在帮助⽂档中查找对应的内容;

7、点击 "显⽰" 之后,出现如下内容:

8、复制 enterEvent() ,粘贴在项⽬⽂件 "mylabel.h" 中;

9、重写 enterEvent() ⽅法;

10、在 UI ⽂件中选中 Label,右键 ------> 提升为...

11、当点击 "提升为... " 之后,弹出如下对话框:

12、修改基类:

13、执⾏效果如下:当⿏标进⼊设计好的标签之后,就会在应⽤程序输出栏中打印:⿏标进⼊

实践(重写enterevent和leaveevent):
cpp
复制代码
#include "label.h"
#include<QDebug>
Label::Label(QWidget*parent):QLabel(parent)//继承关系
{
}
void Label::enterEvent(QEvent *event)
{
(void) event;
qDebug()<<"enterEvent";
}
void Label::leaveEvent(QEvent *event)
{
(void) event;
qDebug()<<"leaveEvent";
}
⽰例2:当⿏标点击时,获取对应的坐标值;
1、在上述⽰例的基础上,在 mylabel.h 中声明 mousePressEvent() ⽅法;

2、在 mylabel.cpp 中重写 mousePressEvent() ⽅法;

实现效果如下:

⽰例:⿏标左键点击时,打印对应的坐标值,⿏标右键点击时,打印基于屏幕的坐标

1.3 按键事件
Qt 中的按键事件是通过 QKeyEvent 类来实现的。当键盘上的按键被按下或者被释放时,键盘事件便
会触发。在帮助⽂档中查找 QKeyEvent 类如下:

查找按键事件中所有的按键类型:在帮助⽂档中输⼊:Qt::Key,如下图:
1.3.1 单个按键
⽰例:当某个按键被按下时,输出:某个按键被按下了;
1、新建项⽬,在头⽂件 "widget.h" 中声明虚函数 keyPressEvent();如下图:

2、在 "widget.cpp" ⽂件中重写 keyPressEvent() 虚函数;

1.3.2 组合按键
在 Qt 助⼿中搜索:Qt::KeyboardModifier,如下图⽰:

**Qt::KeyboardModifier 中定义了在处理键盘事件时对应的修改键。**在 Qt 中,键盘事件可以与修改键⼀起使⽤,以实现⼀些复杂的交互操作。KeyboardModifier 中修改键的具体描述如下:
Qt::NoModifier ⽆修改键
Qt::ShiftModifier Shift 键
Qt::ControlModifier Ctrl 键
Qt::AltModifier Alt 键
Qt::MetaModifier Meta键(在Windows上指Windows键,在macOS上指Command键)
Qt::KeypadModifier 使⽤键盘上的数字键盘进⾏输⼊时,Num Lock键处于打开状态
Qt::GroupSwitchModifier ⽤于在输⼊法 组之间 切换
⽰例:

键盘实践:
cpp
复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QKeyEvent>
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::keyPressEvent(QKeyEvent *event)
{
//qDebug()<<event->key();
if(event->key()==Qt::Key_A&&event->modifiers()==Qt::ControlModifier)//按下a和ctrl
{
qDebug()<<"按下ctrl+A键";
}
}
1.4 ⿏标事件
在 Qt 中,⿏标事件是⽤ QMouseEvent 类来实现的。当在窗⼝中按下⿏标或者移动⿏标时,都会产⽣
⿏标事件。
利⽤ QMouseEvent 类可以获取⿏标的哪个键被按下了以及⿏标的当前位置等信息。在 Qt 帮助⽂档中
查找QMouseEvent类 如下图⽰:

1.4.1 ⿏标单击事件
在 Qt 中,⿏标按下是通过虚函数 mousePressEvent() 来捕获的。mousePressEvent() 函数原型如
下:
virtual protected\] void QWidget::mousePressEvent(QMouseEvent \*event)
⿏标左右键及滚的表⽰如下:
Qt::LeftButton ⿏标左键
Qt::RightButton ⿏标右键
Qt::MidButton ⿏标滚轮
⽰例1:⿏标左键
1、在 "widget.h" 头⽂件中声明⿏标按下事件;

2、在 "widget.cpp" ⽂件中重新实现 mousePressEvent() 函数;

实现效果如下:

⽰例2:⿏标右键:

鼠标按下事件的实现:
```cpp
#include "label.h"
#include
#include
Label::Label(QWidget*parent):QLabel(parent)
{
}
void Label::mousePressEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton)
{
qDebug()<<"按下左键";
}
else if(event->button()==Qt::RightButton)
{
qDebug()<<"按下右键";
}
//当前event对象就包含了鼠标点击位置的坐标
qDebug()<x()<<","<y();
//globalX和globalY是以屏幕左上方为原点,获取的坐标
qDebug()<globalX()<<","<globalY();
}
```
实现效果如下:

⽰例3:⿏标滚轮

实现效果如下:

#### 1.4.2 ⿏标释放事件
⿏标释放事件是通过虚函数 **mouseReleaseEvent()** 来捕获的。mouseReleaseEvent() 函数原型如
下:\[virtual protected\] void QWidget::mouseReleaseEvent(QMouseEvent \*event)
⽰例:

执⾏效果如下:

#### 1.4.3 ⿏标双击事件
⿏标双击事件是通过虚函数:**mouseDoubleClickEvent()** 来实现的。mouseDoubleClickEvent()
函数原型如下:
\[virtual protected\] void QWidget::mouseDoubleClickEvent(QMouseEvent \*event)
⽰例:⿏标左键双击

执⾏效果如下:

鼠标释放和双击


```cpp
#include "label.h"
#include
#include
Label::Label(QWidget*parent):QLabel(parent)
{
}
void Label::mousePressEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton)
{
qDebug()<<"按下左键";
}
else if(event->button()==Qt::RightButton)
{
qDebug()<<"按下右键";
}
//当前event对象就包含了鼠标点击位置的坐标
qDebug()<x()<<","<y();
//globalX和globalY是以屏幕左上方为原点,获取的坐标
qDebug()<globalX()<<","<globalY();
}
void Label::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton)
{
qDebug()<<"释放左键";
}
else if(event->button()==Qt::RightButton)
{
qDebug()<<"释放右键";
}
}
void Label::mouseDoubleClickEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton)
{
qDebug()<<"双击左键";
}
else if (event->button()==Qt::RightButton)
{
qDebug()<<"双击右键";
}
}
```

#### .4.4 ⿏标移动事件
⿏标移动事件是通过虚函数:mouseMoveEvent() 来实现的。同时为了实时捕获⿏标位置信息,需要通过函数 setMouseTracking() 来追踪⿏标的位置。mouseMoveEvent()函数原型如下:
\[virtual protected\] void QWidget::mouseMoveEvent(QMouseEvent \*event)
setMouseTracking()函数原型如下:
void setMouseTracking(bool enable)
说明:
setMouseTracking() 函数默认是 false,需要设置为 true,才能实时捕获⿏标位置信息。否则只有当⿏标按下时才能捕获其位置信息。
⽰例:

执⾏效果:

eg:
```cpp
#include "widget.h"
#include "ui_widget.h"
#include
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//把这个选项设置为true,才能实现追踪鼠标的移动位置
this->setMouseTracking(true);
}
Widget::~Widget()
{
delete ui;
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
qDebug()<x()<y();
}
```

#### 1.4.5 滚轮事件
在 Qt 中,⿏标滚轮事件是通过 QWheelEvent 类来实现的。滚轮滑动的距离可以通过 delta() 函数获
取。delta() 函数原型如下:
int QGraphicsSceneWheelEvent::delta() const
**其中返回值代表滚轮滑动的距离** 。正数表⽰滚轮相对于⽤⼾向前滑动,负数表⽰滚轮相对于⽤⼾向后
滑动。
⽰例:

执⾏效果如下:

eg:
```cpp
#include "widget.h"
#include "ui_widget.h"
#include
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
total =0;
}
Widget::~Widget()
{
delete ui;
}
void Widget::wheelEvent(QWheelEvent *event)
{
total+=event->delta();
qDebug()<setupUi(this);
//开启定时器事件
//此处timerId是一个定时器的身份标识
timerId = this->startTimer(1000);//每秒减一
}
Widget::~Widget()
{
delete ui;
}
void Widget::timerEvent(QTimerEvent *event)
{
//如果一个程序中存在多个定时器(startTimer创建的定时器),此时每个定时器都会触发timeEvent函数
//先判断一下这次触发是否是想要的定时器触发的
if(event->timerId()!=this->timerId)//?
{
//如果不是我们的定时器触发的,就直接忽略
//当前程序中只有这一个定时器
return;
}
//是咱们自己搞得定时器
int value = ui->lcdNumber->intValue();//在lcd
if(value<=0)
{
//停止定时器
this->killTimer(this->timerId);//
}
value-=1;
ui->lcdNumber->display(value);//lcd 是display
}
```
### 1.6 事件分发器
#### 1.6.1 概述
在 Qt 中,事件分发器(Event Dispatcher) 是⼀个核⼼概念,⽤于处理 GUI 应⽤程序中的事件。事件分
发器负责将事件从⼀个对象传递到另⼀个对象,直到事件被处理或被取消。每个继承⾃ QObject类 或
QObject类 本⾝都可以在本类中重写 bool event(QEvent \*e) 函数,来实现相关事件的捕获和拦截。
#### 1.6.2 事件分发器⼯作原理
在 Qt 中,我们发送的事件都是传给了 QObject 对象,更具体点是传给了 QObject 对象的 event() 函
数。所有的事件都会进⼊到这个函数⾥⾯,那么我们处理事件就要重写这个 event() 函数。event() 函
数本⾝不会去处理事件,⽽是根据 事件类型(type值)调⽤不同的事件处理函数。事件分发器就是⼯
作在应⽤程序向下分发事件的过程中,如下图:

如上图,事件分发器⽤于分发事件。在此过程中,事件分发器也可以做拦截操作。事件分发器主要是
通过 bool event(QEvent \*e) 函数来实现。其返回值为布尔类型,若为 ture,代表拦截,不向下分
发。
Qt 中的事件是封装在 QEvent类 中,在 Qt 助⼿中输⼊ QEvent 可以查看其所包括的事件类型,如下图⽰


⽰例:
1、在 "widget.h" 头⽂件中声明 ⿏标点击事件 和 事件分发器;如下图⽰:

2、在 "widget.cpp" ⽂件中实现 ⿏标点击事件 和 拦截事件;

执⾏结果如下:

### 1.7 事件过滤器
在 Qt 中,⼀个对象可能经常要查看或拦截另外⼀个对象的事件,如对话框想要拦截按键事件,不让别
的组件接收到,或者修改按键的默认值等。通过上⾯的学习,我们已经知道,Qt 创建了 QEvent事件
对象之后,会调⽤QObject 的 event()函数 处理事件的分发。显然,我们可以在 event()函数 中实现拦
截的操作。由于 event()函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很
多个event()函数。这当然相当⿇烦,更不⽤说重写 event()函数还得⼩⼼⼀堆问题。好在 Qt 提供了另
外⼀种机制来达到这⼀⽬的:事件过滤器。
事件过滤器是在应⽤程序分发到 event事件分发器 之前,再做⼀次更⾼级的拦截。如下图⽰:

事件过滤器的⼀般使⽤步骤:
1、安装事件过滤器;
2、重写事件过滤器函数:eventfilter() 。
⽰例:
1、新建 Qt 项⽬,基类选择 QWidget,同时勾选 UI 界⾯⽂件,如下图⽰;

2、设计 UI ⽂件,如下图⽰;

3、在项⽬新添加⼀个类:MyLabel;
先选中项⽬名称 QEvent,点击⿏标右键,选择 add new ... ,弹出如下对话框:

4、选择:Choose ... ,弹出如下界⾯:

5、此时项⽬中会新添加以下两个⽂件:

6、在 UI ⽂件中选中 Label,右键 ------\> 提升为...

7、当点击 "提升为... " 之后,弹出如下对话框:

6、在 "mylabel.h" 中声明 ⿏标点击事件 和 事件分发器;

7、在 "mylabel.cpp" ⽂件中实现⿏标点击事件和事件分发器;

8、在 "widget.h" 头⽂件中声明事件过滤器函数;

9、在 "widget.cpp" ⽂件中实现事件过滤器的两个步骤;


执⾏结果如下所⽰:

过滤器小结:
事件分发/事件过滤 属于Qt事件机制背后的一些逻辑。
Qt也把这部分内容提供了一些API让程序员有更多的操作空间。
事件分发☞重写event函数,直接获取到所有的事件。
event函数☞杀伤力比较广,不当使用可能对现有逻辑(现有的事件体系造成一些负面影响)
当然,有的场景中,比如要禁用用户的某种操作,可以考虑使用事件过滤机制。
## 扩展
moveEvent窗口移动时出发的事件
resizeEvent窗口大小改变时触发的事件
## 2. Qt ⽂件
类比
C语言中,fopen打开文件,fread fwrite读写文件 fclose关闭文件
c++中,fstream打开文件,\<\< \>\>读写文件 close关闭文件
Linux中,open打开文件,read write读写文件 close关闭文件 ☞一般开发中很少会直接使用,主要是理解文件操作背后的原理
Qt也提供了一套文件操作。
Qt中使用上述的几种方案来读写文件,也可以完全可以的。(linux这一套,局限于linux系统windows上的Qt,就需要使用windows api)
但是即使如此,Qt还是又封装了一套。
Qt诞生的太早了,c++还没有"标准化概念"。
咱们在编写Qt程序的时候,更加推荐使用Qt自己提供的这一套文件操作。和QString等Qt内置的类可以很好的配合。



### 2.1 Qt ⽂件概述
⽂件操作是应⽤程序必不可少的部分。Qt 作为⼀个通⽤开发库,提供了跨平台的⽂件操作能⼒。 Qt
提供了很多关于⽂件的类,通过这些类能够对⽂件系统进⾏操作,如⽂件读写、⽂件信息获取、⽂件
复制或重命名等。
### 2.2 输⼊输出设备类
在 Qt 中,⽂件读写的类为 QFile 。QFile 的⽗类为 QFileDevice ,QFileDevice 提供了⽂件交互操作的
底层功能。 QFileDevice 的⽗类是 QIODevice,QIODevice 的⽗类为 QObject 。
QIODevice 是 Qt 中所有输⼊输出设备(input/output device,简称 I/O 设备)的基础类,I/O 设备就
是能进⾏数据输⼊和输出的设备,例如⽂件是⼀种 I/O 设备,⽹络通信中的 socket 是 I/O 设备, 串
⼝、蓝⽛等通信接⼝也是 I/O 设备,所以它们也是从 QIODevice 继承来的。Qt 中主要的⼀些 I/O 设备
类的继承关系如下图所⽰:

上图中各类的说明如下:
• QFile 是⽤于⽂件操作和⽂件数据读写的类,使⽤ QFile 可以读写任意格式的⽂件。
• QSaveFile 是⽤于安全保存⽂件的类。使⽤ QSaveFile 保存⽂件时,它会先把数据写⼊⼀个临时⽂
件,成功提交后才将数据写⼊最终的⽂件。如果保存过程中出现错误,临时⽂件⾥的数据不会被写
⼊最终⽂件,这样就能确保最终⽂件中不会丢失数据或被写⼊部分数据。 在保存⽐较⼤的⽂件或复
杂格式的⽂件时可以使⽤这个类,例如从⽹络上下载⽂件等。
• QTemporaryFile 是⽤于创建临时⽂件的类。使⽤函数 QTemporaryFile::open() 就能创建⼀个⽂件
名唯⼀的临时⽂件,==在 QTemporaryFile 对象被删除时,临时⽂件被⾃动删除。 ==
QsaveFile 要写入大量的数据,写文件的时候,往往会把旧的文件先清空,再写。万一新的数据写一半,结果出错了,新的数据用不了旧的数据也没了。
如果你想要写一个文件,会自动的把内容先写到临时文件里(不会破坏原有文件)。等到所有的内容都写完了之后,再把就文件自动删除,并且用新的文件替换旧的文件。这种策略是一个广泛常见的策略。
• QTcpSocket 和 QUdpSocket 是分别实现了 TCP 和 UDP 的类。
• QSerialPort 是实现了串⼝通信的类,通过这个类可以实现计算机与串⼝设备的通信。
• QBluetoothSocket 是⽤于蓝⽛通信的类。⼿机和平板计算机等移动设备有蓝⽛通信模块,笔记本
电脑⼀般也有蓝⽛通信模块。通过QBluetoothSocket类,就可以编写蓝⽛通信程。如编程实现笔
记本电脑与⼿机的蓝⽛通信。
• QProcess 类⽤于启动外部程序,并且可以给程序传递参数。
• QBuffer 以⼀个 QByteArray 对象作为数据缓冲区,将 QByteArray 对象当作⼀个 I/O 设备来读写。
### 2.3 ⽂件读写类
在 Qt 中,⽂件的读写主要是通过 QFile 类来实现。在 QFile 类中提供了⼀些⽤来读写⽂件的⽅法。对于⽂件的操作主要有:
• 读数据:QFile 类中提供了多个⽅法⽤于读取⽂件内容;如 read()、readAll()、readLine()等。
• 写数据:QFile 类中提供了多个⽅法⽤于往⽂件中写内容;如 write()、writeData()等。
• 关闭⽂件:⽂件使⽤结束后必须⽤函数 close() 关闭⽂件。
访问⼀个设备之前,需要使⽤ open()函数 打开该设备,⽽且必须指定正确的打开模式,QIODevice 中
所有的打开模式由 QIODevice::OpenMode 枚举变量定义,其取值如下:

QIODevice::NotOpen 没有打开设备
QIODevice::ReadOnly 以只读⽅式打开设备
QIODevice::WriteOnly 以只写⽅式打开设备
QIODevice::ReadWrite 以读写⽅式打开设备
QIODevice::Append 以追加⽅式打开设备,数据将写到⽂件末尾
QIODevice::Truncate 每次打开⽂件后重写⽂件内容,原内容将被删除
QIODevice::Text 在读⽂件时,⾏尾终⽌符会被转换为 '\\n';当写⼊⽂件时,⾏尾终⽌符会被转换为
本地编码。如 Win32上为'\\r\\n';
QIODevice::Unbuffered ⽆缓冲形式打开⽂件,绕过设备中的任何缓冲区
QIODevice::NewOnly ⽂件存在则打开失败,不存在则创建⽂件
⽰例1:读取⽂件内容
1. 新建 Qt 项⽬,在 UI ⽂件中拖⼊⼀个 LineEdit,⼀个pushButton,⼀个 TextEdit。当点击按钮
时,弹出窗⼝选择要读取的⽂件,并将读取到的内容在 TextEdit 中显⽰;

2. 在 "widget.cpp" ⽂件中实现对应功能;
```cpp
#include "widget.h"
#include "ui_widget.h"
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->btn,&QPushButton::clicked,[=](){
QString path = QFileDialog::getOpenFileName(this,"打开⽂
件","C:\\Users\\Lenovo\\Desktop");
ui->lineEdit->setText(path);
QFile file(path); //path:代表⽂件路径this
//打开⽂件
file.open(QIODevice::ReadOnly); //以只读的⽅式打开⽂件
QString str = file.readAll();
ui->textEdit->setText(str);
//关闭⽂件
file.close();
});
}
```
实现效果如下:

⽰例2:写⽂件
在上述⽰例的基础上修改 "widget.cpp" ⽂件;
```cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->btn,&QPushButton::clicked,[=](){
QString path = QFileDialog::getOpenFileName(this,"打开⽂
件","C:\\Users\\Lenovo\\Desktop");
ui->lineEdit->setText(path);
QFile file(path); //path:代表⽂件路径
//打开⽂件 进⾏写操作
file.open(QIODevice::Append); //以追加的⽅式进⾏写
file.write("【这是⽰例!!!】");
//关闭⽂件
file.close();
});
}
```
实现效果如下:

eg:实现简易记事本(打开和保存功能)
```cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setWindowTitle("简单的记事本");
//获取到菜单栏
QMenuBar *menuBar =this->menuBar();//必须是这个不然识别不了
//添加菜单
QMenu*menu = new QMenu("文件");
menuBar->addMenu(menu);
//添加菜单项
QAction*action1 = new QAction("打开");
QAction*action2 = new QAction("保存");
menu->addAction(action1);
menu->addAction(action2);
//指定一个输入框
edit = new QPlainTextEdit();//创建输入框
QFont font;
font.setPixelSize(20);//设置样式
edit->setFont(font);
this->setCentralWidget(edit);
//连接QAction的信号槽
connect(action1,&QAction::triggered,this,&MainWindow::handleAction1);
connect(action2,&QAction::triggered,this,&MainWindow::handleAction2);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::handleAction1()
{
//1、先弹出"打开文件"对话框,让用户选择打开那个文件
QString path = QFileDialog::getOpenFileName(this);
//2、把文件名显示到状态栏里
QStatusBar *statusBar = this->statusBar();
statusBar->showMessage(path);
//3、根据用户选择的路径,构造一个QFile对象,并打开文件
QFile file(path);
bool ret = file.open(QIODevice::ReadOnly);
if(!ret)
{
//打开文件夹失败
statusBar->showMessage(path+"打开文件失败");
return;
}
//4、读取文件
QString text = file.readAll();
//5、关闭文件!!
file.close();
//6、读到的内容设置到输入框中
edit->setPlainText(text);
}
void MainWindow::handleAction2()
{
//1、先弹出"保存文件"对话框
QString path = QFileDialog::getSaveFileName(this);
//2、在状态栏中显示这个文件名
QStatusBar *statusBar = this->statusBar();
statusBar->showMessage(path);
//3、根据用户选择的路径,构建一个QFile对象,并打开文件
QFile file(path);
bool ret = file.open(QIODevice::WriteOnly);
if(!ret)
{
//打开文件失败
statusBar->showMessage(path+"打开文件失败");
return;
}
//4、写文件
const QString &text = edit->toPlainText();
file.write(text.toUtf8());
//5、关闭文件
file.close();
}
```

### 2.4 ⽂件和⽬录信息类
QFileInfo 是 Qt 提供的⼀个⽤于获取⽂件和⽬录信息的类,如获取⽂件名、⽂件⼤⼩、⽂件修改⽇期
等。QFileInfo类中提供了很多的⽅法,常⽤的有:
• isDir() 检查该⽂件是否是⽬录;
• isExecutable() 检查该⽂件是否是可执⾏⽂件;
• fileName() 获得⽂件名;
• completeBaseName() 获取完整的⽂件名;
• suffix() 获取⽂件后缀名;
• completeSuffix() 获取完整的⽂件后缀;
• size() 获取⽂件⼤⼩;
• isFile() 判断是否为⽂件;
• fileTime() 获取⽂件创建时间、修改时间、最近访问时间等;
⽰例:
在 "widget.cpp" ⽂件中添加如下代码:
```cpp
#include
#include
#include
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->btn,&QPushButton::clicked,[=](){
QString path = QFileDialog::getOpenFileName(this,"打开⽂
件","C:\\Users\\Lenovo\\Desktop");
//QFileInfo ⽂件信息类
QFileInfo fileinfo(path);
//⽂件名
qDebug() << "⽂件名为:" << fileinfo.fileName().toUtf8().data();
//⽂件后缀名
qDebug() << "后缀名为:" << fileinfo.suffix().toUtf8().data();
//⽂件⼤⼩
qDebug() << "⽂件⼤⼩为:" << fileinfo.size();
//⽂件路径
qDebug() << "⽂件路径为:" << fileinfo.path().toUtf8().data();
//判断是否为⽂件
qDebug() << "是否为⽂件:"<< fileinfo.isFile();
//⽂件创建时间
QDateTime time1 = fileinfo.fileTime(QFileDevice::FileBirthTime);
qDebug() << "创建时间为:" << time1.toString("yyyy-MM-dd
hh:mm:ss").toUtf8().data();
//⽂件的最后修改⽇期
QDateTime time2 = fileinfo.lastModified();
qDebug() << "最后修改时间为:"<< time2.toString("yyyy-MM-dd
hh:mm:ss").toUtf8().data();
//判断是否为⽂件夹
qDebug() << "是否为⽬录:" << fileinfo.isDir();
});
}
```
实现效果如下

eg:FileInfo的使用
```cpp
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
//弹出文件对话框,并获取到文件的属性信息
QString path = QFileDialog::getOpenFileName(this);
//构造出一个QFileInfo对象
QFileInfo fileInfo(path);
//打印相关属性
qDebug()< //添加头⽂件
class TimeThread : public QThread
{
Q_OBJECT
public:
TimeThread();
void run(); //线程任务函数
signals:
void sendTime(QString Time); //声明信号函数
};
#endif // TIMETHREAD_H
/******************************** widget.h ********************************/
#ifndef WIDGET_H
#define WIDGET_H
#include
#include //添加头⽂件
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_btn_clicked();
void showTime(QString Time);
private:
Ui::Widget *ui;
TimeThread t; //定义线程对象
};
#endif // WIDGET_H
/******************************** timethread.cpp
********************************/
#include "timethread.h"
#include
#include
TimeThread::TimeThread()
{ }
void TimeThread::run()
{
while(1)
{
QString time = QTime::currentTime().toString("hh:mm:ss");
qDebug() << time;
emit sendTime(time); //发送信号
sleep(1);
}
}
/******************************** widget.cpp ********************************/
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(&t,&TimeThread::sendTime,this,&Widget::showTime);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_btn_clicked()
{
t.start(); //开启线程
}
void Widget::showTime(QString Time)
{
ui->label->setText(Time);
}
```
执⾏效果:

说明:
1、线程函数内部不允许操作 UI 图形界⾯,⼀般⽤数据处理;
2、connect() 函数第五个参数表⽰的为连接的⽅式,且只有在多线程的时候才意义。
connect() 函数第五个参数为 Qt::ConnectionType,⽤于指定信号和槽的连接类型。同时影响信号的
传递⽅式和槽函数的执⾏顺序。Qt::ConnectionType 提供了以下五种⽅式:
Qt::AutoConnection
在 Qt 中,会根据信号和槽函数所在的线程⾃动选择连接类型。如果信号和槽函
数在同⼀线程中,那么使⽤ Qt:DirectConnection 类型;如果它们位于不同的
线程中,那么使⽤Qt::QueuedConnection 类型。
Qt::DirectConnection
当信号发出时,槽函数会⽴即在同⼀线程中执⾏。这种连接类型适⽤于信号和
槽函数在同⼀线程中的情况,可以实现直接的函数调⽤,但需要注意线程安全
性。
Qt::QueuedConnection
当信号发出时,槽函数会被插⼊到接收对象所属的线程的事件队列中,等待下
⼀次事件循环时执⾏。这种连接类型适⽤于信号和槽函数在不同线程中的情
况,可以确保线程安全。
Qt::BlockingQueuedConnec
tion
与 Qt:QueuedConnection 类似,但是发送信号的线程会被阻塞,直到槽函数
执⾏完毕,这种连接类型适⽤于需要等待槽函数执⾏完毕再继续的场景,但需
要注意可能引起线程死锁的⻛险。
Qt::UniqueConnection 这是⼀个标志,可以使⽤位或与上述任何⼀种连接类型组合使⽤。
### 3.4 线程安全
实现线程互斥和同步常⽤的类有:
• 互斥锁:QMutex、QMutexLocker
• 条件变量:QWaitCondition
• 信号量:QSemaphore
• 读写锁:QReadLocker、QWriteLocker、QReadWriteLock
#### 3.4.1 互斥锁
互斥锁是⼀种保护和防⽌多个线程同时访问同⼀对象实例的⽅法,在 Qt 中,互斥锁主要是通过
QMutex类来处理。
• QMutex
特点:QMutex 是 Qt 框架提供的互斥锁类,⽤于保护共享资源的访问,实现线程间的互斥操作。
⽤途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。
```cpp
QMutex mutex;
mutex.lock(); //上锁
//访问共享资源
//...
mutex.unlock(); //解锁
```
• QMutexLocker
特点:QMutexLocker 是 QMutex 的辅助类,使⽤ RAII(Resource Acquisition Is Initialization)⽅式
对互斥锁进⾏上锁和解锁操作。
⽤途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。
```cpp
QMutex mutex;
{
QMutexLocker locker(&mutex); //在作⽤域内⾃动上锁
//访问共享资源
//...
} //在作⽤域结束时⾃动解锁
```
• QReadWriteLocker、QReadLocker、QWriteLocker
特点:
QReadWriteLock 是读写锁类,⽤于控制读和写的并发访问。
QReadLocker ⽤于读操作上锁,允许多个线程同时读取共享资源。
QWriteLocker ⽤于写操作上锁,只允许⼀个线程写⼊共享资源。
⽤途:在某些情况下,多个线程可以同时读取共享数据,但只有⼀个线程能够进⾏写操作。读写锁提
供了更⾼效的并发访问⽅式。
```cpp
QReadWriteLock rwLock;
//在读操作中使⽤读锁
{
QReadLocker locker(&rwLock); //在作⽤域内⾃动上读锁
//读取共享资源
//...
} //在作⽤域结束时⾃动解读锁
//在写操作中使⽤写锁
{
QWriteLocker locker(&rwLock); //在作⽤域内⾃动上写锁
//修改共享资源
//...
} //在作⽤域结束时⾃动解写锁
```
⽰例1:
```cpp
******************************* myThread.h **********************************/
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include
#include
class myThread : public QThread
{
Q_OBJECT
public:
explicit myThread(QObject *parent = nullptr);
void run();
private:
static QMutex mutex; //多个线程使⽤⼀把锁
static int num; //多个线程访问⼀个数据
};
#endif // MYTHREAD_H
/******************************* myThread.cpp
**********************************/
#include "mythread.h"
#include
QMutex myThread::mutex;
int myThread::num = 0;
myThread::myThread(QObject *parent) : QThread(parent)
{ }
void myThread::run()
{
while(1)
{
this->mutex.lock(); //加锁
qDebug() << "Current Thread: " << this << ", Value: " << this->num++;
this->mutex.unlock(); //解锁
QThread::sleep(1); //线程睡眠两秒
}
}
/******************************* mainwindow.h
**********************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
/******************************* mainwindow.cpp
**********************************/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "mythread.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
myThread *t1 = new myThread(this);
myThread *t2 = new myThread(this);
t1->start();
t2->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
```
执⾏效果:
两个线程使⽤⼀把锁,操作⼀个数据,数据会被两个线程依次打印:0、1、2、3、4 ...

⽰例2:在上述⽰例的基础上使⽤ QMutexLocker 锁。
```cpp
/******************************* myThread.cpp
**********************************/
#include "mythread.h"
#include
QMutex myThread::mutex;
int myThread::num = 0;
myThread::myThread(QObject *parent) : QThread(parent)
{ }
void myThread::run()
{
while (1)
{
//QMutexLocker:创建的时候加锁,当QMutexLocker局部销毁的时候解锁
{
QMutexLocker lock(&this->mutex);
qDebug() << "Current Thread: " << this << ", Value: " << this-
>num++;
}
QThread::sleep(1);// 线程睡眠两秒
}
}
```
#### 3.4.2 条件变量
在多线程编程中,假设除了等待操作系统正在执⾏的线程之外,某个线程还必须等待某些条件满⾜才能执⾏,这时就会出现问题。这种情况下,线程会很⾃然地使⽤锁的机制来阻塞其他线程,因为这只是线程的轮流使⽤,并且该线程等待某些特定条件,⼈们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进⼊了睡眠状态,这样其他线程就可以继续运⾏。当条件满⾜时,等待条件的线程将被另⼀个线程唤醒。
在 Qt 中,专⻔提供了 QWaitCondition类 来解决像上述这样的问题。
特点:QWaitCondition 是 Qt 框架提供的条件变量类,⽤于线程之间的消息通信和同步。
⽤途:在某个条件满⾜时等待或唤醒线程,⽤于线程的同步和协调
```cpp
QMutex mutex;
QWaitCondition condition;
//在等待线程中
mutex.lock();
//检查条件是否满⾜,若不满⾜则等待
while (!conditionFullfilled())
{
condition.wait(&mutex); //等待条件满⾜并释放锁
}
//条件满⾜后继续执⾏
//...
mutex.unlock();
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();
```
#### 3.4.3 信号量
有时在多线程编程中,需要确保多个线程可以相应的访问⼀个数量有限的相同资源。例如,运⾏程序的设备可能是⾮常有限的内存,因此我们更希望需要⼤量内存的线程将这⼀事实考虑在内,并根据可⽤的内存数量进⾏相关操作,多线程编程中类似问题通常⽤信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,⽽且可以跟踪可⽤资源的数量。
特点:QSemaphore 是 Qt 框架提供的计数信号量类,⽤于控制同时访问共享资源的线程数量。
⽤途:限制并发线程数量,⽤于解决⼀些资源有限的问题。
```cpp
QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中进⾏类似操作
```
### 4. Qt ⽹络
和多线程类似, Qt 为了⽀持跨平台, 对⽹络编程的 API 也进⾏了重新封装.
注意:
• 实际 Qt 开发中进⾏⽹络编程, 也不⼀定使⽤ Qt 装的⽹络 API, 也有⼀定可能使⽤的是系
统原⽣ API 或者其他第三⽅框架的 API.
在进⾏⽹络编程之前, 需要在项⽬中的 .pro ⽂件中添加 network 模块.
添加之后要⼿动编译⼀下项⽬, 使 Qt Creator 能够加载对应模块的头⽂件.
### 4.1 UDP Socket
4.1.1 核⼼ API 概览
主要的类有两个. QUdpSocket 和 QNetworkDatagram
QUdpSocket 表⽰⼀个 UDP 的 socket ⽂件.
名称 类型 说明 对标原⽣ API
bind(const QHostAddress\&,
quint16)
⽅法 绑定指定的端⼝号. bind
receiveDatagram() ⽅法 返回 QNetworkDatagram . 读取
⼀个 UDP 数据报.
recvfrom
writeDatagram(const
QNetworkDatagram\&)
⽅法 发送⼀个 UDP 数据报. sendto
readyRead 信号 在收到数据并准备就绪后触发. ⽆ (类似于 IO 多路复⽤的通
知机制)
QNetworkDatagram 表⽰⼀个 UDP 数据报.
名称 类型 说明 对标原⽣ API
QNetworkDatagram(const
QByteArray\&, const
QHostAddress\& , quint16 )
构造函
数
通过 QByteArray , ⽬标 IP 地址,
⽬标端⼝号 构造⼀个 UDP 数据报.
通常⽤于发送数据时.
⽆ data() ⽅法 获取数据报内部持有的数据. 返回
QByteArray
⽆ senderAddress() ⽅法 获取数据报中包含的对端的 IP 地
址. ⽆, recvfrom 包含了该功能.
senderPort() ⽅法 获取数据报中包含的对端的端⼝号. ⽆, recvfrom 包含了该功
能.
#### 4.1.2 回显服务器
1. 创建界⾯, 包含⼀个 QListWidget ⽤来显⽰消息.

2. 创建 QUdpSocket 成员
修改 widget.h
```cpp
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QUdpSocket* socket;
}
```
修改 widget.cpp, 完成 socket 后续的初始化
⼀般来说, 要先连接信号槽, 再绑定端⼝
如果顺序反过来, 可能会出现端⼝绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这
个请求就有可能错过了.
```cpp
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 , 完成处理请求的过程
• 读取请求并解析
• 根据请求计算响应
• 把响应写回到客⼾端
```cpp
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());
ocket->writeDatagram(responseDatagram);
// 显⽰打印⽇志
QString log = "[" + requestDatagram.senderAddress().toString() + ":" +
QString::number(requestDatagram.senderPort())
+ "] req: " + request + ", resp: " + response;
ui->listWidget->addItem(log);
}
```
4. 实现 process 函数
由于我们此处是实现回显服务器. 所以 process ⽅法中并没有包含实质性的内容.
```cpp
QString Widget::process(const QString& request)
{
return request;
}
```
**"根据请求处理响应" 是服务器开发中的最核⼼的步骤.
⼀个商业服务器程序, 这⾥的逻辑可能是⼏万⾏⼏⼗万⾏代码量级的.**
此时, 服务器程序编写完毕.
但是直接运⾏还看不出效果. 还需要搭配客⼾端来使⽤.
#### 4.1.3 回显客⼾端
1. 创建界⾯. 包含⼀个 QLineEdit , QPushButton , QListWidget
• 先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的
sizePolicy 为 Expanding
• 再使⽤垂直布局把 QListWidget 和上⾯的⽔平布局放好.
• 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺⼨⽐例根据个⼈喜好微调).

2. 在 widget.cpp 中, 先创建两个全局常量, 表⽰服务器的 IP 和 端⼝
```cpp
// 提前定义好服务器的 IP 和 端⼝
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;
```
3. 创建 QUdpSocket 成员
修改 widget.h, 定义成员
```cpp
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 创建 socket 成员
QUdpSocket* socket;
}
```
修改 widget.cpp, 初始化 socket
```cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 设置窗⼝名字
this->setWindowTitle("客⼾端");
// 2. 实例化 socket
socket = new QUdpSocket(this);
}
```
4. 给发送按钮 slot 函数, 实现发送请求
```cpp
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 的构造函数, 通过信号槽, 来处理服务器的响应.
```cpp
connect(socket, &QUdpSocket::readyRead, this, [=]() {
const QNetworkDatagram responseDatagram = socket->receiveDatagram();
QString response = responseDatagram.data();
ui->listWidget->addItem(QString("服务器说: ") + response);
});
```
最终执⾏效果
启动多个客⼾端都可以正常⼯作.

### 4.2 TCP Socket
#### 4.2.1 核⼼ API 概览
核⼼类是两个: QTcpServer 和 QTcpSocket
QTcpServer ⽤于监听端⼝, 和获取客⼾端连接.
名称 类型 说明 对标原⽣ 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 多路复⽤
中的通知机制)
disconnected 信号 连接断开时触发. ⽆ (但是类似于 IO 多路复⽤
中的通知机制)
QByteArray ⽤于表⽰⼀个字节数组. 可以很⽅便的和 QString 进⾏相互转换.
例如:
• 使⽤ QString 的构造函数即可把 QByteArray 转成 QString.
• 使⽤ QString 的 toUtf8 函数即可把 QString 转成 QByteArray.
4.2.2 回显服务器
1. 创建界⾯. 包含⼀个 QListWidget , ⽤于显⽰收到的数据.

2. 创建 QTcpServer 并初始化
修改 widget.h, 添加 QTcpServer 指针成员.
```cpp
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 创建 QTcpServer
QTcpServer* tcpServer;
}
```
修改 widget.cpp, 实例化 QTcpServer 并进⾏后续初始化操作.
• 设置窗⼝标题
• 实例化 TCP server. (⽗元素设为当前控件, 会在⽗元素销毁时被⼀起销毁).
• 通过信号槽, 处理客⼾端建⽴的新连接.
• 监听端⼝
```cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 设置窗⼝标题
this->setWindowTitle("服务器");
// 2. 实例化 TCP server
tcpServer = new QTcpServer(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.
• 通过信号槽, 处理收到请求的情况
• 通过信号槽, 处理断开连接的情况
```cpp
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 ⽅法中并没有包含实质性的内容.
```cpp
QString Widget::process(const QString &request)
{
return request;
}
```
"根据请求处理响应" 是服务器开发中的最核⼼的步骤.
⼀个商业服务器程序, 这⾥的逻辑可能是⼏万⾏⼏⼗万⾏代码量级的.
此时, 服务器程序编写完毕.
但是直接运⾏还看不出效果. 还需要搭配客⼾端来使⽤.
#### 4.2.3 回显客⼾端
1. 创建界⾯. 包含⼀个 QLineEdit , QPushButton , QListWidget
• 先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的
sizePolicy 为 Expanding
• 再使⽤垂直布局把 QListWidget 和上⾯的⽔平布局放好.
• 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺⼨⽐例根据个⼈喜好微调).

2. 创建 QTcpSocket 并实例化
修改 widget.h, 创建成员.
```cpp
2) 创建 QTcpSocket 并实例化
修改 widget.h, 创建成员.
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 新增 QTcpSocket
QTcpSocket* socket;
};
```
修改 widget.cpp, 对 QTcpSocket 进⾏实例化.
• 设置窗⼝标题
• 实例化 socket 对象 (⽗元素设为当前控件, 会在⽗元素销毁时被⼀起销毁).
• 和服务器建⽴连接.
• 等待并确认连接是否出错.
```cpp
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 函数, 实现发送请求给服务器.
```cpp
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.cpp 中的 Widget 构造函数, 通过信号槽, 处理收到的服务器的响应.
```cpp
/ 处理服务器返回的响应.
connect(socket, &QTcpSocket::readyRead, this, [=]() {
QString response = socket->readAll();
qDebug() << response;
ui->listWidget->addItem(QString("服务器说: ") + response);
});
```
先启动服务器, 再启动客⼾端(可以启动多个), 最终执⾏效果:
由于我们使⽤信号槽处理同⼀个客⼾端的多个请求, 不涉及到循环, 也就不会使客⼾端之间相互影响
了.
### 4.3 HTTP Client
进⾏ Qt 开发时, 和服务器之间的通信很多时候也会⽤到 HTTP 协议.
通过 HTTP 从服务器获取数据.
• 通过 HTTP 向服务器提交数据.
4.3.1 核⼼ 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::KnownHeaders
header,
const QVariant \&value)
设置请求头.
其中的 QNetworkRequest::KnownHeaders 是⼀个枚举类型, 常⽤取值:
取值 说明
ContentTypeHeader 描述 body 的类型.
ContentLengthHeader 描述 body 的⻓度.
LocationHeader ⽤于重定向报⽂中指定重定向地址. (响应中使⽤, 请求
⽤不到)
CookieHeader 设置 cookie
UserAgentHeader 设置 User-Agent
QNetworkReply 表⽰⼀个 HTTP 响应. 这个类同时也是 QIODevice 的⼦类.
⽅法 说明
error() 获取出错状态.
errorString() 获取出错原因的⽂本.
readAll() 读取响应 body.
header(QNetworkRequest::KnownHeaders
header)
读取响应指定 header 的值
此外, QNetworkReply 还有⼀个重要的信号 finished 会在客⼾端收到完整的响应数据之后触
发.
#### 4.3.2 代码⽰例
给服务器发送⼀个 GET 请求.
1. 创建界⾯. 包含⼀个 QLineEdit , QPushButton
• 先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的
sizePolicy 为 Expanding
• 再使⽤垂直布局把 QPlainTextEdit 和上⾯的⽔平布局放好. ( QPlainTextEdit 的
readOnly 设为 true )
• 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺⼨⽐例根据个⼈喜好微调).

**此处建议使⽤ QPlainTextEdit ⽽不是 QTextEdit . 主要因为 QTextEdit 要进⾏富
⽂本解析, 如果得到的 HTTP 响应体积很⼤, 就会导致界⾯渲染缓慢甚⾄被卡住.**
2) 修改 widget.h, 创建 QNetworkAccessManager 属性
```cpp
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
// 新增属性
QNetworkAccessManager* manager;
};
```
3. 修改 widget.cpp, 创建实例
```cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 实例化属性
manager = new QNetworkAccessManager(this);
}
```
4. 编写按钮的 slot 函数, 实现发送 HTTP 请求功能.
```cpp
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);
// qDebug() << html;
} else {
// 响应出错
ui->plainTextEdit->setPlainText(response->errorString());
}
response->deleteLater();
});
}
```
执⾏程序, 观察效果

发送 POST 请求代码也是类似. 使⽤ manager-\>post() 即可. 此处不再演⽰.
### 4.4 其他模块
Qt 中还提供了 FTP, DNS, SSL 等⽹络相关的组件⼯具. 此处不再⼀⼀展开介绍. 有需要的可以⾃⾏
翻阅官⽅⽂档学习相关 API 的使⽤.
## 5. Qt ⾳视频
### 5.1 Qt ⾳频
在 Qt 中,⾳频主要是通过 QSound 类来实现。但是需要注意的是 QSound 类只⽀持播放 wav 格式的⾳频⽂件。也就是说如果想要添加⾳频效果,那么⾸先需要将 ⾮wav格式 的⾳频⽂件转换为 wav 格式。
通过帮助⼿册查看 QSound 类如下:

注意:
使⽤ QSound 类时,需要添加模块:multimedia.
#### 5.1.1 核⼼API概览
play() 开始或继续播放当前源。
#### 5.1.2 ⽰例
```cpp
/********************************* SoundTest.pro
*********************************/
QT += core gui multimedia //添加⾳频模块
/********************************* widget.cpp
*********************************/
#include "widget.h"
#include "ui_widget.h"
#include //添加⾳频头⽂件
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//实例化对象
QSound *sound = new QSound(":/1.wav",this);
connect(ui->btn,&QPushButton::clicked,[=](){
sound->play(); //播放
});
}
Widget::~Widget()
{
delete ui;
}
```
### 5.2 Qt 视频
在 Qt 中,视频播放的功能主要是通过 QMediaPlayer类 和 QVideoWidget类 来实现。在使⽤这两个类
时要添加对应的模块 multimedia 和multimediawidgets 。
#### 5.2.1 核⼼API概览
setMedia() 设置当前媒体源。
setVideoOutput() 将QVideoWidget视频输出附加到媒体播放器。
如果媒体播放器已经附加了视频输出,将更换⼀个新的。
#### 5.2.2 ⽰例
⾸先在 .pro ⽂件中添加 multimedia 和 multimediawidgets 两个模块;如下图⽰:

```cpp
/********************************* widget.h *********************************/
#ifndef WIDGET_H
#define WIDGET_H
#include
#include //⽔平布局
#include //垂直布局
#include //显⽰视频
#include //播放声⾳
#include //按钮
#include //设置图标
#include //选择⽂件/⽂件夹
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
#include
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()
{ }
```
eg;
添加音频

```cpp
#include "widget.h"
#include "ui_widget.h"
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
sound = new QSound(":/n/a.wav");
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
//在这里进行音频播放
sound->play();
}
```
## 总结
以上就是qt系统相关的内容了。