C++ 网络编程基础:Socket API 与 TCP 通信

围绕 Socket API 与 TCP/UDP 通信的核心概念、函数对照、示例代码与常见坑。 核心概念速查 概念 说明 关键点 Socket 网络通信端点 文件描述符抽象 TCP 面向连接、可靠 三次握手、四次挥手 UDP 无连接、不可靠 快速、轻量 sockaddr 通用地址结构 IPv4/IPv6 统一接口 字节序 大端/小端转换 htons() htonl() TCP 三次握手与四次挥手 三次握手(建立连接): Client ──SYN────▶ Server Client ◀─SYN+Ack─ Server Client ──Ack────▶ Server 四次挥手(关闭连接): Client ──FIN────▶ Server Client ◀─Ack───── Server Client ◀─FIN───── Server Client ──Ack────▶ Server TCP 客户端-服务器模型 服务器: socket() → bind() → listen() → accept() → recv/send → close() 客户端: socket() → connect() → send/recv → close() 核心函数对照表 函数 用途 参数要点 socket() 创建套接字 AF_INET, SOCK_STREAM, 0 bind() 绑定地址 套接字 + sockaddr_in listen() 监听连接 backlog 队列长度 accept() 接受连接 返回新套接字描述符 connect() 发起连接 服务器地址 send/recv() 发送/接收数据 缓冲区 + 长度 htons/htonl() 主机→网络字节序 short/long ntohs/ntohl() 网络→主机字节序 short/long 从 Qt 到原生 Socket Qt 写法 原生等价物 差异要点 server.listen(QHostAddress::Any, 8080) socket() + bind() + listen() 三步 Qt 一行帮你做三件事 newConnection 信号 → nextPendingConnection() accept() Qt 信号通知;原生阻塞死等 socket->connectToHost("ip", port) inet_pton() + connect() IP 解析要自己做 socket->write(bytes) send(fd, buf, len, 0) 返回值要检查,可能只发一半 readyRead 信号 → readAll() recv(fd, buf, size, 0) 没有"就绪通知",直接阻塞读 errorOccurred / stateChanged 检查返回值 + errno 错误要手动判断 QUdpSocket::writeDatagram/readDatagram sendto()/recvfrom() 多一个对端地址参数 QHostAddress("127.0.0.1") sockaddr_in + inet_pton() 字符串 IP 手动转结构体 端口字节序自动处理 必须自己 htons() Qt 内部帮你转了 核心差异:Qt 信号槽背后是「事件循环 + 就绪通知」(reactor 模式)。原生编程没有这个循环——要么阻塞死等,要么用线程,要么用 select/epoll 自己搭事件循环。学原生网络的本质,就是把 Qt 隐藏的三件事(fd 管理、字节序、事件分发)亲手做一遍。 ...

2026 年 10 月 13 日

C++ 项目约定:目录结构、依赖管理、构建与 Git

所谓「约定」= 一套项目结构规范 + 依赖管理规范:以后不管做什么项目,都按这个套路来,不用每次重新想。 适用背景:Windows(VS2022)只做 Boost + OpenCV,不做 Qt;Qt 开发放在 Linux(Ubuntu 虚拟机/服务器)。 文中 <OpenCV安装目录>、<Boost根目录>、<项目根目录> 为占位符,替换成本机实际目录即可。 一、总体原则 三句话: CMake 是唯一的构建系统,不碰 qmake、不碰手动 .vcxproj。 依赖一律走环境变量 + find_package,不在 CMakeLists.txt 里硬编码路径。 Windows 端 IDE 是 VS2022 + CMake 项目模式;Linux 端用 Qt Creator 或 VS Code。 二、目录结构约定(Windows 端) 每个项目固定长这样: <项目根目录>\<项目名>\ ├── CMakeLists.txt ├── CMakePresets.json ├── CMakeUserPresets.json # 本地预设(不提交 Git) ├── .gitignore ├── README.md ├── src\ # 源码 │ ├── main.cpp │ ├── core\ # Boost 业务逻辑 │ │ ├── Service.h │ │ └── Service.cpp │ └── vision\ # OpenCV 图像处理 │ ├── ImageProcessor.h │ └── ImageProcessor.cpp ├── include\ # 对外头文件(可选) ├── resources\ # 静态资源、图片(可选) ├── tests\ # 测试代码(可选) ├── third_party\ # 第三方源码(可选,一般不用) └── out\ # 构建输出(不提交 Git) └── build\ ├── debug\ └── release\ 关键点: ...

