Qt C++系统资源监控工具开发实战:跨平台实现CPU内存磁盘网络监控
2026/7/21 3:16:04 网站建设 项目流程

大家好,我是专注于分享Qt/C++实战经验的博主。在课程设计或毕业设计项目中,一个既能展示C++功底,又能体现Qt跨平台GUI开发能力的“系统资源监控工具”是很多同学的首选。这类项目看似简单,但涉及系统API调用、多线程、实时数据可视化等多个核心知识点,网上资料往往零散不成体系。

本文将手把手带你从零构建一个功能完整的Qt C++系统资源监控工具,涵盖CPU、内存、磁盘、网络等核心指标的实时监控与图表展示。文章不仅提供可直接运行的完整代码,还会深入讲解每个模块的设计原理、跨平台兼容性处理以及开发中常见的“坑点”。无论你是正在寻找课设/大作业选题的学生,还是希望提升Qt实战能力的开发者,都能从本文中获得一套可直接复用的解决方案。

1. 项目概述与核心技术栈

1.1 什么是系统资源监控工具?

系统资源监控工具,顾名思义,是一个用于实时采集、显示和分析计算机硬件资源(如CPU、内存、磁盘、网络)使用情况的软件。它通过周期性地查询操作系统提供的接口(API)来获取各项指标数据,并以图形化界面(GUI)的形式直观地展示给用户,例如动态变化的曲线图、仪表盘或数字面板。

在课程设计或大作业中,开发这样一个工具具有多重价值:

  1. 综合性强:融合了C++核心语法、面向对象设计、Qt GUI编程、多线程、系统编程等多个知识点。
  2. 实践价值高:模拟了真实世界中运维监控、性能分析工具的开发场景。
  3. 可扩展性好:基础框架搭建完成后,可以轻松添加监控进程、GPU温度、电池状态等更多功能。
  4. 跨平台潜力:基于Qt框架,项目可以相对容易地移植到Windows、Linux、macOS等不同操作系统。

1.2 核心功能设计

我们计划实现一个具备以下核心功能的监控工具:

  • CPU监控:实时显示总体CPU使用率,以及每个逻辑核心的使用率。
  • 内存监控:实时显示物理内存和虚拟内存(交换空间)的使用量、总量及使用百分比。
  • 磁盘监控:显示各磁盘分区的总容量、已用空间、可用空间及使用率。
  • 网络监控:实时显示指定网络接口的上传和下载速度。
  • 数据可视化:使用Qt Charts模块绘制CPU和内存使用率的历史趋势曲线图。
  • 界面布局:采用清晰的仪表盘和图表布局,数据更新流畅。

1.3 技术栈选型与原理

  • C++:作为核心开发语言,负责底层逻辑和系统API调用。其高性能特性适合处理高频的数据采集任务。
  • Qt 5/6:作为GUI框架,提供跨平台的窗口、控件和图表组件。我们将主要使用QWidgetsQtCharts模块。
  • 系统API
    • Windows:使用Windows.h中的PDH(性能数据助手)系列函数或GetSystemTimes等API。
    • Linux/macOS:通过读取/proc文件系统(Linux)或调用sysctlvm_stat等接口来获取信息。
  • 多线程 (QThread):将耗时的数据采集任务放在独立线程中,避免阻塞主UI线程,保证界面流畅。
  • 定时器 (QTimer):用于周期性地触发数据采集和界面更新。

设计原理:工具的核心是一个“生产者-消费者”模型。后台工作线程(生产者)定时从系统API读取数据,放入缓冲区或直接通过信号槽机制发送;主UI线程(消费者)接收数据并更新图表和控件。Qt的信号槽机制是线程间通信的安全桥梁。

2. 开发环境搭建

2.1 Qt安装与配置

