Qt【第六篇】 ——— 事件处理、多线程、网络与文件等操作详解

目录

鼠标事件

widget.cpp(重写鼠标进入事件与离开事件)

widget.cpp(重写获取鼠标点击与释放事件)

widget.cpp(重写鼠标移动事件与滚轮滚动事件)

事件与信号槽的关系

事件的实现原理

鼠标相关事件函数

[setMouseTracking 鼠标追踪设置](#setMouseTracking 鼠标追踪设置)
键盘事件

widget.cpp(重写键盘事件)

键盘事件概述

键盘事件核心函数与类

按键参数获取方法

修饰键常量说明

[QShortCut 底层实现原理](#QShortCut 底层实现原理)
定时器事件

widget.cpp(重写定时器事件)

[定时器事件与 QTimer 关系](#定时器事件与 QTimer 关系)

[startTimer 函数](#startTimer 函数)

[killTimer 函数](#killTimer 函数)

[timerId 变量](#timerId 变量)

[timerEvent 函数](#timerEvent 函数)
窗口移动和大小改变事件

widget.cpp(重写窗口移动事件与窗口大小改变事件)

窗口移动事件

窗口大小改变事件
文件操作

mainwindow.cpp(文件打开、读取、写入、关闭操作)

[widget.cpp(QFileInfo 获取文件信息)](#widget.cpp(QFileInfo 获取文件信息))

[QFile 类](#QFile 类)

[QPlainTextEdit 类](#QPlainTextEdit 类)

[QFileInfo 类](#QFileInfo 类)
多线程与互斥锁

widget.cpp(重写run函数实现倒计时)

widget.cpp(加锁让多个线程对同一变量正确递增)

[QThread 类](#QThread 类)

[QMutex 互斥锁](#QMutex 互斥锁)

[Qt 与 Linux 创建线程的区别](#Qt 与 Linux 创建线程的区别)

[线程入口函数 run 重写机制](#线程入口函数 run 重写机制)

[QWaitCondition 条件等待](#QWaitCondition 条件等待)

[QSemaphore 信号量](#QSemaphore 信号量)
UDP

widget.cpp(编写UDP回显服务端)

widget.cpp(编写UDP回显客户端)

网络模块配置

[QUdpSocket 类](#QUdpSocket 类)

[readyRead 信号](#readyRead 信号)

执行顺序规范

[服务端与客户端 QUdpSocket 作用](#服务端与客户端 QUdpSocket 作用)

[QNetworkDatagram 类](#QNetworkDatagram 类)

[QUdpSocket 核心成员函数](#QUdpSocket 核心成员函数)

[Qt UDP 函数与 Linux UDP 函数对应关系](#Qt UDP 函数与 Linux UDP 函数对应关系)
TCP

widget.cpp(编写TCP回显服务端)

widget.cpp(编写TCP回显客户端)

[QTcpServer 类](#QTcpServer 类)

[QTcpSocket 类](#QTcpSocket 类)
HTTP

[QNetworkAccessManager 类](#QNetworkAccessManager 类)

[QNetworkRequest 类](#QNetworkRequest 类)

[QNetworkReply 类](#QNetworkReply 类)

核心成员函数

核心信号


鼠标事件

widget.cpp(重写鼠标进入事件与离开事件)

复制代码
------------------------------ mypushbutton.h ------------------------------
#ifndef MYPUSHBUTTON_H
#define MYPUSHBUTTON_H

#include <QWidget>
// 引入QPushButton基类,自定义按钮需要继承它
#include <QPushButton>

// 自定义按钮类 MyPushButton
// 继承自 Qt 标准按钮 QPushButton,实现功能扩展
// Q_OBJECT 宏:必须添加!支持Qt的信号槽、事件系统
class MyPushButton : public QPushButton
{
    Q_OBJECT
public:
    // 构造函数
    // parent:父窗口指针,由Qt自动管理内存
    explicit MyPushButton(QWidget* parent = nullptr);

protected:
    // 重写【鼠标进入控件】事件
    // 当鼠标移动到按钮上时,自动触发该函数
    void enterEvent(QEvent* event) override;

    // 重写【鼠标离开控件】事件
    // 当鼠标从按钮上移开时,自动触发该函数
    void leaveEvent(QEvent* event) override;
};

#endif // MYPUSHBUTTON_H



------------------------------ mypushbutton.cpp ------------------------------
#include "mypushbutton.h"
// 调试输出头文件,用于打印日志
#include <QDebug>

// 构造函数实现
// 初始化列表:调用父类 QPushButton 的构造函数,绑定父窗口
MyPushButton::MyPushButton(QWidget* parent) : QPushButton(parent)
{
    // 构造函数:可在这里初始化按钮样式、文字等
}

// 鼠标进入按钮事件重写
void MyPushButton::enterEvent(QEvent *event)
{
    // (void)event:忽略未使用的事件参数,避免编译器警告
    (void)event;
    // 鼠标进入按钮时,控制台打印日志
    qDebug() << "enterEvent:鼠标进入了按钮";
}

// 鼠标离开按钮事件重写
void MyPushButton::leaveEvent(QEvent *event)
{
    // 忽略未使用的事件参数
    (void)event;
    // 鼠标离开按钮时,控制台打印日志
    qDebug() << "leaveEvent:鼠标离开了按钮";
}



------------------------------ widgt.cpp ------------------------------ 
#include "widget.h"
#include "ui_widget.h"
// 引入自定义按钮头文件
#include "mypushbutton.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    // 初始化UI界面
    ui->setupUi(this);

    // ==================== 创建自定义按钮对象 ====================
    // 动态创建 MyPushButton 自定义按钮
    // 指定父窗口为 this(当前Widget),Qt自动管理内存
    MyPushButton* button = new MyPushButton(this);

    // 设置按钮在窗口中的位置:x=100,y=100
    button->move(100, 100);

    // 设置按钮显示的文字
    button->setText("按钮");

    // 功能:鼠标移入/移出按钮时,控制台会自动打印日志
}

// 析构函数:释放UI对象,自定义按钮由Qt自动释放
Widget::~Widget()
{
    delete ui;
}

widget.cpp(重写获取鼠标点击与释放事件)

复制代码
------------------------------ label.h ------------------------------
#ifndef LABEL_H
#define LABEL_H

#include <QWidget>
// 继承QLabel标签控件
#include <QLabel>
// 鼠标事件需要的头文件(声明用)
#include <QMouseEvent>

// 自定义Label类,继承Qt标准标签控件 QLabel
// Q_OBJECT:Qt核心宏,必须添加,支持事件系统、信号槽
class Label : public QLabel
{
    // Qt 元对象系统必需宏
    Q_OBJECT
public:
    // 构造函数,parent为父窗口,用于Qt自动管理内存
    explicit Label(QWidget* parent = nullptr);

// 重写鼠标事件(protected权限)
protected:
    // 鼠标按下事件
    void mousePressEvent(QMouseEvent *ev) override;
    // 鼠标释放事件
    void mouseReleaseEvent(QMouseEvent *ev) override;
    // 鼠标双击事件
    void mouseDoubleClickEvent(QMouseEvent *ev) override;
};

#endif // LABEL_H



------------------------------ label.cpp ------------------------------
#include "label.h"
// 鼠标事件类头文件(实现用)
#include <QMouseEvent>
// 调试输出,打印日志
#include <QtDebug>

// 构造函数:调用父类QLabel的构造函数,绑定父窗口
Label::Label(QWidget* parent) : QLabel(parent)
{
    // 可在此处初始化标签样式、文字等
}

// 重写:鼠标按下事件
// ev:鼠标事件参数,包含按键、坐标等信息
void Label::mousePressEvent(QMouseEvent *ev)
{
    // 判断按下的鼠标按键
    if(ev->button() == Qt::LeftButton)
    {
        qDebug() << "鼠标左键按下";
    }
    else if(ev->button() == Qt::RightButton)
    {
        qDebug() << "鼠标右键按下";
    }

    // 打印【局部坐标】:相对于标签控件左上角的坐标 (x,y)
    qDebug() << "标签内局部坐标:" << ev->x() << ", " << ev->y();
    // 打印【全局坐标】:相对于整个屏幕左上角的坐标
    qDebug() << "屏幕全局坐标:" << ev->globalX() << ", " << ev->globalY();
}

// 重写:鼠标释放事件
void Label::mouseReleaseEvent(QMouseEvent *ev)
{
    if(ev->button() == Qt::LeftButton)
    {
        qDebug() << "鼠标左键释放";
    }
    else if(ev->button() == Qt::RightButton)
    {
        qDebug() << "鼠标右键释放";
    }
}

// 重写:鼠标双击事件
void Label::mouseDoubleClickEvent(QMouseEvent *ev)
{
    if(ev->button() == Qt::LeftButton)
    {
        qDebug() << "双击左键";
    }
    else if(ev->button() == Qt::RightButton)
    {
        qDebug() << "双击右键";
    }
}


------------------------------ widgt.cpp ------------------------------ 
#include "widget.h"
#include "ui_widget.h"
// 引入自定义标签头文件
#include "label.h"

// 主窗口构造函数
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    // 初始化UI界面
    ui->setupUi(this);

    // ==================== 创建自定义Label控件 ====================
    // 动态创建自定义标签,父窗口为当前Widget
    Label* label = new Label(this);

    // 设置标签位置和大小
    // setGeometry(x, y, width, height)
    // 左上角坐标(100,100),宽300,高300
    label->setGeometry(100, 100, 300, 300);

    // 设置标签边框样式:矩形边框(方便可视化看到标签范围)
    label->setFrameShape(QLabel::Box);
}

// 析构函数:释放UI对象,自定义标签由Qt自动回收内存
Widget::~Widget()
{
    delete ui;
}

widget.cpp(重写鼠标移动事件与滚轮滚动事件)

复制代码
#include "widget.h"
#include "ui_widget.h"
// Qt调试输出,打印坐标、滚轮数值
#include <QtDebug>
// 鼠标事件头文件(鼠标移动、按下、释放)
#include <QMouseEvent>
// 滚轮事件头文件
#include <QWheelEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 开启【鼠标追踪】功能
    // 默认false:只有鼠标按下时,才会触发 mouseMoveEvent 鼠标移动事件
    // 设置为true:鼠标在窗口上**移动就触发**,无需按下鼠标
    this->setMouseTracking(true);
}

Widget::~Widget()
{
    // 释放UI对象,Qt自动管理内存
    delete ui;
}

// 重写:鼠标移动事件
// 鼠标在窗口内移动时,自动触发该函数
void Widget::mouseMoveEvent(QMouseEvent *event)
{
    // 打印鼠标在**窗口局部坐标系**中的坐标 (x, y)
    // 坐标原点:窗口左上角
    qDebug() << "鼠标坐标:" << event->x() << ", " << event->y();
}

// 重写:鼠标滚轮事件
// 鼠标滚轮滚动时,自动触发该函数
void Widget::wheelEvent(QWheelEvent *event)
{
    // event->delta():获取滚轮滚动的步长值
    // 正数 → 滚轮**向上**滚动
    // 负数 → 滚轮**向下**滚动
    // 标准值:±120(每滚动一格的数值)
    qDebug() << "滚轮滚动量:" << event->delta();
}

事件与信号槽的关系

事件与信号槽的层级关联 :信号槽是事件的上层封装 ,事件是信号槽的底层实现机制 。用户操作触发事件 ,事件处理完成后,控件发出对应信号 ,通过connect完成信号与槽函数的绑定。

信号槽执行流程:用户操作控件→触发底层事件→事件处理完成→控件发出信号→槽函数执行逻辑。

事件执行流程:用户操作→触发对应事件虚函数→执行自定义事件处理逻辑。

事件的实现原理

多态与虚函数重写 :事件通过重写父类虚函数 实现,依托 C++多态特性完成。Qt 控件基类内置事件相关虚函数,子类继承基类后重写对应虚函数,程序运行时调用子类重写后的函数实现。

自定义控件事件实现步骤

  1. 创建子类,继承 Qt 标准控件类;

  2. 添加Q_OBJECT宏,支持 Qt 事件与信号槽机制;

  3. protected权限下声明重写的事件函数,添加override关键字;

  4. 实现事件函数,编写自定义处理逻辑;

  5. 主窗口中创建自定义控件对象,完成事件绑定。

    class MyPushButton : public QPushButton
    {
    Q_OBJECT
    protected:
    void enterEvent(QEvent* event) override;
    };

鼠标相关事件函数

enterEvent 鼠标进入事件:鼠标移入控件区域时自动触发,处理鼠标进入操作。

复制代码
void MyPushButton::enterEvent(QEvent *event)
{
    (void)event;
    qDebug() << "enterEvent:鼠标进入了按钮";
}

leaveEvent 鼠标离开事件:鼠标移出控件区域时自动触发,处理鼠标离开操作。

复制代码
void MyPushButton::leaveEvent(QEvent *event)
{
    (void)event;
    qDebug() << "leaveEvent:鼠标离开了按钮";
}

mousePressEvent 鼠标按下事件 :鼠标按键按下时触发,通过QMouseEvent::button()判断按键类型。

关键参数ev->x()ev->y()控件局部坐标ev->globalX()ev->globalY()屏幕全局坐标

复制代码
void Label::mousePressEvent(QMouseEvent *ev)
{
    if(ev->button() == Qt::LeftButton)
        qDebug() << "鼠标左键按下";
    qDebug() << "标签内局部坐标:" << ev->x() << ", " << ev->y();
}

mouseReleaseEvent 鼠标释放事件:鼠标按键松开时触发,配合按下事件完成点击逻辑处理。

mouseDoubleClickEvent 鼠标双击事件:鼠标按键双击时触发,可区分左右键双击操作。

mouseMoveEvent 鼠标移动事件:鼠标在控件或窗口内移动时触发,默认需按下按键才会响应。

wheelEvent 滚轮滚动事件 :鼠标滚轮滚动时触发,event->delta()获取滚动数值,正数代表向上滚动负数代表向下滚动

复制代码
void Widget::wheelEvent(QWheelEvent *event)
{
    qDebug() << "滚轮滚动量:" << event->delta();
}

setMouseTracking 鼠标追踪设置

setMouseTracking 核心作用 :设置窗口或控件的鼠标追踪 状态,控制mouseMoveEvent的触发条件。

参数规则

  • false(默认值):仅按下鼠标按键时,mouseMoveEvent才会触发;

  • true:鼠标在窗口内移动即触发mouseMoveEvent,无需按下任何按键。

    this->setMouseTracking(true);


键盘事件

widget.cpp(重写键盘事件)

复制代码
#include "widget.h"
#include "ui_widget.h"
// QKeyEvent:键盘事件类,处理按键按下/释放
#include <QKeyEvent>
// QDebug:调试输出,打印按键信息
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    // 初始化UI界面
    ui->setupUi(this);
}

Widget::~Widget()
{
    // 释放UI对象,Qt自动管理内存
    delete ui;
}

// 重写【键盘按下事件】
// 当窗口获得焦点时,按下任意键盘按键都会触发该函数
// event:键盘事件参数,包含按键编码、修饰键(Ctrl/Shift/Alt)等信息
void Widget::keyPressEvent(QKeyEvent *event)
{
    // 1. 打印当前按下按键的【键值编码】
    // 每个按键对应一个唯一的数字(如A键是65)
    qDebug() << "按键编码:" << event->key();

    // 2. 判断是否按下了【普通A键】
    // Qt::Key_A:Qt定义的A键常量
    if(event->key() == Qt::Key_A)
    {
        qDebug() << "按下了 a 键";
    }

    // 3. 判断是否按下了【组合键 Ctrl + A】
    // event->modifiers():获取修饰键(Ctrl/Shift/Alt)
    // Qt::ControlModifier:Ctrl键修饰符常量
    if(event->key() == Qt::Key_A && event->modifiers() == Qt::ControlModifier)
    {
        qDebug() << "按下了 Ctrl + a 键";
    }
}

键盘事件概述

键盘事件定义 :Qt 中响应键盘按键操作的底层事件机制 ,窗口获得焦点时,按下或释放键盘按键会触发对应事件函数,完成键盘交互的自定义处理。

核心作用:实现单按键、组合键的操作监听与逻辑响应,是 Qt 键盘交互的基础实现方式。

键盘事件核心函数与类

keyPressEvent 函数 :Qt 提供的键盘按下事件虚函数,窗口获取焦点后,按下任意键盘按键会自动触发该函数,为键盘按下操作的核心处理入口。

复制代码
// 重写键盘按下事件函数
void Widget::keyPressEvent(QKeyEvent *event)
{
    // 自定义键盘按键处理逻辑
}

QKeyEvent 类 :Qt 专属的键盘事件参数封装类,存储按键编码、修饰键、按键状态等全部键盘操作信息,作为参数传递给键盘事件函数。

按键参数获取方法

event->key() :获取当前按下按键的键值编码,返回整型数值,每个按键对应 Qt 定义的唯一常量,用于精准判断按下的具体按键。

复制代码
// 打印按键编码,判断A键
qDebug() << "按键编码:" << event->key();
if(event->key() == Qt::Key_A)
{
    qDebug() << "按下了 a 键";
}

event->modifiers() :获取键盘操作的修饰键状态 ,用于检测 Ctrl、Shift、Alt 等辅助按键是否按下,是实现组合键功能的核心方法。

修饰键常量说明

Qt::ControlModifier :Qt 定义的Ctrl 键修饰符常量,对应键盘上的 Ctrl 按键,与 event->modifiers () 配合使用,完成 Ctrl 组合键的判断。

复制代码
// 判断Ctrl+A组合键
if(event->key() == Qt::Key_A && event->modifiers() == Qt::ControlModifier)
{
    qDebug() << "按下了 Ctrl + a 键";
}

QShortCut 底层实现原理

QShortCut 定位 :Qt 提供的快捷键封装组件 ,用于快速实现键盘快捷键功能。底层逻辑 :QShortCut 底层基于键盘事件 封装实现,内部通过解析 keyPressEvent 事件、调用 QKeyEvent 类参数、判断按键与修饰键状态,完成快捷键的触发与响应,属于键盘事件的上层封装组件


定时器事件

widget.cpp(重写定时器事件)

复制代码
#include "widget.h"
#include "ui_widget.h"

// 构造函数:初始化窗口与定时器
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 给 LCD 数字控件设置初始显示值:10
    ui->lcdNumber->display(10);

    // ==================== 开启定时器 ====================
    // startTimer(毫秒数):启动一个定时器,每隔指定时间自动触发 timerEvent 事件
    // 参数 1000 = 1秒,即每隔1秒触发一次定时器事件
    // 返回值:定时器的唯一 ID(timerId),用于区分多个定时器
    timerId = this->startTimer(1000);
}

// 析构函数:释放 UI 资源
Widget::~Widget()
{
    delete ui;
}

// ==================== 重写定时器事件函数 ====================
// 定时器触发时,自动调用该函数
void Widget::timerEvent(QTimerEvent *event)
{
    // 校验:只处理当前窗口的定时器(防止多个定时器冲突)
    // 如果触发的定时器ID 不等于 我们启动的定时器ID,直接返回不处理
    if(event->timerId() != this->timerId)
        return;

    // 获取 LCD 控件当前显示的整数值
    int value = ui->lcdNumber->intValue();

    // 判断:如果数值 ≤ 0,停止定时器,结束倒计时
    if(value <= 0)
    {
        // killTimer(定时器ID):关闭并销毁指定的定时器
        this->killTimer(this->timerId);
        return;
    }

    // 数值自减 1,并更新显示到 LCD 控件(实现每秒减1的倒计时效果)
    ui->lcdNumber->display(--value);
}

定时器事件与 QTimer 关系

QTimer 是 Qt 提供的高级定时器类 ,底层基于timerEvent 定时器事件封装实现,简化定时器的使用流程。

startTimer 函数

startTimer 用于启动定时器,参数为毫秒单位 的时间间隔,函数返回值为定时器唯一 ID

复制代码
// 启动定时器,每隔1000毫秒触发一次timerEvent
timerId = this->startTimer(1000);

killTimer 函数

killTimer 用于关闭指定 ID 的定时器,停止定时器事件触发,释放定时器资源。

复制代码
// 关闭指定ID的定时器
this->killTimer(this->timerId);

timerId 变量

timerId 存储startTimer 返回的定时器唯一标识 ,用于区分多个定时器,在timerEvent 中校验触发来源。

复制代码
// 校验定时器ID,确保处理指定定时器
if(event->timerId() != this->timerId)
    return;

timerEvent 函数

timerEvent 是定时器触发的事件处理函数 ,重写该函数实现定时逻辑,通过QTimerEvent 参数获取触发的定时器 ID。

复制代码
// 重写定时器事件
void Widget::timerEvent(QTimerEvent *event)
{
    // 定时执行的逻辑代码
}

窗口移动和大小改变事件

widget.cpp(重写窗口移动事件与窗口大小改变事件)

复制代码
#include "widget.h"
#include "ui_widget.h"
// 调试输出头文件(打印窗口位置、大小)
#include <QDebug>
// 窗口移动/大小改变事件头文件(Qt自动隐式包含,显式写出更规范)
#include <QMoveEvent>
#include <QResizeEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    // 初始化UI界面
    ui->setupUi(this);
}

Widget::~Widget()
{
    // 释放UI对象,Qt自动管理内存
    delete ui;
}

// 重写:窗口移动事件
// 当**拖动窗口改变位置**时,自动触发该函数
void Widget::moveEvent(QMoveEvent *event)
{
    // event->pos():获取窗口**左上角**移动后的**新坐标**
    // 坐标格式:(x, y),相对于屏幕左上角
    qDebug() << "窗口新位置:" << event->pos();
}

// 重写:窗口大小改变事件
// 当**拖动窗口边缘缩放大小**时,自动触发该函数
void Widget::resizeEvent(QResizeEvent *event)
{
    // event->size():获取窗口缩放后的**新尺寸**
    // 尺寸格式:(宽度, 高度)
    qDebug() << "窗口新尺寸:" << event->size();
}

窗口移动事件

moveEvent:窗口位置发生改变时自动触发的事件函数,拖动窗口、代码设置窗口位置均会触发该函数。

复制代码
void Widget::moveEvent(QMoveEvent *event)
{
    qDebug() << "窗口新位置:" << event->pos();
}

QMoveEvent :封装窗口移动相关信息的事件类,event->pos() 获取窗口左上角的新坐标

窗口大小改变事件

resizeEvent:窗口尺寸发生改变时自动触发的事件函数,拖动窗口边框、代码修改窗口大小均会触发该函数。

复制代码
void Widget::resizeEvent(QResizeEvent *event)
{
    qDebug() << "窗口新尺寸:" << event->size();
}

QResizeEvent :封装窗口尺寸变化相关信息的事件类,event->size() 获取窗口缩放后的新尺寸


文件操作

mainwindow.cpp(文件打开、读取、写入、关闭操作)

复制代码
// 需补充的头文件(代码中使用了对应组件,必须包含)
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMenuBar>    // 菜单栏
#include <QMenu>       // 菜单
#include <QAction>     // 菜单项
#include <QPlainTextEdit> // 纯文本编辑框
#include <QFont>       // 字体
#include <QFileDialog> // 文件对话框
#include <QStatusBar>  // 状态栏
#include <QFile>       // 文件读写
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    // 设置窗口标题(优化:增强用户体验)
    this->setWindowTitle("简易记事本");

    // ==================== 1. 创建并设置菜单栏 ====================
    // 获取主窗口自带的菜单栏(QMainWindow内置组件)
    QMenuBar* menubar = this->menuBar();
    // 将菜单栏绑定到主窗口(固定写法)
    this->setMenuBar(menubar);

    // ==================== 2. 添加顶级菜单 ====================
    // 创建"文件"菜单
    QMenu* menu = new QMenu("文件");
    // 将菜单添加到菜单栏中
    menubar->addMenu(menu);

    // ==================== 3. 添加菜单项(动作) ====================
    QAction* action1 = new QAction("打开"); // 打开文件菜单项
    QAction* action2 = new QAction("保存"); // 保存文件菜单项
    menu->addAction(action1);  // 把菜单项添加到文件菜单
    menu->addAction(action2);

    // ==================== 4. 设置中央文本编辑控件 ====================
    // QPlainTextEdit:纯文本编辑框(记事本核心控件)
    edit = new QPlainTextEdit();
    // 将编辑框设为主窗口的中央控件(铺满窗口)
    this->setCentralWidget(edit);

    // ==================== 5. 设置编辑框默认字体大小 ====================
    QFont font;
    font.setPixelSize(20); // 字体大小20像素
    edit->setFont(font);   // 应用字体到编辑框

    // ==================== 6. 绑定菜单项信号槽 ====================
    // 点击"打开" → 触发 handleAction1 函数
    connect(action1, &QAction::triggered, this, &MainWindow::handleAction1);
    // 点击"保存" → 触发 handleAction2 函数
    connect(action2, &QAction::triggered, this, &MainWindow::handleAction2);
}

MainWindow::~MainWindow()
{
    delete ui;
}

// ==================== 槽函数:处理【打开】菜单项 ====================
void MainWindow::handleAction1()
{
    // 弹出文件打开对话框,获取选中文件的路径
    QString path = QFileDialog::getOpenFileName(this);

    // 获取状态栏,显示文件路径
    QStatusBar* statusbar = this->statusBar();
    statusbar->showMessage(path);

    // 创建文件对象,绑定文件路径
    QFile file(path);
    // 以【只读】方式打开文件
    bool ret = file.open(QFile::ReadOnly);
    // 打开失败:状态栏提示错误
    if(ret == false)
    {
        statusbar->showMessage(path + " 打开失败.");
        return;
    }

    // 读取文件所有内容(字符串格式)
    QString text = file.readAll();
    // 将文件内容显示到文本编辑框
    edit->setPlainText(text);

    // 关闭文件
    file.close();
}

// ==================== 槽函数:处理【保存】菜单项 ====================
void MainWindow::handleAction2()
{
    // ❌ BUG:保存文件应该用 getSaveFileName,此处误用了打开文件对话框
    QString path = QFileDialog::getOpenFileName(this);

    // 获取状态栏,显示文件路径
    QStatusBar* statusbar = this->statusBar();
    statusbar->showMessage(path);

    // 创建文件对象
    QFile file(path);
    // 以【只写】方式打开文件
    bool ret = file.open(QFile::WriteOnly);
    if(ret == false)
    {
        statusbar->showMessage(path + " 打开失败.");
        return;
    }

    // 获取编辑框中的所有文本
    const QString text = edit->toPlainText();
    // 将文本转为UTF-8编码,写入文件
    file.write(text.toUtf8());

    // 关闭文件
    file.close();
}

widget.cpp(QFileInfo 获取文件信息)

复制代码
#include "widget.h"
#include "ui_widget.h"
// 文件对话框:弹出选择文件的窗口
#include <QFileDialog>
// 文件信息类:专门获取文件的名称、后缀、大小、路径等元信息
#include <QFileInfo>
// 调试输出:在控制台打印文件信息
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    // 初始化UI设计师创建的界面(包含一个pushButton按钮)
    ui->setupUi(this);
}

Widget::~Widget()
{
    // 释放UI对象资源
    delete ui;
}

// 按钮点击槽函数:点击按钮后执行
void Widget::on_pushButton_clicked()
{
    // 1. 弹出文件选择对话框,获取用户选中文件的**完整路径**
    // 取消选择则返回空字符串
    QString path = QFileDialog::getOpenFileName(this);

    // 2. 创建文件信息对象,绑定选中的文件路径
    // QFileInfo:Qt提供的专用类,快速读取文件/目录的所有元信息
    QFileInfo fileInfo(path);

    // 3. 打印文件的各类信息
    qDebug() << "文件名(带后缀):" << fileInfo.fileName();    // 获取文件名(如:test.txt)
    qDebug() << "文件后缀(扩展名):" << fileInfo.suffix();    // 获取文件后缀(如:txt)
    qDebug() << "文件所在路径:" << fileInfo.path();            // 获取文件路径(不含文件名)
    qDebug() << "文件大小(字节):" << fileInfo.size();        // 获取文件大小,单位:字节
    qDebug() << "是否是文件:" << fileInfo.isFile();            // 判断是否为文件(是返回true)
    qDebug() << "是否是目录:" << fileInfo.isDir();             // 判断是否为文件夹(是返回true)
}

QFile 类

核心定义 :Qt 提供的文件操作核心类 ,用于实现文件的打开、读取、写入、关闭 等底层 IO 操作,支持文本文件、二进制文件的读写。初始化方式:通过文件路径构造对象,绑定目标文件。

复制代码
// 绑定文件路径,创建QFile对象
QFile file(path);

open 函数 :打开文件,参数指定打开模式 ,返回 bool 类型表示打开结果。常用打开模式:QFile::ReadOnly (只读)、QFile::WriteOnly(只写)。

复制代码
// 以只读模式打开文件
bool ret = file.open(QFile::ReadOnly);
// 以只写模式打开文件
bool ret = file.open(QFile::WriteOnly);

readAll 函数 :读取文件全部内容,返回 QString 类型的文本数据。

复制代码
// 读取文件所有内容
QString text = file.readAll();

write 函数 :向文件写入数据,参数为字节数组类型数据。配合toUtf8将字符串转为 UTF-8 编码,适配中文写入。

复制代码
// 写入文本数据
file.write(text.toUtf8());

close 函数:关闭文件,释放文件占用的系统资源,文件操作完成后必须调用。

复制代码
// 关闭文件
file.close();

QPlainTextEdit 类

核心定义 :Qt 提供的纯文本编辑控件 ,作为记事本类应用的核心组件,支持文本的显示、编辑、多行输入。setCentralWidget :将控件设置为主窗口的中央控件,自动铺满整个主窗口区域。

复制代码
// 创建文本编辑控件
QPlainTextEdit* edit = new QPlainTextEdit();
// 设置为中央控件
this->setCentralWidget(edit);

setPlainText 函数 :向控件中设置文本内容,覆盖原有内容。

复制代码
// 将文件内容显示到编辑框
edit->setPlainText(text);

toPlainText 函数 :获取控件中的全部文本内容,返回 QString 类型数据。

复制代码
// 获取编辑框内的文本
QString text = edit->toPlainText();

setFont 函数 :设置控件的文本字体,支持字体大小、样式配置。

复制代码
QFont font;
font.setPixelSize(20);
edit->setFont(font);

QFileInfo 类

核心定义 :Qt 提供的文件信息获取类 ,用于读取文件 / 目录的元数据信息 ,不涉及文件内容的读写。初始化方式:通过文件路径构造对象,绑定目标文件。

复制代码
// 创建文件信息对象
QFileInfo fileInfo(path);

fileName 函数 :获取完整文件名(包含后缀名)。

复制代码
fileInfo.fileName();

suffix 函数 :获取文件后缀名(扩展名)。

复制代码
fileInfo.suffix();

path 函数 :获取文件所在目录路径(不含文件名)。

复制代码
fileInfo.path();

size 函数 :获取文件大小 ,单位为字节

复制代码
fileInfo.size();

isFile 函数 :判断路径是否为文件,返回 bool 类型结果。

复制代码
fileInfo.isFile();

isDir 函数 :判断路径是否为目录,返回 bool 类型结果。

复制代码
fileInfo.isDir();

本回答由AI生成,仅供参考,请仔细甄别,如有需求请咨询专业人士。


多线程与互斥锁

widget.cpp(重写run函数实现倒计时)

复制代码
----------------------------------- thread.h -----------------------------------
#ifndef THREAD_H
#define THREAD_H

#include <QWidget>
// QThread:Qt线程基类,自定义线程必须继承它
#include <QThread>

// 自定义线程类 Thread
// 继承 QThread,重写 run() 方法实现子线程逻辑
// Q_OBJECT:Qt元对象宏,支持信号槽机制
class Thread : public QThread
{
    Q_OBJECT
public:
    // 线程构造函数
    Thread();

protected:
    // 重写 QThread 的核心函数 run()
    // 线程启动后,**子线程**会自动执行该函数内的代码
    void run() override;

signals:
    // 自定义信号:用于子线程向主线程发送通知
    // 信号只需声明,无需实现
    void notify();
};

#endif // THREAD_H

----------------------------------- thread.cpp -----------------------------------
#include "thread.h"

// 线程构造函数(空实现,无需初始化逻辑)
Thread::Thread()
{

}

// 核心:线程执行函数
// 注意:此函数运行在【子线程】中,不会阻塞主线程/UI界面
void Thread::run()
{
    // 循环10次,实现10秒倒计时通知
    for(int i = 0; i < 10; i++)
    {
        sleep(1);  // 线程休眠1秒(QThread::sleep,仅在子线程中使用)
        emit notify(); // 发送自定义信号,通知主线程更新UI
    }
}

----------------------------------- widget.h -----------------------------------
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
// 包含自定义线程类头文件
#include "thread.h"

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; } // UI命名空间
QT_END_NAMESPACE

// 主窗口类(UI界面,运行在【主线程】)
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    // 自定义槽函数:接收子线程的信号,更新LCD数字
    void handle();

private:
    Ui::Widget *ui; // UI对象指针

    // 定义自定义线程对象(栈对象,无需手动new/delete)
    Thread thread;
};
#endif // WIDGET_H

----------------------------------- widget.cpp -----------------------------------
#include "widget.h"
#include "ui_widget.h"

// 主窗口构造函数
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this); // 初始化UI界面

    // LCD数字控件初始显示 10
    ui->lcdNumber->display(10);

    // ==================== 核心:跨线程信号槽连接 ====================
    // 连接 子线程的notify信号 → 主线程的handle槽函数
    // Qt自动支持跨线程信号槽通信,无需额外处理
    connect(&thread, &Thread::notify, this, &Widget::handle);

    // ==================== 启动子线程 ====================
    // start():调用run()函数,线程开始执行(不会阻塞主线程)
    thread.start();
}

// 主窗口析构函数
Widget::~Widget()
{
    delete ui;
}

// 槽函数:接收子线程信号,更新LCD数字(运行在【主线程/UI线程】)
void Widget::handle()
{
    // 获取LCD当前显示的数字
    int value = ui->lcdNumber->intValue();
    // 数字自减1,更新显示(实现倒计时效果)
    ui->lcdNumber->display(--value);
}

widget.cpp(加锁让多个线程对同一变量正确递增)

复制代码
----------------------------------- thread.h -----------------------------------
#ifndef THREAD_H
#define THREAD_H

#include <QWidget>
// QThread:Qt线程基类
#include <QThread>
// QMutex:互斥锁,用于解决多线程同时访问共享变量的冲突问题
#include <QMutex>

// 自定义线程类,继承 QThread
class Thread : public QThread
{
    Q_OBJECT
public:
    Thread();

    // 重写线程执行函数,子线程要做的任务写在这里
    void run() override;

    // 静态变量:所有 Thread 对象共享同一个 num(共享资源)
    static int num;

    // 静态互斥锁:所有线程共用一把锁,保护共享变量 num
    static QMutex mutex;
};

#endif // THREAD_H

----------------------------------- thread.cpp -----------------------------------
#include "thread.h"

// 【静态成员变量必须在类外初始化】
// 初始化共享变量,初始值为 0
int Thread::num = 0;

// 初始化静态互斥锁
QMutex Thread::mutex;

Thread::Thread()
{

}

// 子线程执行函数
void Thread::run()
{
    // 每个线程对共享变量 num 进行 50000 次 ++ 操作
    for(int i = 0; i < 50000; i++)
    {
        // ==================== 互斥锁核心用法 ====================
        // 加锁:只允许一个线程进入临界区
        mutex.lock();

        // 临界区代码:操作共享变量
        num++;

        // 解锁:释放锁,让其他线程可以访问
        mutex.unlock();
    }
}

----------------------------------- widget.cpp -----------------------------------
#include "widget.h"
#include "ui_widget.h"
// 调试输出,打印最终结果
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 创建两个线程对象
    Thread t1;
    Thread t2;

    // 启动两个子线程,开始执行 run() 函数
    t1.start();
    t2.start();

    // wait():主线程等待子线程执行完成
    // 等 t1、t2 都执行完,主线程才继续往下走
    t1.wait();
    t2.wait();

    // 打印最终结果
    // 有锁:结果一定是 100000(5万+5万)
    // 无锁:结果会小于 100000(线程安全问题)
    qDebug() << "最终结果:" << Thread::num;
}

Widget::~Widget()
{
    delete ui;
}

QThread 类

QThread 是 Qt 提供的线程基类 ,用于实现跨平台的多线程编程,封装了底层线程操作,简化线程的创建、启动、等待与销毁流程。核心成员函数

  • start() :启动线程,自动调用run() 函数,线程进入运行状态,不阻塞主线程。

  • run() :线程的入口函数,子线程执行的逻辑代码均写在该函数内,需重写实现自定义逻辑。

  • wait():阻塞当前线程,等待目标子线程执行完毕后再继续执行。

  • sleep(int secs) :线程休眠指定秒数,仅能在子线程中调用。

    // 启动线程
    thread.start();
    // 主线程等待子线程结束
    t1.wait();
    // 子线程休眠1秒
    sleep(1);

QMutex 互斥锁

QMutex 是 Qt 提供的互斥锁类 ,用于解决多线程共享资源竞争 问题,保证同一时间只有一个线程访问临界区资源。核心成员函数

  • lock():对共享资源加锁,若资源已被锁定,线程阻塞等待直至锁释放。

  • unlock() :释放锁,允许其他线程访问共享资源。使用规则:操作共享变量前后必须成对调用加锁与解锁,避免死锁。

    // 加锁
    mutex.lock();
    // 操作共享资源
    num++;
    // 解锁
    mutex.unlock();

Qt 与 Linux 创建线程的区别

Linux 线程创建 :基于 POSIX 标准,调用pthread_create函数,直接指定线程执行的普通函数,面向过程编程。Qt 线程创建 :基于面向对象 思想,必须自定义类继承 QThread ,重写类中的run() 函数作为线程入口,依托 Qt 元对象系统支持信号槽跨线程通信。核心差异:Linux 线程依赖函数指针,Qt 线程依赖类的继承与重写,跨平台性更强。

线程入口函数 run 重写机制

run() 是 QThread 类的虚函数 ,为线程的唯一入口函数。自定义线程类继承 QThread 后,必须重写 run () 函数,将子线程的业务逻辑写入该函数。调用start() 启动线程时,Qt 底层自动在子线程中执行重写后的run() 函数,主线程保持运行状态。

复制代码
// 自定义线程类重写run函数
class Thread : public QThread
{
protected:
    void run() override;
};
// 子线程逻辑实现
void Thread::run()
{
    // 子线程执行代码
}

QWaitCondition 条件等待

QWaitCondition 是 Qt 提供的条件变量类 ,配合QMutex 使用,实现线程间的等待与唤醒机制。核心成员函数

  • wait(QMutex *mutex):解锁互斥锁并阻塞当前线程,等待唤醒信号。
  • wakeOne():唤醒一个处于等待状态的线程。
  • wakeAll() :唤醒所有处于等待状态的线程。适用场景:生产者 - 消费者模型、线程同步等待。

QSemaphore 信号量

QSemaphore 是 Qt 提供的计数信号量类 ,用于控制多线程对有限共享资源 的访问,支持多资源同步。核心成员函数

  • acquire(int n = 1):申请 n 个资源,资源不足时线程阻塞。
  • release(int n = 1) :释放 n 个资源,唤醒等待的线程。适用场景:限制同时访问资源的线程数量,比互斥锁更灵活。

UDP

widget.cpp(编写UDP回显服务端)

复制代码
#include "widget.h"
#include "ui_widget.h"
// UDP 套接字类
#include <QUdpSocket>
// 消息提示框
#include <QMessageBox>
// UDP 数据报类(封装数据、目标IP、端口)
#include <QNetworkDatagram>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // ==================== 1. 创建 UDP 套接字对象 ====================
    // 指定父对象 this,由 Qt 自动管理内存
    socket = new QUdpSocket(this);

    // 设置窗口标题为"服务器"
    this->setWindowTitle("服务器");

    // ==================== 2. 绑定信号槽:当有数据到达时触发 ====================
    // readyRead():UDP 套接字收到数据时自动发出此信号
    // 连接到槽函数 processRequest() 处理消息
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);

    // ==================== 3. 绑定端口,启动 UDP 服务 ====================
    // bind(IP, 端口):监听本机 9090 端口的所有 UDP 消息
    // QHostAddress::Any:监听所有网卡(本地/局域网)
    bool ret = socket->bind(QHostAddress::Any, 9090);

    // 如果绑定失败(端口被占用等),弹出错误提示
    if(ret == false)
    {
        QMessageBox::critical(this, "服务器启动出错", socket->errorString());
        return;
    }
}

Widget::~Widget()
{
    delete ui;
}

// ==================== 核心槽函数:处理收到的 UDP 数据 ====================
void Widget::processRequest()
{
    // ==================== 1. 读取客户端发送的数据报 ====================
    // receiveDatagram():读取一整条 UDP 数据报(包含数据+发送方IP+端口)
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();

    // 从数据报中提取 客户端发送的原始数据(转 QString)
    QString request = requestDatagram.data();

    // ==================== 2. 构造服务器回复消息 ====================
    // 简单回显:客户端消息 + :Server
    const QString& response = request + ":Server";

    // ==================== 3. 构造回复数据报,发送给客户端 ====================
    // 参数:(数据内容, 客户端IP, 客户端端口)
    QNetworkDatagram responseDatagram(
        response.toUtf8(),        // 要发送的数据
        requestDatagram.senderAddress(),  // 客户端 IP
        requestDatagram.senderPort()      // 客户端 端口
    );

    // 写数据报 → 发送回客户端
    socket->writeDatagram(responseDatagram);

    // ==================== 4. 在界面日志窗口显示通信记录 ====================
    QString log = "[" + requestDatagram.senderAddress().toString() +
            ":" + QString::number(requestDatagram.senderPort()) + "]" +
            " 客户端请求:" + request + " | 服务端回应:" + response;

    // 添加到列表控件显示
    ui->listWidget->addItem(log);
}

widget.cpp(编写UDP回显客户端)

复制代码
#include "widget.h"
#include "ui_widget.h"
// UDP 套接字
#include <QUdpSocket>
// UDP 数据报(封装数据、IP、端口)
#include <QNetworkDatagram>

// 服务器 IP 和端口(与服务器端保持一致)
const QString& SERVER_IP = "127.0.0.1";  // 本地回环地址
const quint16 SERVER_PORT = 9090;        // 服务器端口 9090

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // ==================== 1. 创建 UDP 套接字 ====================
    // 客户端不需要 bind,只需要创建 socket 对象即可
    socket = new QUdpSocket(this);

    // 设置窗口标题为"客户端"
    this->setWindowTitle("客户端");

    // ==================== 2. 绑定信号槽 ====================
    // 当服务器回复消息时,socket 会发出 readyRead 信号
    // 触发 processResponse 函数读取服务器数据
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processResponse);
}

Widget::~Widget()
{
    delete ui;
}

// ==================== 槽函数:接收服务器的回复 ====================
void Widget::processResponse()
{
    // 读取服务器发来的 UDP 数据报
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();

    // 提取数据内容
    QString response = responseDatagram.data();

    // 在界面列表中显示服务器消息
    ui->listWidget->addItem("服务器说:" + response);
}

// ==================== 按钮点击:发送消息给服务器 ====================
void Widget::on_pushButton_clicked()
{
    // 获取输入框中的文本
    QString text = ui->lineEdit->text();

    // ==================== 构造 UDP 数据报 ====================
    // 参数:发送的数据、服务器IP、服务器端口
    QNetworkDatagram requestDatagram(
        text.toUtf8(),          // 消息内容
        QHostAddress(SERVER_IP),// 目标IP
        SERVER_PORT             // 目标端口
    );

    // ==================== 发送数据报 ====================
    socket->writeDatagram(requestDatagram);

    // 在界面显示自己发送的消息
    ui->listWidget->addItem("客户端说:" + text);

    // 清空输入框
    ui->lineEdit->setText("");
}

网络模块配置

Qt 网络模块依赖 :使用 UDP/TCP 等网络 API,必须在项目的.pro文件中添加配置,加载network模块,否则无法识别网络相关类。

复制代码
# .pro文件配置
QT += core gui network

QUdpSocket 类

QUdpSocket 是 Qt 提供的UDP 套接字封装类,实现无连接的 UDP 网络通信,封装底层 UDP 套接字操作,支持跨平台使用。

复制代码
// 创建UDP套接字对象
socket = new QUdpSocket(this);

readyRead 信号

readyReadQUdpSocket 类的内置信号,当套接字接收到网络数据报时自动触发,是 UDP 数据接收的核心通知机制。

执行顺序规范

信号槽绑定优先 :必须先完成readyRead信号与槽函数的关联,再执行端口绑定操作。执行顺序错误会导致数据到达时无处理函数,造成数据丢失。

复制代码
// 正确顺序:先绑定信号槽
connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
// 后绑定端口
socket->bind(QHostAddress::Any, 9090);

服务端与客户端 QUdpSocket 作用

服务端 socket :负责绑定端口 、监听网络数据、接收客户端数据、向客户端回发数据。客户端 socket :无需绑定端口,负责向服务端发送数据、接收服务端的回复数据。

QNetworkDatagram 类

QNetworkDatagram 是 Qt 封装的UDP 数据报类 ,统一封装数据内容、目标 IP 地址、目标端口、发送方 IP / 端口信息,简化 UDP 数据传输的参数管理。

复制代码
// 构造UDP数据报
QNetworkDatagram requestDatagram(
    text.toUtf8(),
    QHostAddress(SERVER_IP),
    SERVER_PORT
);

QUdpSocket 核心成员函数

bind 函数:服务端专用,绑定 IP 地址与端口号,开启端口监听,参数为监听地址与端口。

复制代码
// 绑定本机所有网卡的9090端口
bool ret = socket->bind(QHostAddress::Any, 9090);

receiveDatagram 函数 :读取套接字中的 UDP 数据报,返回封装后的QNetworkDatagram对象。

复制代码
const QNetworkDatagram& requestDatagram = socket->receiveDatagram();

writeDatagram 函数 :发送 UDP 数据报,支持直接传入QNetworkDatagram对象完成数据发送。

复制代码
socket->writeDatagram(responseDatagram);

Qt UDP 函数与 Linux UDP 函数对应关系

bind :Qt 的bind函数 对应 Linux 的bind函数,完成套接字与端口的绑定。receiveDatagram :Qt 的receiveDatagram函数 对应 Linux 的recvfrom函数,接收 UDP 数据并获取发送方地址信息。writeDatagram :Qt 的writeDatagram函数 对应 Linux 的sendto函数,向指定地址端口发送 UDP 数据。


TCP

widget.cpp(编写TCP回显服务端)

复制代码
#include "widget.h"
#include "ui_widget.h"
// TCP 服务器类
#include <QTcpServer>
// TCP 套接字类(客户端连接)
#include <QTcpSocket>
// 错误提示框
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置窗口标题
    this->setWindowTitle("服务端");

    // ==================== 1. 创建 TCP 服务器对象 ====================
    // QTcpServer* tcpServer;  (需要在头文件中声明)
    tcpServer = new QTcpServer(this);

    // ==================== 2. 绑定信号槽:有新客户端连接时触发 ====================
    // newConnection():当有客户端尝试连接时,服务器发出此信号
    connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);

    // ==================== 3. 启动监听 ====================
    // listen(IP, 端口):监听本机 9090 端口,等待客户端连接
    // QHostAddress::Any:监听所有网络地址(局域网/本地都能连)
    bool ret = tcpServer->listen(QHostAddress::Any, 9090);

    // 如果启动失败(端口被占用),弹出错误并退出程序
    if(ret == false)
    {
        QMessageBox::critical(this, "服务端启动失败", tcpServer->errorString());
        exit(-1); // 退出程序
    }
}

Widget::~Widget()
{
    delete ui;
}

// ==================== 核心槽函数:处理新客户端连接 ====================
void Widget::processConnection()
{
    // ==================== 1. 获取与客户端通信的 Socket ====================
    // nextPendingConnection():取出等待连接的客户端套接字
    QTcpSocket* clientSock = tcpServer->nextPendingConnection();

    // ==================== 2. 打印客户端上线日志 ====================
    // peerAddress():客户端IP
    // peerPort():客户端端口
    QString log = "[" + clientSock->peerAddress().toString() + 
                  ":" + QString::number(clientSock->peerPort()) + 
                  "] 客户端上线.";
    ui->listWidget->addItem(log);

    // ==================== 3. 绑定【客户端发消息】信号(Lambda 写法) ====================
    // readyRead():客户端发送数据过来时触发
    connect(clientSock, &QTcpSocket::readyRead, this, [=](){
        // 读取客户端发送的所有数据
        QString request = clientSock->readAll();
        
        // 构造回复消息(回显机制)
        const QString response = request + ":Server";
        
        // 将回复消息发送给客户端
        clientSock->write(response.toUtf8());

        // 打印通信日志
        QString log = "[" + clientSock->peerAddress().toString() +
                ":" + QString::number(clientSock->peerPort()) + "]" + 
                " client:" + request + ", server:" + response;
        ui->listWidget->addItem(log);
    });

    // ==================== 4. 绑定【客户端断开连接】信号(Lambda 写法) ====================
    connect(clientSock, &QTcpSocket::disconnected, this, [=](){
        // 打印客户端下线日志
        QString log = "[" + clientSock->peerAddress().toString() + 
                      ":" + QString::number(clientSock->peerPort()) + 
                      "] 客户端下线.";
        ui->listWidget->addItem(log);

        // 安全释放客户端 Socket 内存(必须用 deleteLater)
        clientSock->deleteLater();
    });
}

widget.cpp(编写TCP回显客户端)

复制代码
#include "widget.h"
#include "ui_widget.h"
// TCP 套接字(客户端用)
#include <QTcpSocket>
// 错误提示框
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget))
{
    ui->setupUi(this);

    // 设置窗口标题
    this->setWindowTitle("客户端");

    // ==================== 1. 创建 TCP 客户端套接字 ====================
    // QTcpSocket* tcpSocket;  (需要在头文件中声明)
    tcpSocket = new QTcpSocket(this);

    // ==================== 2. 主动连接服务器 ====================
    // 参数:服务器IP、服务器端口
    // 127.0.0.1 = 本地回环地址(自己连自己)
    tcpSocket->connectToHost("127.0.0.1", 9090);

    // ==================== 3. 绑定信号槽:收到服务端消息时触发 ====================
    connect(tcpSocket, &QTcpSocket::readyRead, this, &Widget::processResponse);

    // ==================== 4. 等待连接成功(阻塞等待) ====================
    bool ret = tcpSocket->waitForConnected();
    if(ret == false)
    {
        // 连接失败:弹出错误提示并退出
        QMessageBox::critical(this, "连接服务器出错", tcpSocket->errorString());
        exit(-1);
    }
}

Widget::~Widget()
{
    delete ui;
}

// ==================== 槽函数:处理服务端发来的消息 ====================
void Widget::processResponse()
{
    // 读取服务端发送的所有数据
    QString response = tcpSocket->readAll();

    // 在界面列表显示服务端消息
    ui->listWidget->addItem("服务端说:" + response);
}

// ==================== 按钮点击:发送消息给服务端 ====================
void Widget::on_pushButton_clicked()
{
    // 获取输入框文本
    const QString& text = ui->lineEdit->text();

    // ==================== 发送数据给服务器 ====================
    tcpSocket->write(text.toUtf8());

    // 在界面显示自己发送的消息
    ui->listWidget->addItem("客户端说:" + text);

    // 清空输入框
    ui->lineEdit->setText("");
}

QTcpServer 类

核心定义 :Qt 提供的TCP 服务器套接字类 ,用于监听端口、接收客户端连接请求,是 TCP 服务端的核心组件。构造函数:创建服务器对象,指定父对象实现自动内存管理。

复制代码
tcpServer = new QTcpServer(this);

newConnection 信号 :QTcpServer 的核心信号,检测到新客户端连接请求时自动触发,是处理新连接的入口。

复制代码
connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);

listen 函数:启动服务端监听,绑定 IP 地址与端口号,等待客户端连接。

复制代码
bool ret = tcpServer->listen(QHostAddress::Any, 9090);

nextPendingConnection 函数 :获取已建立连接的客户端 QTcpSocket 对象,完成连接接收。

复制代码
QTcpSocket* clientSock = tcpServer->nextPendingConnection();

errorString 函数 :获取服务器操作的错误描述信息,用于异常提示。

复制代码
QMessageBox::critical(this, "服务端启动失败", tcpServer->errorString());

QTcpSocket 类

核心定义 :Qt 提供的TCP 通信套接字类,服务端与客户端通用,实现 TCP 数据的收发、连接状态管理。

服务端使用场景 通过QTcpServer::nextPendingConnection获取客户端套接字,用于与单个客户端通信。

客户端使用场景 直接new创建对象,调用connectToHost主动连接服务端。

复制代码
tcpSocket = new QTcpSocket(this);

核心信号 readyRead 信号 :套接字接收到对端数据时自动触发,用于读取接收数据。

复制代码
connect(clientSock, &QTcpSocket::readyRead, this, [=](){});

disconnected 信号TCP 连接断开时自动触发,用于处理下线逻辑。

复制代码
connect(clientSock, &QTcpSocket::disconnected, this, [=](){});

核心成员函数 connectToHost 函数:客户端专用,主动向服务端发起 TCP 连接,指定服务端 IP 与端口。

复制代码
tcpSocket->connectToHost("127.0.0.1", 9090);

waitForConnected 函数 :客户端专用,阻塞等待连接建立完成,返回连接结果。

复制代码
bool ret = tcpSocket->waitForConnected();

readAll 函数 :读取套接字缓冲区的所有数据,返回字符串类型数据。

复制代码
QString request = clientSock->readAll();

write 函数:向对端发送数据,参数为字节数组类型。

复制代码
clientSock->write(response.toUtf8());

peerAddress 函数 :获取对端 IP 地址,服务端获取客户端 IP,客户端获取服务端 IP。

复制代码
clientSock->peerAddress().toString()

peerPort 函数 :获取对端端口号,获取通信对端的端口信息。

复制代码
QString::number(clientSock->peerPort())

deleteLater 函数:安全释放套接字对象内存,避免内存泄漏。

复制代码
clientSock->deleteLater();

HTTP

复制代码
#include "widget.h"
#include "ui_widget.h"
// HTTP 网络请求管理类
#include <QNetworkAccessManager>
// HTTP 请求封装类
#include <QNetworkRequest>
// HTTP 响应(回复)类
#include <QNetworkReply>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置窗口标题
    this->setWindowTitle("HTTP 客户端");

    // ==================== 1. 创建网络访问管理器 ====================
    // 整个程序只需要一个 manager,负责发送所有 HTTP 请求
    manager = new QNetworkAccessManager(this);
}

Widget::~Widget()
{
    delete ui;
}

// ==================== 按钮点击:发送 HTTP GET 请求 ====================
void Widget::on_pushButton_clicked()
{
    // ==================== 2. 封装 URL ====================
    // 从输入框获取网址(如 https://www.baidu.com)
    QUrl url(ui->lineEdit->text());

    // ==================== 3. 封装 HTTP 请求 ====================
    QNetworkRequest request(url);

    // ==================== 4. 发送 GET 请求 ====================
    // manager->get() 发送请求,返回一个 QNetworkReply* 用于接收结果
    QNetworkReply* response = manager->get(request);

    // ==================== 5. 监听请求完成信号 ====================
    // finished():当服务器返回数据、请求结束时触发
    connect(response, &QNetworkReply::finished, this, [=](){
        // 判断请求是否成功(无错误)
        if(response->error() == QNetworkReply::NoError)
        {
            // 成功:读取服务器返回的所有数据(网页源代码)
            QString html = response->readAll();
            // 显示到文本框
            ui->plainTextEdit->setPlainText(html);
        }
        else
        {
            // 失败:显示错误信息(如 无法连接、404、超时等)
            ui->plainTextEdit->setPlainText("请求失败:" + response->errorString());
        }

        // ==================== 6. 释放内存 ====================
        // 请求结束后必须释放 reply 对象,防止内存泄漏
        response->deleteLater();
    });
}

QNetworkAccessManager 类

QNetworkAccessManager 是 Qt 提供的HTTP 网络请求管理核心类,负责统一发送、调度所有 HTTP 请求,管控网络请求的完整生命周期,程序中创建单例对象即可满足所有请求需求。

复制代码
// 创建网络访问管理对象
manager = new QNetworkAccessManager(this);

QNetworkRequest 类

QNetworkRequest 是 Qt 提供的HTTP 请求封装类 ,用于存储请求的 URL 地址、请求头、请求参数等核心信息,作为 HTTP 请求的载体传入发送接口。QUrl 是 Qt 提供的URL 地址封装类,将字符串格式的网址转换为 Qt 标准 URL 格式,适配网络请求规范。

复制代码
// 封装请求URL
QUrl url(ui->lineEdit->text());
// 封装完整HTTP请求
QNetworkRequest request(url);

QNetworkReply 类

QNetworkReply 是 Qt 提供的HTTP 响应封装类 ,存储服务器返回的响应数据、错误码、错误描述、响应头等全部结果信息,由get函数自动创建并返回。

核心成员函数

get 函数 :QNetworkAccessManager 的成员函数,用于发送HTTP GET 请求,参数为封装好的 QNetworkRequest 对象,返回值为 QNetworkReply 指针。

复制代码
QNetworkReply* response = manager->get(request);

error 函数 :QNetworkReply 的成员函数,获取 HTTP 请求的错误码QNetworkReply::NoError为常量,表示请求无错误、执行成功。

复制代码
if(response->error() == QNetworkReply::NoError)

errorString 函数 :QNetworkReply 的成员函数,获取请求失败的文本描述信息,直观展示网络错误原因。

复制代码
response->errorString()

readAll 函数 :QNetworkReply 的成员函数,读取服务器返回的全部响应数据,GET 请求中对应网页的 HTML 源码。

复制代码
QString html = response->readAll();

deleteLater 函数 :QNetworkReply 的成员函数,安全释放响应对象内存,避免请求完成后产生内存泄漏。

复制代码
response->deleteLater();

核心信号

finished 信号 :QNetworkReply 的内置信号,HTTP 请求执行完毕(无论成功或失败)时自动触发,是处理服务器响应结果的核心入口。

复制代码
connect(response, &QNetworkReply::finished, this, [=](){
    // 处理响应结果逻辑
});
相关推荐
用户805533698033 小时前
嵌入式Linux驱动开发——模块参数与内核调试:让模块"活"起来的魔法
qt
liulilittle3 小时前
静态隧道 UDP 限制与绕过:以 DMIT 机房为例
网络·网络协议·udp
一条闲鱼_mytube3 小时前
【深入理解】HTTP/3 与 QUIC 协议:从原理到 Go 语言实战
网络协议·http·golang
冉佳驹4 小时前
Qt【第七篇】 ——— QSS 样式表与绘图 API 核心用法及 UI 定制功能总结
qt·qbrush·qpainter·qss·paintevent·qpen
森G4 小时前
45、QGraphicsScene 与 QGraphicsView 框架---------绘图
c++·qt
sycmancia4 小时前
QT——计算器核心算法
开发语言·qt·算法
EmbeddedCore5 小时前
物联网通讯协议怎么选?MQTT、TCP、UDP、HTTP、HTTPS全面解析
物联网·tcp/ip·http
深念Y5 小时前
前端实时通信技术:HTTP轮询、SSE、WebSocket、WebRTC
前端·websocket·网络协议·http·实时互动·轮询·实时通信
Pyeako15 小时前
PyQt5 + PaddleOCR实战:打造桌面级实时文字识别工具
开发语言·人工智能·python·qt·paddleocr·pyqt5