2026 年 10 月 13 日

QMovie 动画播放

QMovie 是 Qt 中用于播放动画(主要是 GIF 格式)的类。它本身不是一个控件,不能直接显示在界面上,通常需要借助 QLabel(或 QGraphicsView)来展示播放效果。 它的核心作用是:逐帧读取动图文件,按时间间隔把每一帧渲染出来,从而实现动画播放。 1. 效果演示 通过 QLabel 显示一个加载中的 GIF 动画,并提供「开始 / 暂停 / 停止」三个按钮,演示 QMovie 的常用接口。 2. 属性和方法 QMovie 的工作流程大致是:加载文件 → 启动播放 → 播放结束。常用的属性和方法如下。 2.1 构造与加载 // 通过文件名构造 QMovie(const QString &fileName, const QByteArray &format = QByteArray(), QObject *parent = nullptr); // 通过 QIODevice 构造(可用于资源文件、网络数据等) QMovie(QIODevice *device, const QByteArray &format = QByteArray(), QObject *parent = nullptr); // 设置 / 获取动画的文件名 void setFileName(const QString &fileName); QString fileName() const; // 判断动画是否有效(文件是否能被正确解析) bool isValid() const; 2.2 播放控制 // 开始播放 void start(); // 暂停 / 继续(QMovie 没有 pause()/resume(),只有这个开关式接口,且它是一个槽) void setPaused(bool paused); // true 为暂停,false 为继续 // 没有 paused()/isPaused() 这样的查询方法,是否暂停用 state() 判断 MovieState state() const; // NotRunning / Paused / Running // 停止播放,并回到第一帧 void stop(); // 缓存模式 CacheMode cacheMode() const; void setCacheMode(CacheMode mode); QMovie 提供的播放控制比较简单,注意 stop() 与 setPaused(true) 的区别: ...

2026 年 10 月 13 日

QSlider滑动条

本文搬运转载自 明王讲QT - QSlider,仅供学习记录使用。版权归原作者所有。 QSlider 滑动条控件 QSlider是滑动条控件 它允许用户通过拖动滑块来选择一个范围内的值。通常用于调整音量、亮度、进度等需要连续数值输入的场景 1. 效果演示三个滑动条,分别用来调节RGB三个颜色的值,并显示到左侧各自的文本框中 并且将RGB这三个颜色的组合,来设置上面的文本框的背景颜色 2. 属性和方法QSlider继承自QAbstractSlider,它的绝大多数属性都是从QAbstractSlider继承而来的。 QSlider有很多属性,完整的可查看帮助文档。这里仅列出常用的属性和方法 2.1 值滑动条和值相关的属性包括:当前值、最大值、最小值 // 获取和设置当前值 int value() const; void setValue(int); // 获取和设置最大值 int maximum() const; void setMaximum(int); // 获取和设置最小值 int minimum() const; void setMinimum(int); // 一次设置最大值和最小值 void setRange(int min, int max) 2.2 方向Qt中滑动条有水平滑动条和垂直滑动条之分,通过修改orientation属性来设置 // 获取和设置滑动条的方向 Qt::Orientation orientation() const; void setOrientation(Qt::Orientation); 2.3 步长步长是指滑动条一次增加或减少的值。这里又包括两个步长: singleStep:是指按键盘的左右箭头(←/→)时的步长 pageStep:是指点击滑块两侧时的步长 // 获取和设置singleStep int singleStep() const; void setSingleStep(int); // 获取和设置pageStep int pageStep() const; void setPageStep(int); 2.4 刻度线```cpp // 获取和设置刻度线间隔 int tickInterval() const void setTickInterval(int ti) // 获取和设置刻度线的位置 QSlider::TickPosition tickPosition() const void setTickPosition(QSlider::TickPosition position) ...

2026 年 10 月 13 日

Qt Model/View 架构详解 — Item Model 体系

