C++服务器监控系统代码实现
下面是一个精简但完整的 ServerStatus 风格监控系统 C++ 实现,包含服务端(接收数据、生成 JSON、看门狗告警)和客户端(采集 /proc 数据并上报)。
架构概览
┌─────────────┐ TCP/35601 ┌─────────────────┐
│ 客户端 │ ──────────────────► │ 服务端 │
│ (C++ 采集) │ JSON 数据上报 │ (sergate) │
└─────────────┘ │ - 接收客户端数据│
│ - 生成 stats.json│
│ - Watchdog 告警 │
└─────────────────┘
│
▼
┌─────────────────┐
│ Web 前端 │
│ (读取 stats.json)│
└─────────────────┘服务端代码 (server.cpp)
// server.cpp - ServerStatus 风格服务端
// 编译: g++ -std=c++11 -o sergate server.cpp -lpthread -lcurl
#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <sstream>
#include <thread>
#include <chrono>
#include <vector>
#include <map>
#include <mutex>
#include <algorithm>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <curl/curl.h>
#include <json/json.h> // 需要安装 libjsoncpp-dev
#define PORT 35601
#define MAX_CLIENTS 50
#define BUFFER_SIZE 4096
// ---------- 客户端状态 ----------
struct ClientStats {
std::string username;
std::string name;
std::string type;
std::string host;
bool online;
double cpu;
long memory_total;
long memory_used;
long hdd_total;
long hdd_used;
long network_in;
long network_out;
double load_1;
double load_5;
double load_15;
long uptime;
long last_update;
};
// ---------- 看门狗规则 ----------
struct WatchdogRule {
std::string name;
std::string expression;
int interval; // 最小告警间隔(秒)
std::string callback;
long last_alert; // 上次告警时间戳
};
std::vector<ClientStats> clients(MAX_CLIENTS);
std::mutex clients_mutex;
std::vector<WatchdogRule> watchdogs;
std::map<std::string, int> username_to_index;
// ---------- 读取配置文件 ----------
bool load_config(const std::string& config_file) {
std::ifstream ifs(config_file);
if (!ifs.is_open()) {
std::cerr << "无法打开配置文件: " << config_file << std::endl;
return false;
}
Json::Value root;
Json::Reader reader;
if (!reader.parse(ifs, root)) {
std::cerr << "JSON 解析失败" << std::endl;
return false;
}
// 解析服务器列表
const Json::Value& servers = root["servers"];
for (unsigned int i = 0; i < servers.size() && i < MAX_CLIENTS; i++) {
clients[i].username = servers[i]["username"].asString();
clients[i].name = servers[i].get("name", clients[i].username).asString();
clients[i].type = servers[i].get("type", "kvm").asString();
clients[i].host = servers[i].get("host", "").asString();
clients[i].online = false;
clients[i].last_update = 0;
username_to_index[clients[i].username] = i;
}
// 解析看门狗规则
const Json::Value& wd = root["watchdog"];
if (wd.isArray()) {
for (unsigned int i = 0; i < wd.size(); i++) {
WatchdogRule rule;
rule.name = wd[i].get("name", "rule" + std::to_string(i)).asString();
rule.expression = wd[i]["expression"].asString();
rule.interval = wd[i].get("interval", 60).asInt();
rule.callback = wd[i].get("callback", "").asString();
rule.last_alert = 0;
watchdogs.push_back(rule);
}
}
std::cout << "配置加载成功: " << servers.size() << " 台服务器, "
<< watchdogs.size() << " 条看门狗规则" << std::endl;
return true;
}
// ---------- 解析客户端上报数据 ----------
bool parse_client_data(const std::string& data, ClientStats& stats) {
Json::Value root;
Json::Reader reader;
if (!reader.parse(data, root)) return false;
stats.cpu = root.get("cpu", 0.0).asDouble();
stats.memory_total = root.get("memory_total", 0).asInt64();
stats.memory_used = root.get("memory_used", 0).asInt64();
stats.hdd_total = root.get("hdd_total", 0).asInt64();
stats.hdd_used = root.get("hdd_used", 0).asInt64();
stats.network_in = root.get("network_in", 0).asInt64();
stats.network_out = root.get("network_out", 0).asInt64();
stats.load_1 = root.get("load_1", 0.0).asDouble();
stats.load_5 = root.get("load_5", 0.0).asDouble();
stats.load_15 = root.get("load_15", 0.0).asDouble();
stats.uptime = root.get("uptime", 0).asInt64();
stats.online = true;
stats.last_update = time(nullptr);
return true;
}
// ---------- 评估看门狗表达式(简化版) ----------
bool evaluate_expression(const std::string& expr, const ClientStats& stats) {
// 简化实现:将 "cpu>90" 转为实际比较
// 完整实现建议使用 ExprTk 库
std::string e = expr;
// 替换变量为实际值
auto replace_var = [&](const std::string& var, double val) {
size_t pos;
while ((pos = e.find(var)) != std::string::npos) {
std::string val_str = std::to_string(val);
e.replace(pos, var.length(), val_str);
}
};
replace_var("cpu", stats.cpu);
replace_var("load_1", stats.load_1);
replace_var("load_5", stats.load_5);
replace_var("load_15", stats.load_15);
replace_var("memory_used", stats.memory_used);
replace_var("memory_total", stats.memory_total);
// 简单解析 "a>b" 或 "a<b" 格式
// 注意:这只是简化实现,复杂表达式请用 ExprTk
size_t pos;
if ((pos = e.find(">")) != std::string::npos) {
double left = std::stod(e.substr(0, pos));
double right = std::stod(e.substr(pos + 1));
return left > right;
}
if ((pos = e.find("<")) != std::string::npos) {
double left = std::stod(e.substr(0, pos));
double right = std::stod(e.substr(pos + 1));
return left < right;
}
return false;
}
// ---------- 发送告警(使用 libcurl) ----------
void send_alert(const std::string& url, const std::string& message) {
if (url.empty()) return;
CURL* curl = curl_easy_init();
if (!curl) return;
std::string full_url = url;
// 如果 URL 包含占位符,简单替换
size_t pos = full_url.find("{message}");
if (pos != std::string::npos) {
full_url.replace(pos, 9, message);
}
curl_easy_setopt(curl, CURLOPT_URL, full_url.c_str());
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
// ---------- 检查看门狗 ----------
void check_watchdogs() {
time_t now = time(nullptr);
std::lock_guard<std::mutex> lock(clients_mutex);
for (auto& client : clients) {
if (!client.online) continue;
for (auto& rule : watchdogs) {
// 检查告警间隔
if (now - rule.last_alert < rule.interval) continue;
if (evaluate_expression(rule.expression, client)) {
std::ostringstream msg;
msg << "[" << client.name << "] 触发告警: " << rule.name
<< " (CPU:" << client.cpu << "% 内存:"
<< (client.memory_used * 100 / client.memory_total) << "%)";
std::cout << msg.str() << std::endl;
send_alert(rule.callback, msg.str());
rule.last_alert = now;
}
}
}
}
// ---------- 写入 stats.json ----------
void write_stats_json() {
std::lock_guard<std::mutex> lock(clients_mutex);
Json::Value root(Json::arrayValue);
for (auto& client : clients) {
if (client.username.empty()) continue;
Json::Value item;
item["username"] = client.username;
item["name"] = client.name;
item["type"] = client.type;
item["host"] = client.host;
item["online"] = client.online;
item["cpu"] = client.cpu;
item["memory_total"] = (Json::Int64)client.memory_total;
item["memory_used"] = (Json::Int64)client.memory_used;
item["hdd_total"] = (Json::Int64)client.hdd_total;
item["hdd_used"] = (Json::Int64)client.hdd_used;
item["network_in"] = (Json::Int64)client.network_in;
item["network_out"] = (Json::Int64)client.network_out;
item["load_1"] = client.load_1;
item["load_5"] = client.load_5;
item["load_15"] = client.load_15;
item["uptime"] = (Json::Int64)client.uptime;
root.append(item);
}
std::ofstream ofs("stats.json");
Json::StyledWriter writer;
ofs << writer.write(root);
}
// ---------- 处理客户端连接 ----------
void handle_client(int client_fd) {
char buffer[BUFFER_SIZE];
std::string data;
// 接收认证信息
int n = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
if (n <= 0) { close(client_fd); return; }
buffer[n] = '\0';
data = buffer;
// 解析认证: "auth username password"
std::string username, password;
std::istringstream iss(data);
std::string cmd;
iss >> cmd >> username >> password;
if (cmd != "auth") {
send(client_fd, "error: invalid command\n", 23, 0);
close(client_fd);
return;
}
// 验证用户名(密码验证省略,实际应比对 config.json)
int idx = -1;
{
std::lock_guard<std::mutex> lock(clients_mutex);
auto it = username_to_index.find(username);
if (it != username_to_index.end()) {
idx = it->second;
}
}
if (idx < 0) {
send(client_fd, "error: invalid username\n", 25, 0);
close(client_fd);
return;
}
send(client_fd, "ok\n", 3, 0);
std::cout << "客户端 " << username << " 认证成功" << std::endl;
// 循环接收数据
while (true) {
n = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
if (n <= 0) break;
buffer[n] = '\0';
data = buffer;
// 处理 update 命令
if (data.find("update") == 0) {
std::string json_data = data.substr(7); // 跳过 "update "
ClientStats stats;
if (parse_client_data(json_data, stats)) {
std::lock_guard<std::mutex> lock(clients_mutex);
clients[idx].online = true;
clients[idx].cpu = stats.cpu;
clients[idx].memory_total = stats.memory_total;
clients[idx].memory_used = stats.memory_used;
clients[idx].hdd_total = stats.hdd_total;
clients[idx].hdd_used = stats.hdd_used;
clients[idx].network_in = stats.network_in;
clients[idx].network_out = stats.network_out;
clients[idx].load_1 = stats.load_1;
clients[idx].load_5 = stats.load_5;
clients[idx].load_15 = stats.load_15;
clients[idx].uptime = stats.uptime;
clients[idx].last_update = time(nullptr);
send(client_fd, "ok\n", 3, 0);
} else {
send(client_fd, "error: parse failed\n", 21, 0);
}
}
}
// 客户端断开
{
std::lock_guard<std::mutex> lock(clients_mutex);
clients[idx].online = false;
}
std::cout << "客户端 " << username << " 断开连接" << std::endl;
close(client_fd);
}
// ---------- 后台线程:定期写 JSON 和检查看门狗 ----------
void background_worker() {
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(5));
write_stats_json();
check_watchdogs();
}
}
// ---------- 主函数 ----------
int main(int argc, char* argv[]) {
signal(SIGPIPE, SIG_IGN);
curl_global_init(CURL_GLOBAL_DEFAULT);
std::string config_file = "config.json";
if (argc > 1) config_file = argv[1];
if (!load_config(config_file)) {
return 1;
}
// 创建 socket
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
return 1;
}
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
return 1;
}
if (listen(server_fd, 10) < 0) {
perror("listen");
return 1;
}
std::cout << "ServerStatus 服务端启动,监听端口 " << PORT << std::endl;
// 启动后台线程
std::thread worker(background_worker);
worker.detach();
// 主循环接受连接
while (true) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept");
continue;
}
std::thread client_thread(handle_client, client_fd);
client_thread.detach();
}
close(server_fd);
curl_global_cleanup();
return 0;
}客户端代码 (client.cpp)
// client.cpp - ServerStatus 风格客户端
// 编译: g++ -std=c++11 -o client client.cpp -lpthread
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <cstring>
#include <thread>
#include <chrono>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <json/json.h>
#define SERVER_PORT 35601
#define INTERVAL 3 // 上报间隔(秒)
// ---------- 读取 /proc/stat 获取 CPU 使用率 ----------
double get_cpu_usage() {
static long prev_user = 0, prev_nice = 0, prev_system = 0;
static long prev_idle = 0, prev_iowait = 0, prev_irq = 0, prev_softirq = 0;
static bool first = true;
std::ifstream stat("/proc/stat");
std::string line;
if (!std::getline(stat, line)) return 0.0;
std::istringstream iss(line);
std::string cpu;
long user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice;
iss >> cpu >> user >> nice >> system >> idle >> iowait >> irq >> softirq
>> steal >> guest >> guest_nice;
long total = user + nice + system + idle + iowait + irq + softirq + steal;
long idle_all = idle + iowait;
if (first) {
prev_user = user; prev_nice = nice; prev_system = system;
prev_idle = idle; prev_iowait = iowait; prev_irq = irq; prev_softirq = softirq;
first = false;
return 0.0;
}
long total_diff = total - (prev_user + prev_nice + prev_system + prev_idle +
prev_iowait + prev_irq + prev_softirq);
long idle_diff = idle_all - (prev_idle + prev_iowait);
prev_user = user; prev_nice = nice; prev_system = system;
prev_idle = idle; prev_iowait = iowait; prev_irq = irq; prev_softirq = softirq;
if (total_diff == 0) return 0.0;
return 100.0 * (1.0 - (double)idle_diff / total_diff);
}
// ---------- 读取 /proc/meminfo 获取内存信息 ----------
void get_memory(long& total, long& used) {
std::ifstream meminfo("/proc/meminfo");
std::string line;
long mem_total = 0, mem_available = 0;
while (std::getline(meminfo, line)) {
if (line.find("MemTotal:") == 0) {
std::istringstream iss(line);
std::string key;
iss >> key >> mem_total;
} else if (line.find("MemAvailable:") == 0) {
std::istringstream iss(line);
std::string key;
iss >> key >> mem_available;
}
}
total = mem_total * 1024; // kB -> bytes
used = (mem_total - mem_available) * 1024;
}
// ---------- 读取磁盘使用情况 ----------
void get_disk(long& total, long& used) {
// 使用 statvfs 更准确,这里简化为读取 / 分区
std::ifstream mounts("/proc/mounts");
std::string line;
while (std::getline(mounts, line)) {
if (line.find(" / ") != std::string::npos) {
// 简单实现:使用 df 命令或 statvfs
// 这里简化返回示例值
total = 100 * 1024 * 1024 * 1024LL; // 100GB
used = 50 * 1024 * 1024 * 1024LL; // 50GB
return;
}
}
}
// ---------- 读取网络流量 ----------
void get_network(long& in_bytes, long& out_bytes) {
std::ifstream net("/proc/net/dev");
std::string line;
in_bytes = 0;
out_bytes = 0;
// 跳过前两行标题
std::getline(net, line);
std::getline(net, line);
while (std::getline(net, line)) {
std::istringstream iss(line);
std::string iface;
long rx_bytes, rx_packets, rx_errs, rx_drop, rx_fifo, rx_frame, rx_compressed, rx_multicast;
long tx_bytes, tx_packets, tx_errs, tx_drop, tx_fifo, tx_colls, tx_carrier, tx_compressed;
iss >> iface;
if (iface == "lo:") continue; // 跳过 loopback
iss >> rx_bytes >> rx_packets >> rx_errs >> rx_drop >> rx_fifo >> rx_frame >> rx_compressed >> rx_multicast;
iss >> tx_bytes >> tx_packets >> tx_errs >> tx_drop >> tx_fifo >> tx_colls >> tx_carrier >> tx_compressed;
in_bytes += rx_bytes;
out_bytes += tx_bytes;
}
}
// ---------- 读取负载 ----------
void get_load(double& load1, double& load5, double& load15) {
std::ifstream loadavg("/proc/loadavg");
std::string line;
if (std::getline(loadavg, line)) {
std::istringstream iss(line);
iss >> load1 >> load5 >> load15;
}
}
// ---------- 读取运行时间 ----------
long get_uptime() {
std::ifstream uptime_file("/proc/uptime");
double uptime_sec;
if (uptime_file >> uptime_sec) {
return (long)uptime_sec;
}
return 0;
}
// ---------- 构建 JSON 数据 ----------
std::string build_json() {
Json::Value root;
root["cpu"] = get_cpu_usage();
long mem_total, mem_used;
get_memory(mem_total, mem_used);
root["memory_total"] = (Json::Int64)mem_total;
root["memory_used"] = (Json::Int64)mem_used;
long hdd_total, hdd_used;
get_disk(hdd_total, hdd_used);
root["hdd_total"] = (Json::Int64)hdd_total;
root["hdd_used"] = (Json::Int64)hdd_used;
long net_in, net_out;
get_network(net_in, net_out);
root["network_in"] = (Json::Int64)net_in;
root["network_out"] = (Json::Int64)net_out;
double load1, load5, load15;
get_load(load1, load5, load15);
root["load_1"] = load1;
root["load_5"] = load5;
root["load_15"] = load15;
root["uptime"] = (Json::Int64)get_uptime();
Json::FastWriter writer;
return writer.write(root);
}
// ---------- 连接服务端并上报数据 ----------
bool connect_and_report(const std::string& server_ip, const std::string& username,
const std::string& password) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return false;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, server_ip.c_str(), &addr.sin_addr) <= 0) {
perror("inet_pton");
close(sock);
return false;
}
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("connect");
close(sock);
return false;
}
// 发送认证
std::string auth_msg = "auth " + username + " " + password + "\n";
send(sock, auth_msg.c_str(), auth_msg.length(), 0);
// 接收响应
char buffer[64];
int n = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (n <= 0 || std::string(buffer, n).find("ok") == std::string::npos) {
std::cerr << "认证失败" << std::endl;
close(sock);
return false;
}
std::cout << "认证成功,开始上报数据..." << std::endl;
// 循环上报
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(INTERVAL));
std::string json_data = build_json();
std::string msg = "update " + json_data;
if (send(sock, msg.c_str(), msg.length(), 0) < 0) {
std::cerr << "发送失败,重连..." << std::endl;
close(sock);
return true; // 让外层重连
}
// 接收响应
n = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (n <= 0) {
std::cerr << "服务端断开,重连..." << std::endl;
close(sock);
return true;
}
}
close(sock);
return true;
}
// ---------- 主函数 ----------
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "用法: " << argv[0] << " <服务端IP> <用户名> [密码]" << std::endl;
return 1;
}
std::string server_ip = argv[1];
std::string username = argv[2];
std::string password = (argc > 3) ? argv[3] : "";
while (true) {
if (!connect_and_report(server_ip, username, password)) {
std::cerr << "连接失败,5秒后重试..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
}
return 0;
}配置文件示例 (config.json)
{
"servers": [
{
"username": "s01",
"name": "Web服务器",
"type": "kvm",
"host": "上海"
},
{
"username": "s02",
"name": "数据库服务器",
"type": "kvm",
"host": "北京"
}
],
"watchdog": [
{
"name": "CPU过高",
"expression": "cpu>90",
"interval": 60,
"callback": "https://your-webhook.com/alert"
},
{
"name": "内存不足",
"expression": "memory_used/memory_total>0.9",
"interval": 120,
"callback": "https://your-webhook.com/alert"
}
]
}编译与运行
# 安装依赖
sudo apt-get install libjsoncpp-dev libcurl4-openssl-dev # Debian/Ubuntu
# 或
sudo yum install jsoncpp-devel libcurl-devel # CentOS/RHEL
# 编译服务端
g++ -std=c++11 -o sergate server.cpp -lpthread -lcurl -ljsoncpp
# 编译客户端
g++ -std=c++11 -o client client.cpp -lpthread -ljsoncpp
# 运行服务端
./sergate config.json
# 运行客户端(另开终端)
./client 127.0.0.1 s01核心特性说明
| 特性 | 实现方式 |
|---|---|
| CPU 监控 | 读取 /proc/stat,计算 jiffies 差值获得使用率- |
| 内存监控 | 读取 /proc/meminfo 的 MemTotal 和 MemAvailable- |
| 磁盘监控 | 可通过 statvfs 或 /proc/mounts 获取- |
| 网络流量 | 读取 /proc/net/dev 累计 RX/TX 字节数 |
| 系统负载 | 读取 /proc/loadavg 获取 1/5/15 分钟负载- |
| 看门狗告警 | 表达式引擎评估规则,通过 libcurl 发送 HTTP 回调 |
| 数据持久化 | 后台线程每 5 秒写入 stats.json,供 Web 前端读取 |
扩展建议
- 完整表达式引擎:替换
evaluate_expression为 [ExprTk] 库,支持复杂表达式如cpu>90&load_1>4&memory_total<33554432 - 多客户端支持:当前支持最多 50 个客户端,可通过配置
MAX_CLIENTS调整 - Web 前端:用任何静态 Web 服务器(如 Nginx)提供
stats.json,配合 HTML/JS 渲染仪表盘 - Docker 部署:参考官方 Dockerfile 封装服务端和客户端
- 密码验证:在
config.json中为每个服务器增加password字段,服务端认证时比对
最新评论 (0/10)