本项目基于Qt 5.15 LTSQt 6.2+版本进行开发,这两个版本稳定且社区支持好。

  1. 下载安装:访问Qt官网,下载Qt Online Installer。安装时,务必勾选以下组件:

    • Qt套件:如Qt 5.15.2Qt 6.5.0
    • 编译器:Windows下选择MinGW 64-bitMSVC;Linux下通常用GCC;macOS下用Clang
    • 关键模块Qt Charts(用于绘图)。在安装器的“Select Components”步骤中,展开Qt版本,找到Qt Charts并勾选。
  2. 验证安装:打开Qt Creator,新建一个Qt Widgets Application项目,编译并运行。如果能成功弹出窗口,则基础环境配置正确。

2.2 项目创建与模块配置

  1. 在Qt Creator中,新建项目,选择Qt Widgets Application
  2. 给项目命名,例如SystemMonitor
  3. 在项目配置文件(.pro文件)中,需要添加charts模块以支持图表功能。打开SystemMonitor.pro,添加一行:
    QT += core gui charts
    对于Qt 6,可能还需要添加core5compat(如果使用旧式信号槽语法,但推荐使用新语法)。
  4. 为了支持C++11及以上特性,可以在.pro文件中添加:
    CONFIG += c++11

2.3 跨平台编译注意事项

  • Windows:使用MinGW或MSVC编译均可。如果使用MSVC,请确保已安装对应版本的Visual Studio Build Tools。
  • Linux:需要安装Qt的开发包和必要的系统库。在Ubuntu/Debian上,可以使用sudo apt install build-essential qtbase5-dev qtcharts5-dev
  • macOS:通过Homebrew安装Qt或使用官方安装包。确保Xcode Command Line Tools已安装。

3. 系统信息采集模块实现(核心)

这是项目的基石,我们需要为不同平台编写特定的代码来获取系统数据。我们将创建一个名为SystemInfo的类来封装这些平台相关的细节。

3.1 创建SystemInfo类

首先,在项目中新建一个C++类,命名为SystemInfo

systeminfo.h头文件:

#ifndef SYSTEMINFO_H #define SYSTEMINFO_H #include <QObject> #include <QVector> class SystemInfo : public QObject { Q_OBJECT public: explicit SystemInfo(QObject *parent = nullptr); ~SystemInfo(); // CPU信息 struct CpuData { double totalUsage; // 总使用率 QVector<double> coreUsage; // 每个核心的使用率 }; // 内存信息 struct MemoryData { quint64 totalPhysical; // 物理内存总量 (Bytes) quint64 usedPhysical; // 已用物理内存 (Bytes) quint64 totalSwap; // 交换分区总量 (Bytes) quint64 usedSwap; // 已用交换分区 (Bytes) }; // 磁盘信息 struct DiskData { QString name; // 磁盘名 (如 C:) quint64 totalBytes; // 总容量 quint64 freeBytes; // 可用容量 quint64 availableBytes; // 可用容量 (同free,为兼容性保留) }; // 网络信息 struct NetworkData { QString interfaceName; // 接口名 quint64 bytesReceived; // 接收总字节数 quint64 bytesSent; // 发送总字节数 }; // 获取数据的接口函数 CpuData getCpuData(); MemoryData getMemoryData(); QVector<DiskData> getDiskData(); NetworkData getNetworkData(const QString &interfaceName); signals: // 可以定义信号,当数据更新时发出(用于高级异步设计) void cpuDataUpdated(const CpuData &data); void memoryDataUpdated(const MemoryData &data); private: // 平台相关的私有实现函数 void initCpuMeasurement(); CpuData calculateCpuUsage(); MemoryData fetchMemoryInfo(); QVector<DiskData> fetchDiskInfo(); NetworkData fetchNetworkInfo(const QString &interfaceName); // 平台相关的成员变量,用于存储上一次的CPU时间等 #ifdef Q_OS_WIN // Windows-specific members (e.g., PDH query handles) void* m_cpuQuery = nullptr; #elif defined(Q_OS_LINUX) || defined(Q_OS_MACOS) // Linux/macOS-specific members QVector<quint64> m_lastCpuTotal; QVector<quint64> m_lastCpuIdle; #endif }; #endif // SYSTEMINFO_H

3.2 Windows平台实现 (systeminfo_win.cpp)

我们使用Windows.hPdh.h(性能数据助手)来获取信息,这是Windows下标准且相对高效的方法。