一、架构总览 Qt 的 Model/View 架构是对经典 MVC 模式的实现与改造: 层级 职责 对应类 Model(数据源) 存储数据、提供数据访问接口 QAbstractItemModel 系列 View(视图) 显示数据、处理用户交互 QTableView, QTreeView, QListView Delegate(委托) 自定义渲染和编辑 QStyledItemDelegate 核心思想:数据(Model)与展示(View)完全解耦,同一个 Model 可以同时被多个 View 显示。 二、类继承体系 QObject └── QAbstractItemModel ← 所有 Item Model 的根基类 ├── QAbstractListModel ← 简单一维列表的抽象基类 │ └── QStringListModel ├── QAbstractTableModel ← 二维表格的抽象基类(自己写表模型时继承它) ├── QStandardItemModel ← 开箱即用的通用模型(树/表/列表通吃) └── SQL 模型是三级单链,不是从 QAbstractTableModel 派生: QAbstractItemModel → QSqlQueryModel → QSqlTableModel → QSqlRelationalTableModel 其他直接从 QAbstractItemModel 派生的专用模型: QFileSystemModel — 文件系统树 三、核心索引系统:QModelIndex Model/View 的通信枢纽是 QModelIndex(模型索引)。 ...

2026 年 10 月 13 日

Qt 架构阅读方法:从构造函数入手

看 Qt 桌面程序,不要从头文件按顺序"从小往大"看——Qt 没有顺序这一说,要从主页面构造函数去总结架构。 一、核心观点 Qt 程序(尤其带界面的桌面应用)里,构造函数是"所有功能的展示": 成员初始化 资源/数据加载函数 功能函数(布局、控件创建、业务初始化) 信号与槽连接(最重要) 想看懂某一个页面的功能大局,步骤是: 看构造函数 → 看构造函数里的注释 → 找被调用的实现方法 → 看"哪些被使用了、有什么用" → 再去对应头文件/源文件看定义 → 看懂 而不是:打开一个头文件,从头到尾按声明顺序啃。 二、为什么 Qt 不能"从头文件按顺序看" 误区 原因 按头文件声明顺序读 头文件只是声明集合,不含调用关系;顺序 ≠ 执行顺序 从最小的类看起 小类之间没有依赖线索,看完全部也不懂"为什么存在" 先读业务函数 不知道它被谁调用、在什么时机触发(信号槽是异步/解耦的) 想找"程序入口→顺序执行" Qt 是事件驱动的,没有线性执行流 Qt 程序的"执行顺序"不是代码顺序,而是: main() 里创建主窗口 → 执行构造函数 构造函数一次性搭好:界面、数据、连接 之后程序进入事件循环,靠信号槽响应——调用关系藏在构造函数里 所以构造函数 = 架构入口,是唯一能"一览全局"的地方。 三、构造函数里到底有什么 以常见主窗口为例: MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // 1. 窗口属性初始化 setWindowTitle("示例程序"); resize(800, 600); // 2. 加载函数:资源、配置、数据 loadConfig(); // 读配置文件 initDatabase(); // 连接数据库 setupToolbar(); // 加载图标/工具栏 // 3. 功能函数:创建控件与布局 createCentralWidget(); createStatusBar(); createMenu(); // 4. ⭐ 信号与槽连接(核心:把"谁"和"谁"绑起来) connect(ui->startBtn, &QPushButton::clicked, this, &MainWindow::onStart); connect(this, &MainWindow::dataReady, ui->table, &TableWidget::refresh); connect(timer, &QTimer::timeout, this, &MainWindow::updateStatus); } 构造函数 = 组装说明书: ...

2026 年 10 月 13 日

Qt 网络编程 01:TCP 客户端与服务端(第一天)

适用版本:Qt 6.9 / MinGW / C++17 更新日期:2026-08-19 源码位置:<你的工程目录>\net(TcpSocket / Tcpserver / TCP_Client / TCP_Server 四个项目) 目录 一、今天做了什么 二、项目清单与角色分工 三、准备工作:.pro 加 network 模块 四、控制台版:客户端 TcpSocket 五、控制台版:服务器 Tcpserver(多客户端广播) 六、GUI 版:TCP_Client 七、GUI 版:TCP_Server 八、核心知识点总结 九、今天踩的坑 / 值得注意的点 十、进阶写法:多客户端封装 ClientConnection 模式 十一、明天可以做什么 一、今天做了什么 用 Qt 实现了 TCP 客户端 / 服务器 双向通信,共 4 个项目: 项目 类型 角色 特点 TcpSocket 控制台(QCoreApplication) 客户端 连接即自动发一条 “Hello Server!” Tcpserver 控制台(QCoreApplication) 服务器 多客户端 + 广播 TCP_Client GUI(QWidget) 客户端 界面输入 IP/端口,收发消息 TCP_Server GUI(QWidget) 服务器 界面选择端口,收发消息 我已有 Qt 基础,但网络编程是零基础。今天的关键收获是建立了"客户端/服务器"模型和 Qt 异步收发的心智模型。 ...

