Ubuntu C++ 实时系统监控指南(网速 & CPU)
Ubuntu C++ 实时系统监控指南(网速 & CPU)
面向纯新手,基于 Linux 内核虚拟文件系统(sysfs / procfs),用 C++ 实现“每秒刷新”的实时监控工具。
1. 基础知识:数据从哪来?
Linux 系统把内核的运行状态以虚拟文件的形式暴露给用户。我们只需要像读普通文本文件一样读取这些文件,就能拿到硬件和设备的信息。
| 监控项目 | 数据源路径 | 作用 |
|---|---|---|
| 实时网速 | /sys/class/net/[网卡名]/statistics/ | 记录网卡自开机以来收发数据的总字节数(rx_bytes / tx_bytes)。 |
| CPU 使用率 | /proc/stat | 第一行(cpu 开头)记录 CPU 自开机以来在各种状态下的累计运行时间(时钟滴答数)。 |
新手提示:
/sys/class按设备功能分类(如net/放网卡),/proc/stat是内核统计信息的汇总。两者都是实时动态生成的,每次读取都会返回最新数值。
2. 项目一:实时网速监控(单网卡)
2.1 原理说明
- 读取 接收字节数(
rx_bytes)和 发送字节数(tx_bytes)。 - 等待 1 秒,再次读取。
- 计算两次读数的差值,即为这一秒内的实时网速(B/s)。
- 自动将单位转换为 KB/s 或 MB/s 以方便阅读。
2.2 完整代码(network_speed.cpp)
#include <iostream>
#include <fstream>
#include <string>
#include <unistd.h>
#include <iomanip>
using namespace std;
// 读取指定网卡、指定类型的累计字节数(rx_bytes / tx_bytes)
long long getBytes(const string& interface, const string& type) {
string path = "/sys/class/net/" + interface + "/statistics/" + type;
ifstream file(path);
long long bytes = 0;
if (file.is_open()) {
file >> bytes;
file.close();
}
return bytes;
}
// 将 B/s 格式化为带单位的字符串
string formatSpeed(long long bytesPerSec) {
if (bytesPerSec < 1024) {
return to_string(bytesPerSec) + " B/s";
} else if (bytesPerSec < 1024 * 1024) {
return to_string(bytesPerSec / 1024) + " KB/s";
} else {
return to_string(bytesPerSec / (1024 * 1024)) + " MB/s";
}
}
int main() {
// 【重要】请将 "eth0" 改成你实际的网卡名(用 ifconfig 或 ip a 查看)
string interface = "eth0";
cout << "正在监控网卡: " << interface << endl;
cout << "按 Ctrl+C 退出监控\n" << endl;
// 首次读取,作为基准值
long long prev_rx = getBytes(interface, "rx_bytes");
long long prev_tx = getBytes(interface, "tx_bytes");
while (true) {
sleep(1); // 每秒刷新一次
long long curr_rx = getBytes(interface, "rx_bytes");
long long curr_tx = getBytes(interface, "tx_bytes");
long long rx_speed = curr_rx - prev_rx; // 下载速度
long long tx_speed = curr_tx - prev_tx; // 上传速度
prev_rx = curr_rx;
prev_tx = curr_tx;
// 使用 \r 让光标回到行首,实现原地刷新效果
cout << "\r下载: " << setw(10) << formatSpeed(rx_speed)
<< " 上传: " << setw(10) << formatSpeed(tx_speed) << flush;
}
return 0;
}3. 项目二:CPU 使用率监控(多核心版)
3.1 原理说明
读取
/proc/stat中所有以cpu开头的行(第一行是总数据,后面的cpu0、cpu1是各核心数据)。
每行包含 10 个数字,我们只需要前 8 个:
[0] user:用户态程序[1] nice:低优先级用户态[2] system:内核态[3] idle:空闲(重要)[4] iowait:等待 I/O(也算空闲)[5] irq:硬中断[6] softirq:软中断[7] steal:被虚拟机偷走
使用率公式:
使用率=(总时间增量−空闲时间增量)总时间增量×100%使用率=总时间增量(总时间增量−空闲时间增量)×100%
其中“空闲时间增量” =
idle增量 +iowait增量。
3.2 完整代码(cpu_monitor.cpp)
#include <iostream>
#include <fstream>
#include <string>
#include <unistd.h>
#include <iomanip>
#include <sstream>
#include <map>
#include <vector>
using namespace std;
// 读取 /proc/stat 中所有 cpu 开头的行,返回 map(键=核心名,值=时间数组)
map<string, vector<long long>> readCPUTimes() {
map<string, vector<long long>> result;
ifstream file("/proc/stat");
string line;
while (getline(file, line)) {
// 只处理以 "cpu" 开头的行(必须放在开头)
if (line.rfind("cpu", 0) != 0) continue;
stringstream ss(line);
string name;
ss >> name; // 取出开头的 "cpu" / "cpu0" / "cpu1"
// 去掉可能出现的干扰项(虽然一般不会)
if (name != "cpu" && (name.size() < 4 || name[3] < '0' || name[3] > '9')) {
continue;
}
vector<long long> times;
long long val;
while (ss >> val) {
times.push_back(val);
}
// 只要前 8 个(多余的 guest 等忽略)
if (times.size() >= 8) {
times.resize(8);
result[name] = times;
}
}
return result;
}
int main() {
cout << "实时监控 CPU(总体 + 每个核心),按 Ctrl+C 退出\n" << endl;
// 首次读取作为基准
map<string, vector<long long>> prev_data = readCPUTimes();
while (true) {
sleep(1);
map<string, vector<long long>> curr_data = readCPUTimes();
// 清空当前行准备刷新
cout << "\r" << string(120, ' ') << "\r";
bool is_first = true;
// 遍历所有核心(C++98/11 兼容写法)
for (map<string, vector<long long>>::iterator it = curr_data.begin();
it != curr_data.end(); ++it) {
const string& name = it->first;
const vector<long long>& curr_times = it->second;
if (prev_data.find(name) == prev_data.end()) continue;
const vector<long long>& prev_times = prev_data[name];
// 计算总时间增量
long long prev_total = 0, curr_total = 0;
long long prev_idle = 0, curr_idle = 0;
for (int i = 0; i < 8; i++) {
prev_total += prev_times[i];
curr_total += curr_times[i];
}
// 空闲 = idle (下标3) + iowait (下标4)
prev_idle = prev_times[3] + prev_times[4];
curr_idle = curr_times[3] + curr_times[4];
long long total_diff = curr_total - prev_total;
long long idle_diff = curr_idle - prev_idle;
double usage = 0.0;
if (total_diff > 0) {
usage = (double)(total_diff - idle_diff) / total_diff * 100.0;
}
// 显示名称美化
string display_name;
if (name == "cpu") {
display_name = "总";
} else {
display_name = "核" + name.substr(3); // "cpu0" -> "核0"
}
// 用 "|" 分隔不同核心
if (!is_first) {
cout << " | ";
}
is_first = false;
cout << display_name << ": " << fixed << setprecision(1) << usage << "%";
// 更新基准值供下一次循环使用
prev_data[name] = curr_times;
}
cout << flush;
}
return 0;
}4. 核心知识点详解
4.1 什么是 stringstream?(摘“路牌”工具)
在 CPU 代码中,我们有一行 stringstream ss(line); 和 ss >> name;。
- 作用:把字符串
"cpu0 50948 0 ..."变成一个“输入流”,然后可以用>>像读cin一样逐个单词提取。 - 为什么需要:因为每行的开头是
cpu0(字符串),后面都是数字。如果不先摘掉开头的字符串,后面的while(ss >> val)试图将cpu0读成long long就会报错。
4.2 /proc/stat 第一行(cpu 行)数值对照表
| 下标 | 字段名 | 含义 | 是否算“空闲” |
|---|---|---|---|
| 0 | user | 用户态普通程序运行时间 | ❌ |
| 1 | nice | 低优先级用户态程序时间 | ❌ |
| 2 | system | 内核(系统调用、驱动)运行时间 | ❌ |
| 3 | idle | 完全闲置(发呆) | ✅ |
| 4 | iowait | 等待硬盘/磁盘 I/O(也是闲着) | ✅ |
| 5 | irq | 硬中断处理时间 | ❌ |
| 6 | softirq | 软中断处理时间 | ❌ |
| 7 | steal | 虚拟机偷走的时间 | ❌ |
4.3 为什么网速代码要读 statistics 目录?
因为 /sys/class/net/[网卡]/statistics/ 直接由网卡驱动维护,数据精准且开销极低。无需调用复杂的 ioctl 或 socket API,这是 Linux 系统监控工具(如 nethogs、iftop)最底层的取数方式。
5. 编译与运行指南
5.1 准备工作
将上面的两段代码分别保存为
network_speed.cpp和cpu_monitor.cpp。
查看自己的网卡名(用于网络监控):
ip a
# 或
ifconfig
找出类似
eth0、enp0s3、wlp2s0的设备名,修改network_speed.cpp第 23 行的interface变量。
5.2 编译
建议使用 C++11 标准(兼容性最好):
# 编译网络监控
g++ -std=c++11 -o network_speed network_speed.cpp
# 编译 CPU 监控
g++ -std=c++11 -o cpu_monitor cpu_monitor.cpp5.3 运行
# 运行网速监控
./network_speed
# 运行 CPU 监控(另开一个终端)
./cpu_monitor退出方式:按 Ctrl + C 终止进程。
6. 运行效果示例
网速监控(会原地刷新):
下载: 1.2 MB/s 上传: 345 KB/s
CPU 监控(双核输出示例):
总: 5.3% | 核0: 2.1% | 核1: 8.5%
7. 常见问题(FAQ)
Q1:编译时提示 to_string 未定义?
A:在编译命令中加上 -std=c++11 即可。
Q2:网络监控显示 0 B/s,但明明在下载东西?
A:检查网卡名是否正确。可以用 cat /sys/class/net/你的网卡/statistics/rx_bytes 手动验证是否有数据增长。
Q3:CPU 监控显示所有核心使用率都是 0.0%?
A:这很正常,说明你的电脑非常空闲。打开浏览器或编译大型程序,数值就会跳动起来。
Q4:rfind("cpu", 0) 是什么意思?
A:rfind 是从字符串末尾往前找,但第二个参数传 0 时表示“从位置 0 开始查找”,结合判断 != 0,就是检查字符串是否以 "cpu" 开头(标准库的 rfind 在这里作用同 find,只是写法上为了兼顾老版本 C++)。
8. 进阶思考
- 自动检测网卡名:遍历
/sys/class/net/目录,跳过名为lo(本地回环)的虚拟网卡,自动绑定第一个物理网卡。 - 显示进程级别的流量:可以结合
netlink或libpcap抓包实现。 - 图形化显示:将输出的数据通过管道传给
gnuplot或Python画图。
最新评论 (0/10)