#include "systeminfo.h" #include <windows.h> #include <pdh.h> #include <psapi.h> // For memory info #include <QDebug> #include <QStorageInfo> #include <QNetworkInterface> #pragma comment(lib, "pdh.lib") SystemInfo::SystemInfo(QObject *parent) : QObject(parent) { initCpuMeasurement(); } SystemInfo::~SystemInfo() { #ifdef Q_OS_WIN if (m_cpuQuery) { PdhCloseQuery((PDH_HQUERY)m_cpuQuery); } #endif } void SystemInfo::initCpuMeasurement() { #ifdef Q_OS_WIN PDH_HQUERY cpuQuery; if (PdhOpenQuery(NULL, 0, &cpuQuery) == ERROR_SUCCESS) { m_cpuQuery = cpuQuery; // 添加所有CPU总使用率的计数器 PdhAddEnglishCounter(cpuQuery, L"\\Processor(_Total)\\% Processor Time", 0, NULL); // 也可以为每个核心添加计数器,这里省略... PdhCollectQueryData(cpuQuery); // 收集初始数据 } #endif } SystemInfo::CpuData SystemInfo::calculateCpuUsage() { CpuData data; data.totalUsage = 0.0; #ifdef Q_OS_WIN if (m_cpuQuery) { PDH_HQUERY hQuery = (PDH_HQUERY)m_cpuQuery; PdhCollectQueryData(hQuery); PDH_FMT_COUNTERVALUE counterVal; PDH_STATUS status = PdhGetFormattedCounterValue( (PDH_HCOUNTER)hQuery, // 这里需要每个计数器的句柄,简化处理 PDH_FMT_DOUBLE, NULL, &counterVal ); // 注意:上述代码是简化版。实际需要为每个计数器单独获取句柄并查询。 // 为了示例清晰,我们使用另一种更直接的方法:GetSystemTimes FILETIME idleTime, kernelTime, userTime; if (GetSystemTimes(&idleTime, &kernelTime, &userTime)) { static quint64 prevIdle = 0, prevKernel = 0, prevUser = 0; quint64 idle = (quint64(idleTime.dwHighDateTime) << 32) | idleTime.dwLowDateTime; quint64 kernel = (quint64(kernelTime.dwHighDateTime) << 32) | kernelTime.dwLowDateTime; quint64 user = (quint64(userTime.dwHighDateTime) << 32) | userTime.dwLowDateTime; quint64 total = (kernel + user); quint64 diffTotal = total - (prevKernel + prevUser); quint64 diffIdle = idle - prevIdle; if (diffTotal > 0 && prevIdle != 0) { // 不是第一次计算 data.totalUsage = (double)(diffTotal - diffIdle) / diffTotal * 100.0; } prevIdle = idle; prevKernel = kernel; prevUser = user; } } #endif // 简化处理,只返回总使用率。核心使用率实现类似但需要枚举处理器。 return data; } SystemInfo::MemoryData SystemInfo::fetchMemoryInfo() { MemoryData data = {0, 0, 0, 0}; #ifdef Q_OS_WIN MEMORYSTATUSEX memInfo; memInfo.dwLength = sizeof(MEMORYSTATUSEX); if (GlobalMemoryStatusEx(&memInfo)) { data.totalPhysical = memInfo.ullTotalPhys; data.usedPhysical = memInfo.ullTotalPhys - memInfo.ullAvailPhys; data.totalSwap = memInfo.ullTotalPageFile; data.usedSwap = memInfo.ullTotalPageFile - memInfo.ullAvailPageFile; } #endif return data; } QVector<SystemInfo::DiskData> SystemInfo::fetchDiskInfo() { QVector<DiskData> disks; QList<QStorageInfo> volumes = QStorageInfo::mountedVolumes(); for (const QStorageInfo &volume : volumes) { if (volume.isValid() && volume.isReady()) { DiskData disk; disk.name = volume.rootPath(); disk.totalBytes = volume.bytesTotal(); disk.freeBytes = volume.bytesFree(); disk.availableBytes = volume.bytesAvailable(); disks.append(disk); } } return disks; } SystemInfo::NetworkData SystemInfo::fetchNetworkInfo(const QString &interfaceName) { NetworkData data; data.interfaceName = interfaceName; static quint64 lastRecv = 0, lastSent = 0; quint64 currentRecv = 0, currentSent = 0; QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); for (const QNetworkInterface &interface : interfaces) { if (interface.name() == interfaceName && interface.isValid()) { QList<QNetworkAddressEntry> entries = interface.addressEntries(); for (const QNetworkAddressEntry &entry : entry) { // 统计所有地址的流量(简化处理,实际应区分IPv4/IPv6) } // Qt本身不直接提供历史流量统计。需要自己记录上一次的字节数。 // 这里使用一个静态变量模拟。生产环境应用更健壮的方法。 QNetworkInterface::InterfaceFlags flags = interface.flags(); if (flags & QNetworkInterface::IsUp && !(flags & QNetworkInterface::IsLoopBack)) { currentRecv = interface.bytesReceived(); currentSent = interface.bytesTransmitted(); break; } } } data.bytesReceived = currentRecv; data.bytesSent = currentSent; // 计算速度需要在外部定时器中进行:(current - last) / interval return data; } // 公有接口函数,内部调用平台相关的fetch函数 SystemInfo::CpuData SystemInfo::getCpuData() { return calculateCpuUsage(); } SystemInfo::MemoryData SystemInfo::getMemoryData() { return fetchMemoryInfo(); } QVector<SystemInfo::DiskData> SystemInfo::getDiskData() { return fetchDiskInfo(); } SystemInfo::NetworkData SystemInfo::getNetworkData(const QString &interfaceName) { return fetchNetworkInfo(interfaceName); }