2026 年 10 月 13 日

Qt 网络编程学习路线(网络编程零基础视角)

适用人群:已掌握 Qt 基础(信号槽、事件循环、QWidget),网络编程零基础 适用版本:Qt 6.x(Qt 5 大部分通用) 更新日期:2026-08-19 目录 1. 学习定位与目标 2. 网络编程零基础必须先懂的 6 个概念 3. 阶段一:TCP 编程入门(第 1~2 周) 4. 阶段二:TCP 深入与实战化(第 3~4 周) 5. 阶段三:UDP 编程(第 5 周) 6. 阶段四:HTTP 与高层网络(第 6~7 周) 7. 阶段五:进阶与安全(第 8 周起) 8. 阶段六:实战项目 9. 推荐资源 10. 总结 1. 学习定位与目标 核心要点:你的 Qt 基础(信号槽、事件循环、对象树)是最大的优势——Qt 把网络编程封装成了"发信号 + 收信号",你要补的不是 C++ 网络 API,而是网络协议的基本概念。 1.1 为什么用 Qt 学网络编程 优势 说明 异步事件驱动 收发数据都靠信号槽触发,不用自己写多线程阻塞 IO 跨平台 Windows / Linux / Android 同一套代码 高层封装 QTcpSocket / QUdpSocket / QNetworkAccessManager 把 socket API 包装成 Qt 风格 与 GUI 天然结合 收发数据直接刷新界面,客户端/服务器程序最容易做出可视化效果 1.2 和 C++ 原生 socket 的对比 原生 C++(Winsock / POSIX) Qt 封装 socket() / bind() / listen() / accept() QTcpServer::listen() / nextPendingConnection() connect() / send() / recv() QTcpSocket::connectToHost() / write() / readAll() 阻塞线程等待,需多线程处理 信号槽异步通知,事件循环驱动 手动管理缓冲区、错误码 readyRead 信号 + readAll(),errorString() 结论:先学概念,再学 Qt API。概念(TCP、端口、粘包)是通用的,Qt 只是更友好的实现工具。 ...

2026 年 10 月 13 日

Qt5 信号槽重载与 explicit 关键字详解

