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, [=](){
    // 处理响应结果逻辑
});
相关推荐
Goodbye4 天前
大模型无状态架构:从 HTTP 协议到 Harness AI 工程的深度解析
http
Quz4 天前
QML Hello World 入门示例
qt
xcyxiner7 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner8 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner8 天前
DicomViewer (添加模型类)3
qt
xcyxiner9 天前
DicomViewer (目录调整) 2
qt
xcyxiner9 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
霜落长河10 天前
抛弃TCP改用UDP,HTTP3怎么了?
http
桥田智能11 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
之歆11 天前
现代 HTTP 客户端深度解析:Fetch 与 Axios
chrome·网络协议·http