3.3 Linux平台实现要点 (systeminfo_linux.cpp)

Linux下主要通过读取/proc文件系统的虚拟文件来获取信息。

// systeminfo_linux.cpp (部分关键函数示例) #include "systeminfo.h" #include <QFile> #include <QTextStream> #include <QStorageInfo> #include <QNetworkInterface> #include <QDebug> #include <sys/sysinfo.h> // for sysinfo() SystemInfo::SystemInfo(QObject *parent) : QObject(parent) { // 初始化CPU时间记录 initCpuMeasurement(); } void SystemInfo::initCpuMeasurement() { #ifdef Q_OS_LINUX QFile file("/proc/stat"); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in(&file); QString line = in.readLine(); // 读取第一行,即总的cpu信息 QStringList list = line.split(' ', QString::SkipEmptyParts); if (list.size() > 4 && list[0] == "cpu") { m_lastCpuTotal.clear(); m_lastCpuIdle.clear(); quint64 total = 0; for (int i = 1; i < list.size(); ++i) { total += list[i].toULongLong(); } quint64 idle = list[4].toULongLong(); // idle时间 m_lastCpuTotal.append(total); m_lastCpuIdle.append(idle); } file.close(); } #endif } SystemInfo::CpuData SystemInfo::calculateCpuUsage() { CpuData data; data.totalUsage = 0.0; #ifdef Q_OS_LINUX QFile file("/proc/stat"); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in(&file); QString line = in.readLine(); QStringList list = line.split(' ', QString::SkipEmptyParts); if (list.size() > 4 && list[0] == "cpu") { quint64 total = 0; for (int i = 1; i < list.size(); ++i) { total += list[i].toULongLong(); } quint64 idle = list[4].toULongLong(); if (!m_lastCpuTotal.isEmpty() && !m_lastCpuIdle.isEmpty()) { quint64 diffTotal = total - m_lastCpuTotal[0]; quint64 diffIdle = idle - m_lastCpuIdle[0]; if (diffTotal > 0) { data.totalUsage = (double)(diffTotal - diffIdle) / diffTotal * 100.0; } } // 更新上一次的值 m_lastCpuTotal[0] = total; m_lastCpuIdle[0] = idle; } file.close(); } #endif return data; } SystemInfo::MemoryData SystemInfo::fetchMemoryInfo() { MemoryData data = {0, 0, 0, 0}; #ifdef Q_OS_LINUX struct sysinfo info; if (sysinfo(&info) == 0) { // 注意单位转换,sysinfo 返回的是 mem_unit (通常是 bytes) quint64 totalPhys = info.totalram * info.mem_unit; quint64 freePhys = info.freeram * info.mem_unit; quint64 totalSwap = info.totalswap * info.mem_unit; quint64 freeSwap = info.freeswap * info.mem_unit; data.totalPhysical = totalPhys; data.usedPhysical = totalPhys - freePhys; data.totalSwap = totalSwap; data.usedSwap = totalSwap - freeSwap; } #endif return data; } // Disk和Network信息获取与Windows版类似,使用QStorageInfo和QNetworkInterface,它们是跨平台的。