适用版本:Qt 5.x / C++14+ 更新日期:2026-07-29 目录 一、信号与槽函数重载(Overload) 场景假设 方案一:QOverload / qOverload(最推荐) 方案二:static_cast 显式转型 方案三:Lambda 表达式(最灵活) 信号和槽同时重载的混搭处理 总结建议 二、explicit 关键字详解 1. 不加 explicit(允许隐式转换) 2. 加上 explicit(禁止隐式转换) 3. Qt 中的经典实战场景 总结 一、信号与槽函数重载(Overload) 在 Qt5 中,信号和槽函数发生重载是非常常见的场景,也是最容易引起编译错误或运行时行为不符合预期的地方。 由于 Qt5 使用 &ClassName::functionName 的新式语法,当遇到重载时,编译器无法自动推断你要使用哪一个具体的函数指针,因此必须进行强制类型转换或使用 Lambda 表达式来消除歧义。 场景假设 假设有一个类,里面定义了两个重载的普通成员函数(在 Qt5 中都可以当槽): class Receiver : public QObject { Q_OBJECT public: // 槽函数 1:无参 void handleUpdate() { qDebug() << "无参槽被触发"; } // 槽函数 2:带一个 int 参数 void handleUpdate(int value) { qDebug() << "带参槽被触发,值为" << value; } }; 现在有一个信号想要连接到这个槽,有以下几种方案: ...

2026 年 10 月 13 日

QTabWidget 与 QStackedWidget 的区别及常用方式

一、概述 QTabWidget 和 QStackedWidget 都是 Qt 中用于管理多页面切换的控件,但它们的设计定位和使用场景有显著区别。 特性 QTabWidget QStackedWidget 内置导航 自带标签栏(Tab Bar) 无内置导航 页面切换方式 点击标签切换 需通过代码 setCurrentIndex() 切换 独立性 独立控件,开箱即用 需配合其他控件(如 QListWidget)使用 灵活性 固定标签栏样式 可自定义任意切换方式 适用场景 设置对话框、分类浏览 向导流程、无标签界面 二、QTabWidget — 自带标签栏的多页容器 2.1 特点 顶部(或底部/左侧/右侧)自带 QTabBar 每个 Tab 对应一个页面(QWidget 子类) 点击 Tab 自动切换页面,无需额外代码 2.2 常用方式 // 创建 TabWidget QTabWidget *tabWidget = new QTabWidget(this); // 创建页面 QWidget *page1 = new QWidget(); QWidget *page2 = new QWidget(); // 添加页面(传入 widget 和标题) tabWidget->addTab(page1, "基本设置"); tabWidget->addTab(page2, "高级设置"); // 设置当前页 tabWidget->setCurrentIndex(0); // 常用设置 tabWidget->setTabPosition(QTabWidget::North); // 标签位置 tabWidget->setTabShape(QTabWidget::Rounded); // 标签形状 tabWidget->setMovable(true); // 信号与槽:标签切换 connect(tabWidget, &QTabWidget::currentChanged, [](int index) { qDebug() << "切换到第" << index << "页"; }); 三、QStackedWidget — 无内置导航的页面堆栈 3.1 特点 不提供任何导航控件,只负责管理页面 一次只显示一个页面(当前页) 必须通过代码或其他控件控制切换 3.2 常用方式 // 创建 StackedWidget QStackedWidget *stackedWidget = new QStackedWidget(this); // 创建页面 QWidget *page1 = new QWidget(); QWidget *page2 = new QWidget(); QWidget *page3 = new QWidget(); // 添加页面(返回 index) int idx1 = stackedWidget->addWidget(page1); int idx2 = stackedWidget->addWidget(page2); int idx3 = stackedWidget->addWidget(page3); // 切换页面 stackedWidget->setCurrentIndex(1); // 按索引切换 stackedWidget->setCurrentWidget(page3); // 按指针切换 // 获取当前页面 int currentIdx = stackedWidget->currentIndex(); 3.3 配合 QListWidget 实现导航 // 左侧列表 + 右侧堆栈的经典布局 QListWidget *list = new QListWidget(); list->addItem("基本设置"); list->addItem("高级设置"); list->addItem("关于"); QStackedWidget *stack = new QStackedWidget(); stack->addWidget(new BasicSettingsPage()); stack->addWidget(new AdvancedSettingsPage()); stack->addWidget(new AboutPage()); // 连接信号 connect(list, &QListWidget::currentRowChanged, stack, &QStackedWidget::setCurrentIndex); 3.4 配合 QPushButton 实现向导流程 // 上一步 / 下一步 向导 connect(btnNext, &QPushButton::clicked, [=]() { int next = stack->currentIndex() + 1; if (next < stack->count()) { stack->setCurrentIndex(next); } }); connect(btnPrev, &QPushButton::clicked, [=]() { int prev = stack->currentIndex() - 1; if (prev >= 0) { stack->setCurrentIndex(prev); } }); 四、核心区别总结 对比维度 QTabWidget QStackedWidget 内置 Tab 栏 有 无 实现代码量 少(开箱即用) 多(需自行实现导航) 切换方式 点击 Tab setCurrentIndex() / setCurrentWidget() 自定义导航 受限(可隐藏 Tab 栏) 完全灵活 典型场景 偏好设置、分类浏览 安装向导、多步表单 灵活性 低(样式固定) 高(完全可控) 如何选择? 需要标准标签切换 → QTabWidget(简单快速) 需要自定义切换方式 → QStackedWidget(如列表导航、按钮翻页) 需要向导/步骤流程 → QStackedWidget(搭配按钮更自然) 既有标签又想自定义 → 用 QTabWidget 隐藏 TabBar + 代码控制切换 五、完整示例:左侧列表 + 右侧堆栈 #include <QApplication> #include <QHBoxLayout> #include <QListWidget> #include <QStackedWidget> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; QHBoxLayout *layout = new QHBoxLayout(&window); // 左侧导航 QListWidget *list = new QListWidget(); list->addItem("页面一"); list->addItem("页面二"); list->addItem("页面三"); list->setFixedWidth(120); // 右侧页面 QStackedWidget *stack = new QStackedWidget(); stack->addWidget(new QLabel("这是页面一的内容")); stack->addWidget(new QLabel("这是页面二的内容")); stack->addWidget(new QLabel("这是页面三的内容")); layout->addWidget(list); layout->addWidget(stack, 1); connect(list, &QListWidget::currentRowChanged, stack, &QStackedWidget::setCurrentIndex); window.resize(600, 400); window.show(); return app.exec(); }

2026 年 10 月 13 日