macOS的实现思路与Linux类似,但读取系统信息的API不同(如使用host_statisticssysctl等)。由于篇幅限制,这里不展开,但原理相通:定义平台相关的宏(Q_OS_MAC),然后实现对应的私有函数。

4. 多线程数据采集与通信

为了避免数据采集(尤其是频繁读取/proc或调用系统API)阻塞UI主线程,导致界面卡顿,我们必须使用工作线程。

4.1 创建工作线程类 MonitorWorker

新建一个MonitorWorker类,继承自QObject,它将在单独的线程中运行。

monitorworker.h

#ifndef MONITORWORKER_H #define MONITORWORKER_H #include <QObject> #include <QTimer> #include "systeminfo.h" class MonitorWorker : public QObject { Q_OBJECT public: explicit MonitorWorker(QObject *parent = nullptr); ~MonitorWorker(); public slots: void startMonitoring(int intervalMs = 1000); // 开始监控,指定间隔 void stopMonitoring(); // 停止监控 signals: // 定义信号,用于将采集到的数据发送给主线程 void cpuDataUpdated(const SystemInfo::CpuData &data); void memoryDataUpdated(const SystemInfo::MemoryData &data); void diskDataUpdated(const QVector<SystemInfo::DiskData> &data); void networkDataUpdated(const SystemInfo::NetworkData &data); private slots: void onTimeout(); // 定时器超时,执行数据采集 private: QTimer *m_timer; SystemInfo *m_systemInfo; QString m_monitoredNetworkInterface; // 要监控的网络接口名 }; #endif // MONITORWORKER_H

monitorworker.cpp

#include "monitorworker.h" #include <QDebug> MonitorWorker::MonitorWorker(QObject *parent) : QObject(parent) , m_timer(new QTimer(this)) , m_systemInfo(new SystemInfo(this)) { m_monitoredNetworkInterface = "eth0"; // 默认监控eth0,实际应可配置 connect(m_timer, &QTimer::timeout, this, &MonitorWorker::onTimeout); } MonitorWorker::~MonitorWorker() { stopMonitoring(); } void MonitorWorker::startMonitoring(int intervalMs) { if (!m_timer->isActive()) { m_timer->start(intervalMs); qDebug() << "Monitoring started with interval" << intervalMs << "ms"; } } void MonitorWorker::stopMonitoring() { if (m_timer->isActive()) { m_timer->stop(); qDebug() << "Monitoring stopped"; } } void MonitorWorker::onTimeout() { // 1. 获取CPU数据并发送信号 SystemInfo::CpuData cpuData = m_systemInfo->getCpuData(); emit cpuDataUpdated(cpuData); // 2. 获取内存数据并发送信号 SystemInfo::MemoryData memData = m_systemInfo->getMemoryData(); emit memoryDataUpdated(memData); // 3. 获取磁盘数据(频率可以低一些,这里每次都会获取) static int diskCounter = 0; if (diskCounter % 5 == 0) { // 每5次采集一次磁盘数据 QVector<SystemInfo::DiskData> diskData = m_systemInfo->getDiskData(); emit diskDataUpdated(diskData); } diskCounter++; // 4. 获取网络数据并计算速度 static quint64 lastRecv = 0, lastSent = 0; SystemInfo::NetworkData netDataRaw = m_systemInfo->getNetworkData(m_monitoredNetworkInterface); // 计算瞬时速度 (Bytes/s) SystemInfo::NetworkData netDataWithSpeed = netDataRaw; if (lastRecv > 0 && lastSent > 0) { // 假设定时器间隔是1000ms netDataWithSpeed.bytesReceived = (netDataRaw.bytesReceived - lastRecv); // 差值即为每秒字节数 netDataWithSpeed.bytesSent = (netDataRaw.bytesSent - lastSent); } else { netDataWithSpeed.bytesReceived = 0; netDataWithSpeed.bytesSent = 0; } lastRecv = netDataRaw.bytesReceived; lastSent = netDataRaw.bytesSent; emit networkDataUpdated(netDataWithSpeed); }

4.2 在主线程中创建并管理Worker线程

在MainWindow的构造函数或初始化函数中设置线程。

mainwindow.cpp片段

#include "mainwindow.h" #include "ui_mainwindow.h" #include "monitorworker.h" #include <QThread> #include <QDebug> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , m_workerThread(new QThread(this)) , m_worker(new MonitorWorker()) { ui->setupUi(this); setupCharts(); // 初始化图表(后面会讲) setupConnections(); // 将worker对象移动到新线程 m_worker->moveToThread(m_workerThread); // 连接线程开始/结束信号到worker的槽 connect(m_workerThread, &QThread::started, m_worker, &MonitorWorker::startMonitoring); connect(m_workerThread, &QThread::finished, m_worker, &MonitorWorker::deleteLater); // 连接worker的数据信号到主窗口的更新槽 connect(m_worker, &MonitorWorker::cpuDataUpdated, this, &MainWindow::onCpuDataUpdated); connect(m_worker, &MonitorWorker::memoryDataUpdated, this, &MainWindow::onMemoryDataUpdated); connect(m_worker, &MonitorWorker::diskDataUpdated, this, &MainWindow::onDiskDataUpdated); connect(m_worker, &MonitorWorker::networkDataUpdated, this, &MainWindow::onNetworkDataUpdated); // 启动线程 m_workerThread->start(); } MainWindow::~MainWindow() { // 优雅地停止线程 m_workerThread->quit(); m_workerThread->wait(); // 等待线程结束 delete ui; } void MainWindow::setupConnections() { // 其他UI信号槽连接... } // 数据更新槽函数示例 void MainWindow::onCpuDataUpdated(const SystemInfo::CpuData &data) { // 更新UI上的CPU显示和图表 ui->labelCpuUsage->setText(QString::number(data.totalUsage, 'f', 1) + "%"); updateCpuChart(data.totalUsage); }

5. 使用Qt Charts实现数据可视化

Qt Charts模块提供了强大的图表组件,非常适合展示时间序列数据。

5.1 在项目中启用并初始化Charts

确保.pro文件中已添加QT += charts。在mainwindow.cpp中包含头文件:

#include <QtCharts> using namespace QtCharts;

5.2 创建CPU使用率历史曲线图

在MainWindow类中创建图表视图和序列。

mainwindow.h中添加

private: Ui::MainWindow *ui; QThread *m_workerThread; MonitorWorker *m_worker; // 图表相关 QChart *m_cpuChart; QLineSeries *m_cpuSeries; QValueAxis *m_cpuAxisX; QValueAxis *m_cpuAxisY; QChartView *m_cpuChartView; QVector<double> m_cpuHistoryData; // 存储最近N个数据点 int m_maxHistoryPoints = 60; // 显示60个点(即最近60秒) void setupCpuChart(); void updateCpuChart(double usage);

mainwindow.cpp中实现

void MainWindow::setupCpuChart() { // 创建图表 m_cpuChart = new QChart(); m_cpuChart->setTitle("CPU Usage History"); m_cpuChart->setTheme(QChart::ChartThemeDark); // 设置主题 // 创建折线序列 m_cpuSeries = new QLineSeries(); m_cpuSeries->setName("CPU %"); m_cpuChart->addSeries(m_cpuSeries); // 创建坐标轴 m_cpuAxisX = new QValueAxis(); m_cpuAxisY = new QValueAxis(); m_cpuAxisX->setRange(0, m_maxHistoryPoints - 1); m_cpuAxisX->setLabelFormat("%d"); m_cpuAxisX->setTitleText("Time (s ago)"); m_cpuAxisY->setRange(0, 100); // CPU使用率百分比 m_cpuAxisY->setTitleText("Usage (%)"); m_cpuChart->addAxis(m_cpuAxisX, Qt::AlignBottom); m_cpuChart->addAxis(m_cpuAxisY, Qt::AlignLeft); m_cpuSeries->attachAxis(m_cpuAxisX); m_cpuSeries->attachAxis(m_cpuAxisY); // 创建图表视图并添加到UI布局中 m_cpuChartView = new QChartView(m_cpuChart); m_cpuChartView->setRenderHint(QPainter::Antialiasing); ui->verticalLayout_chartArea->addWidget(m_cpuChartView); // 假设UI中有一个verticalLayout_chartArea } void MainWindow::updateCpuChart(double usage) { m_cpuHistoryData.append(usage); // 保持数据点数量不超过最大值 if (m_cpuHistoryData.size() > m_maxHistoryPoints) { m_cpuHistoryData.removeFirst(); } // 清空序列并重新添加点 m_cpuSeries->clear(); for (int i = 0; i < m_cpuHistoryData.size(); ++i) { // X轴:越新的数据点索引越大,但显示为“时间前推”,所以用 (m_maxHistoryPoints - 1 - (m_cpuHistoryData.size() - 1 - i)) // 简化:直接使用i作为X,从0到size-1,最新点在最右边。 m_cpuSeries->append(i, m_cpuHistoryData.at(i)); } // 更新X轴范围,使其看起来是滚动的 if (m_cpuHistoryData.size() >= m_maxHistoryPoints) { m_cpuAxisX->setRange(0, m_maxHistoryPoints - 1); } else { m_cpuAxisX->setRange(0, m_cpuHistoryData.size() - 1); } }

5.3 设计主界面布局

使用Qt Designer设计主窗口界面(mainwindow.ui)。一个典型的布局可以包含:

  • 顶部:工具栏或菜单(开始/停止监控,选择网络接口等)。
  • 左侧:仪表盘区域,使用QLabelQProgressBarQLCDNumber等控件实时显示CPU、内存、磁盘、网络的数值。
  • 右侧:图表区域,放置一个QWidget作为容器(例如QFrame),在代码中将其布局设置为QVBoxLayout,然后将QChartView添加进去。

6. 完整项目集成与运行

6.1 项目文件结构

SystemMonitor/ ├── SystemMonitor.pro # 项目文件 ├── main.cpp # 程序入口 ├── mainwindow.h # 主窗口头文件 ├── mainwindow.cpp # 主窗口实现 ├── mainwindow.ui # 主窗口UI设计文件 ├── systeminfo.h # 系统信息采集类头文件 ├── systeminfo_win.cpp # Windows平台实现 ├── systeminfo_linux.cpp # Linux平台实现 ├── systeminfo_mac.cpp # macOS平台实现(可选) ├── monitorworker.h # 工作线程类头文件 └── monitorworker.cpp # 工作线程类实现

6.2 编译与运行

  1. 在Qt Creator中打开SystemMonitor.pro
  2. 选择正确的构建套件(Kit)。
  3. 点击“构建”按钮(或Ctrl+B)编译项目。
  4. 点击“运行”按钮(或Ctrl+R)启动程序。

如果一切顺利,你将看到一个图形化窗口,实时显示系统的CPU、内存等信息,并且CPU使用率以动态曲线图的形式展示。

7. 常见问题与解决方案 (FAQ)

在开发过程中,你可能会遇到以下典型问题:

问题现象可能原因解决方案
编译错误:找不到QtCharts头文件项目.pro文件中未添加QT += charts检查并修改.pro文件,确保已添加该模块。
运行时报错:QChart未定义未包含正确的头文件或未使用命名空间。在源文件中添加#include <QtCharts>using namespace QtCharts;
程序启动后界面卡死或无响应数据采集在UI主线程中进行,阻塞了事件循环。确保已将数据采集逻辑移至工作线程(QThread)。
CPU使用率图表不更新或更新慢1. 信号槽连接失败。
2. 数据采集间隔太短,UI更新跟不上。
3. 图表数据点过多,绘图性能瓶颈。
1. 检查connect语句和线程管理。
2. 适当增加采集间隔(如1000ms)。
3. 限制历史数据点的数量(如只保留最近60个)。
Linux下读取/proc/stat失败文件权限问题(极罕见)或路径错误。确保程序有读取权限。使用QFileerrorString()函数查看具体错误。
Windows下PdhOpenQuery失败未链接pdh.lib库或PDH服务异常。在代码中添加#pragma comment(lib, "pdh.lib")。以管理员身份运行程序测试。
网络速度显示为0或不准1. 网络接口名选择错误(如不是eth0)。
2. 速度计算逻辑有误(未保存上一次的字节数)。
3. Qt的bytesReceived在某些平台/接口上可能不更新。
1. 遍历QNetworkInterface::allInterfaces()打印出所有接口名,选择正确的活跃接口。
2. 检查MonitorWorker::onTimeout()中的速度计算逻辑。
3. 考虑使用平台特定的API(如Linux的/proc/net/dev)获取更精确的网络流量。
跨平台编译错误平台特定的代码被错误地包含在了其他平台的编译中。使用Qt提供的宏进行条件编译:#ifdef Q_OS_WIN#ifdef Q_OS_LINUX#ifdef Q_OS_MAC

8. 项目优化与扩展建议

完成基础功能后,可以从以下方向进行优化和扩展,提升项目的深度和实用性:

  1. 性能优化

    • 减少UI更新频率:并非每次数据采集都需要立刻更新所有UI控件。可以对数值显示控件使用QTimer进行节流更新(如每500ms更新一次),而图表可以保持较高频率。
    • 图表优化QLineSeries在数据点很多时性能会下降。可以使用QLineSeries::replace()函数批量替换数据,而不是逐个append()。或者考虑使用QScatterSeries显示最近的点。
    • 智能采集:当程序窗口最小化或隐藏时,暂停或降低数据采集频率。
  2. 功能扩展

    • 进程监控:列出系统进程,并显示每个进程的CPU、内存占用。在Windows上使用CreateToolhelp32SnapshotAPI,在Linux上读取/proc/[pid]/stat
    • GPU监控:通过NVML (NVIDIA) 或 ADL (AMD) 库获取GPU温度、使用率、显存信息。
    • 温度监控:读取CPU/主板温度传感器数据(Linux下通常通过/sys/class/thermal,Windows需借助第三方库如OpenHardwareMonitorLib)。
    • 警报功能:当CPU使用率、内存使用率或温度超过设定阈值时,弹出系统通知或改变UI颜色。
    • 数据持久化与回看:将监控数据保存到SQLite数据库或文件中,并提供历史数据查询和图表回放功能。
    • 配置化:允许用户通过配置文件或设置界面调整监控间隔、选择监控的磁盘/网络接口、设置图表样式等。
  3. 代码结构优化

    • 使用工厂模式:将SystemInfo中平台相关的创建逻辑抽象为工厂模式,使平台判断更清晰。
    • 引入数据模型:使用QAbstractItemModel等为进程列表等数据提供模型,便于在QTableViewQListView中展示。
    • 更好的错误处理:在所有系统API调用处添加更细致的错误检查和日志记录。
  4. 部署与打包

    • Windows:使用windeployqt工具自动拷贝依赖的Qt DLL,然后使用Inno Setup或NSIS制作安装包。
    • Linux:提供AppImage打包,或编写.deb/.rpm包描述文件。
    • macOS:使用macdeployqt工具,并打包成.dmg.pkg

这个“系统资源监控工具”项目麻雀虽小,五脏俱全。它串联起了C++面向对象、Qt信号槽与多线程、跨平台系统编程、数据可视化等多个关键技能点。通过动手实现它,你不仅能完成一个高质量的课程设计或大作业,更能深入理解桌面应用开发的核心流程。建议你在实现基础功能后,选择一两个扩展方向深入尝试,这会让你的项目脱颖而出。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询