声明:
本文仅以学习交流为目的分享自己的开发成果,希望为更多人提供开发设计的思路,还请善待笔者的开发成果。有任何问题欢迎在文章下方留言或私信,也欢迎评论或私信指教,和大家共同进步!
1. Server Monitor Platform 简介
-
开发语言:
C、C++
-
开发平台:
Linux、Windows
-
开发工具:
Vim、Qt Creator
- 应用平台: 跨平台使用
本文将介绍笔者开发的一套服务器状态监控通知软件 (Server Monitor Platform
),该软件功能包含监测服务器 CPU
温度和内存使用率(目前只监测这两个),该软件在服务器端分布式部署服务程序,以主动监测的方式获取服务器状态信息。用户将在每日 06
点、12
点、19
点、23
点整收到以邮件方式通知的被监测服务器实时状态。当服务器存在风险时(服务器温度超过 65℃
),用户将会收到及时通知邮件,用户即时将视情况处理。若用户超时未能处理,本软件将会在风险等级达到最高时强制服务器关机,同时用户将收到风险解除通知,从而保障服务器安全。
2. 软件的整体分布架构介绍
软件分为两个部分服务端和监控平台(也可以理解为客户端),服务端程序主体采用 C
语言开发,部署在被监测服务器上,监控平台主体采用 C++
开发,运行在带有图形界面的操作系统上 (Windows
、MacOS
、Linux-Desktop
)。服务端与监控平台采用 TCP/IP
协议连接。
在建立好握手后,软件以主动的方式监测服务器状态。监控平台主动向服务端发送握手信息,服务端在接收到握手信号后,初次将会发送当前机器的 CPU
核心数以及内存的总大小。之后服务端将以 2s
为时间单位发送自己当前机器的状态信息给监控平台。(状态信息包含:当前使用内存大小,当前每个 CPU
核心温度值)
监控平台以分布式采集的方式获取服务器状态信息,运行在带有图形界面操作系统的监控平台将每隔 2s
接收到来自服务端的状态信息,经过处理后将实时显示在显示界面上。并且在指定时间以邮件的方式向用户们发送服务器状态信息,当服务器达到预设的风险等级时,将立刻向用户们发送及时同时邮件,并告知风险等级系数,当风险等级系数达到最高时,监测平台将在超过操作时间后强制时服务器关机。
3. 服务端程序
服务端代码比较少就直接贴在本文中了,服务端程序可以简单的看作一个 TCP
的服务器,使用的是固定端口 21031
,当然你也可以自由修改。服务端的主要功能是采集当前 CPU
温度和当前使用的内存大小。
采集温度功能可使用现有工具 lm-sensors
直接获取,然后对其输出的结果进行处理即可;当前的内存使用大小可直接通过读取 /proc/meminfo
文件获得。握手等信号均采用自协商字段。整体来说服务端的实现较为简单。
[注]:lm-sensors 是运行在 Linux 的一款温度采集软件,直接安装即可 yum install lm-sensors,接着执行 sensors-detect 检测可使用的传感器。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#define TCP_IP4 ("10.1.0.149")
#define TCP_PORT (21031)
#define DATA_LENGTH (1024)
#define TEMP_LENGTH (15)
#define GETTEMP_CMD ("sensors")
#define GETCORENUM_CMD ("grep 'core id' /proc/cpuinfo | sort -u | wc -l")
#define GETMEM_TOTAL_CMD ("cat /proc/meminfo | grep MemTotal")
#define GETMEM_AVAILABLE_CMD ("cat /proc/meminfo | grep MemAvailable")
#define CORE_STR ("Core")
#define INITDATA_CMD ("initdata")
#define ACKDATA_CMD ("ackdata")
#define POWERDOWN_CMD ("poweroff")
long unsigned int get_memsize(char * cmd)
{
long unsigned int mem = 0;
FILE * fp = NULL;
char cmdout[DATA_LENGTH] = {0};
printf("Get Mem Size.\n");
fp = popen(cmd, "r");
if (fp == NULL) {
perror("popen error.\n");
return -1;
}
while (fgets(cmdout, sizeof(cmdout), fp) != NULL) {
printf("%s", cmdout);
}
pclose(fp);
int m_s = 0, m_e = 0;
for (int i = 8; i < strlen(cmdout); i++) {
if (cmdout[i] == ' ' && (cmdout[i + 1] >= '0' && cmdout[i + 1] <= '9')) {
m_s = i + 1;
continue;
}
if (cmdout[i] == ' ' && (cmdout[i - 1] >= '0' && cmdout[i - 1] <= '9') && m_s > 0) {
m_e = i;
break;
}
}
int i = 0;
printf("%d:%d\n", m_s, m_e);
for ( ; m_s < m_e; m_s++) {
cmdout[i++] = cmdout[m_s];
}
cmdout[i] = '\0';
printf("> %s\n", cmdout);
for (i = 0; i < strlen(cmdout); i++) {
mem *= 10;
mem += (cmdout[i] - '0');
}
return mem;
}
int get_corenumber(void)
{
int core_num = 0;
FILE * fp = NULL;
char cmdout[DATA_LENGTH] = {0};
printf("Get CPU Core number.\n");
fp = popen(GETCORENUM_CMD, "r");
if (fp == NULL) {
perror("popen error.\n");
return -1;
}
while (fgets(cmdout, sizeof(cmdout), fp) != NULL) {
printf("%s", cmdout);
}
pclose(fp);
core_num = atoi(cmdout);
printf("num:%d\n", core_num);
return core_num;
}
char ** get_coretemp_fromcmdout(char * cmdout, char ** core_list, int core_num)
{
int i = 0, t_s = 0, t_e = 0, list_num = 0;
if (0 == strncmp(CORE_STR, cmdout, strlen(CORE_STR))) {
for (i = 0; i < strlen(cmdout); i++) {
if (cmdout[i] == ':') {
i += 1;
for ( ; i < strlen(cmdout); i++) {
if (0 == t_s && cmdout[i] != ' ') {
t_s = i + 1;
}
if (t_s && cmdout[i] == ' ') {
t_e = i - 3;
break;
}
}
break;
}
continue;
}
// find the first null char*
for (i = 0; i < core_num; i++) {
if (0 == strlen(core_list[i])) {
list_num = i;
break;
}
}
for (i = 0; t_s < t_e; i++) {
core_list[list_num][i] = cmdout[t_s++];
}
core_list[list_num][i] = '\0';
}
return core_list;
}
char ** get_coretemp(char ** core_list, int num)
{
char cmdout[DATA_LENGTH] = {0};
FILE * fp = NULL;
int core_num = num;
printf("Core number: %d\n", core_num);
printf("Get Temperature.\n");
fp = popen(GETTEMP_CMD, "r");
if (fp == NULL) {
perror("popen error.\n");
return NULL;
}
printf("core_list size: %d\n", sizeof(core_list[0]));
int i = 0;
while (fgets(cmdout, sizeof(cmdout), fp) != NULL) {
printf("[%d]>%s", i++, cmdout);
get_coretemp_fromcmdout(cmdout, core_list, core_num);
}
pclose(fp);
printf("-----------------------------------\n");
return core_list;
}
void clear_corelist_data(char ** core_list, int core_num)
{
for (int i = 0; i < core_num; i++) {
core_list[i][0] = '\0';
}
}
void free_corelist(char ** core_list, int core_num)
{
for (int i = 0; i < core_num; i++) {
if (core_list[i] != NULL) {
free(core_list[i]);
core_list[i] = NULL;
}
}
if (core_list != NULL) {
free(core_list);
}
return ;
}
int main(int argc, const char * argv[])
{
int ret = 0;
int connect_fd = 0;
long unsigned int memtotal = 0, memavailable = 0, memused = 0;
double memused_rate = 0;
int core_num = get_corenumber();
char **core_list = (char **)malloc(core_num * sizeof(char *));
memset(core_list, 0, core_num * sizeof(char *));
for (int i = 0; i < core_num; i++) {
core_list[i] = (char *)malloc(sizeof(char) * TEMP_LENGTH);
}
core_list = get_coretemp(core_list, core_num);
for (int i = 0; i < core_num; i++) {
printf("[%d]:%s\n", i, core_list[i]);
}
int socket_fd = socket(PF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
perror("create socket error!");
return -1;
}
printf ("Create socket successful!\n");
// Bind
struct sockaddr_in server_addr = {0};
server_addr.sin_family = PF_INET; // set protocol: Internet
server_addr.sin_port = htons(TCP_PORT);
server_addr.sin_addr.s_addr = inet_addr(TCP_IP4);
ret = bind(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret < 0) {
perror("bind error!\n");
close(socket_fd);
return -1;
}
printf("Bind successful!\n");
ret = listen(socket_fd, 1);
if (ret < 0) {
perror("listen error!\n");
close(socket_fd);
return -1;
}
accept:
while (1) {
printf("Linstening: \n");
// Accept
connect_fd = accept(socket_fd, NULL, NULL);
if (connect_fd < 0) {
perror("accept error!\n");
close(socket_fd);
return -1;
}
printf("Accept successful!\n");
// Recv
char buf[DATA_LENGTH] = {0};
while (1) {
memtotal = get_memsize(GETMEM_TOTAL_CMD);
memavailable = get_memsize(GETMEM_AVAILABLE_CMD);
memused = memtotal - memavailable;
memused_rate = memused * 1000 / memtotal;
memused_rate /= 10;
ret = recv(connect_fd, buf, sizeof(buf), 0);
if (ret <= 0) {
perror("recv error!\n");
goto accept;
continue;
}
buf[ret] = '\0';
if (ret) {
printf("[%d]Recv: %s\n", ret, buf);
}
if (strcmp(buf, INITDATA_CMD) == 0) {
memset(buf, 0, sizeof(buf));
sprintf(buf, "%d\n%d\n", core_num, memtotal / 1024 / 1000);
ret = send(connect_fd, buf, strlen(buf), 0);
if (ret == -1) {
goto accept;
}
//sleep(1);
int t = 0;
// set core temp data
memset(buf, 0, sizeof(buf));
for (int i = 0; i < core_num; i++) {
for (int z = 0; z < strlen(core_list[i]); z++) {
buf[t++] = core_list[i][z];
}
buf[t++] = '\n';
//sprintf(buf, "%s\n", core_list[i]);
}
sprintf(buf, "%s%.2lf\n", buf, memused_rate);
send(connect_fd, buf, strlen(buf), MSG_NOSIGNAL);
if (ret == -1 && errno == EPIPE) {
goto accept;
}
continue;
}
if (strcmp(buf, "quit") == 0) {
break;
}
if (strcmp(buf, ACKDATA_CMD) == 0) {
printf("get new temp\n");
clear_corelist_data(core_list, core_num);
get_coretemp(core_list, core_num);
for (int i = 0; i < core_num; i++) {
printf("%s\n", core_list[i]);
}
int t = 0;
// 将每个核心温度值添加入 buf 中
memset(buf, 0, sizeof(buf));
for (int i = 0; i < core_num; i++) {
for (int z = 0; z < strlen(core_list[i]); z++) {
buf[t++] = core_list[i][z];
}
buf[t++] = '\n';
//sprintf(buf, "%s\n", core_list[i]);
}
// 在 buf 后添加内存使用率,单位 %
sprintf(buf, "%s%.2lf\n", buf, memused_rate);
// 通过 socket 发送
send(connect_fd, buf, strlen(buf), MSG_NOSIGNAL);
if (ret == -1 && errno == EPIPE) {
goto accept;
}
}
if (strcmp(buf, POWERDOWN_CMD) == 0) {
printf("为确保系统安全,将在 5 秒后自动关机。\n");
sleep(5);
system("poweroff");
}
memset(buf, 0, sizeof(buf));
sleep(2);
continue;
}
}
close(connect_fd);
close(socket_fd);
free_corelist(core_list, core_num);
return 0;
}
由于服务端需要运行在后台,因此另外创建一个进程将其以守护进程的方式启动。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
int main(int argc, char * argv[])
{
pid_t pid = fork();
if (pid < 0) {
perror("fork error.\n");
return -1;
} else if (pid > 0) {
exit(0);
} else {
printf("Start Monitor_IM Service...\n");
system("monitor_server");
printf("Stop Monitor_IM Service...\n");
}
return 0;
}
4. 监控平台软件
监控平台软件代码虽然不是特别多,但还是有一些代码量的,至少不适合直接贴在文章中,因此笔者将其上传到各大 git
仓库,读者们可以通过以下几个可以访问的 git
仓库直接下载就好。
GitHub: https://github.com/ImagineMiracle-wxn/Server-Monitor-Platform
Gitee: https://gitee.com/imaginemiracle_wxn_1/server-monitor-platform
GitCode: https://gitcode.net/qq_36393978/server-monitor-platform/-/tree/master[注]:三个链接均无法打开,也可通过私信笔者获取。
4.1. 监控平台软件整体设计
由于在代码中笔者添加了大量注释来帮助读者们阅读代码,在本文中笔者就简要的说明监控平台端的软件设计结构。笔者提供的源码目录如下图。
其中每个目录的内容如下:
- Monitor_Server-v0.3: 服务端监控源码;
- Server_Monitoring_Platform: 监控平台源码;
-
build😗 使用
Qt Creator
编译后生成的构建目录; - doc: 简单的描述文档;
- object: 打包发布后的应用程序;
- pic: 一些图片;
下载好源码后,可以直接使用 Qt Creator
打开 Server_Monitoring_Platform/Server_Monitoring_Platform.pro
文件,笔者使用的是 Qt 6.4.3 MSVC2019_64bit
构建的工程,若你当前的 Qt
并不支持的话,删掉 Server_Monitoring_Platform/Server_Monitoring_Platform.pro.user
文件后,再重新打开即可。使用 Qt Createor
打开该工程后即可看到如下这样的源码列表。
File List:
typelist.h // 通用类型结构体和枚举定义在此
email_im.h // 电子邮件功能类
about_im.h // 关于界面类
emailconfigure.h // 邮件配置界面类
monitorthread_im.h // 监测线程头类
monitoringplatform_im.h // 监测平台顶层类
email_im.cpp // 电子邮件功能类实现
about_im.cpp // 关于界面类实现
emailconfigure.cpp // 邮件配置界面类实现
monitorthread_im.cpp // 监测线程类实现
monitoringplarform_im.cpp // 监测平台顶层实现
main.cpp // 调用顶层类,显示顶层类界面
...... // 其它源文件均为实现邮件功能
其中,如果只是像快速掌握并使用起来的话,读者们只需要重点关注标记的这些文件及其对应的 .h
文件即可,若希望完全掌握那么需要将所有的源码都过一遍就好。
上图简要描绘了软件整体的类设计结构,其中名为 MonitoringPlatform_IM
作为整个 App
的主类,由它来调度所有的功能,其中有两种重要的线程类型,一种是 TimeThread_IM
,它的作用主要为实时的时间显示和计时功能,确保邮件发送时间的精确;另一个是 MonitorThread_IM
,它的作用主要是异步监测每台服务器的状态(每一台服务器使用独立的线程来监测状态)。Email_IM
类是封装了邮件功能的顶层类,读者们想要使用邮件功能的话直接可调用该类的成员函数即可非常的方便。还有另外两个 UI
类,一个是 EmailConfigure
类是提供了一个邮件信息配置的界面,并将最终的配置提交给主类;另一个是 About
类,其功能主要是用于展示软件的基本信息。
5. Server Monitor Platform 部署步骤
上文介绍了该软件由两部分组成,一个是运行在服务器上的服务端程序,另一个则是运行在其它平台的监测软件,接下来看看该如何部署这些软件。
5.1. 在服务器上部署服务端程序并打开
5.1.1. 安装 lm_sensors
yum install lm_sensors
首先安装 lm_sensors
工具,由于笔者已经安装过了,因此这里提示没有做任何操作。
接着执行 sensors-detect
,监测当前的传感器类型,看到有出现 Success
字样则说明匹配成功。
5.1.2. 部署服务端软件
在工程的 Monitor_Server-v0.3/monitor_server/
目录下有如下几个文件。
这里需要打开 monitor_server_v0.3.c
,将其中的 IP
地址修改为服务器的 IP
地址。
修改完后编译代码,并放置入 /bin
目录下:
gcc monitor_server_v0.3.c -o monitor_server
cp monitor_server monitor_service /bin
由于该程序是指定端口连接,因此需要在防火墙上开放该端口,或者直接关闭防火墙(并不建议这样做,毕竟是服务器):
# 开放防火墙 21031 端口
firewall-cmd --permanent --add-port=21031/tcp
# 重新加载防火墙
systemctl reload firewalld
# 查看防火墙开放的端口号
firewall-cmd --list-port
启动服务端程序:
monitor_service
看到 Linstening:
则表示启动成功,由于这样会一直输出 Log
信息,直接关闭该 ssh
连接即可。
5.2. 在图形界面操作系统上打开监控平台软件
5.2.1. 使用 Qt Creator 打开工程
用 Qt Creator
打开 Server_Monitoring_Platform
目录下的 Server_Monitoring_Platform.pro
文件,找到 monitoringplatform_im.cpp
文件,在下图地方处修改添加所要监测的服务器 IP
、使用人、管理人、服务器名。
5.2.2. 编译并发布
修改好代码后,使用 Qt Creator
编译出 Release
版本的可执行程序,再使用 Qt
自带的对应编译器版本的终端,用 windeployqt
命令将其所依赖的所有库文件都搜集完整,再使用 Enigma Virtual Box 将其打包,生成一个可以独立运行的可执行文件。在工程中,笔者并没有将服务器信息的录入单独写成模块在运行时录入,这一点各位读者们可以自己动手实现该功能。
5.2.3. 运行并设置监控平台
本文以 Windows
为例,其它操作系统一样。这里使用笔者之前编译发布好的程序,打开工程目录的 object
目录。
直接双击运行 IM Monitoring v1.4.1000 EDA版
可执行程序。
打开可执行程序后立即看到的是各服务器的状态新信息,现在程序已经处于实时监控的状态,但要使用邮件服务,还需要设置一番。
<1> 依次点击 设置 > Email设置
<2> 根据提示填写信息,发送所使用的邮箱、邮箱密码、邮件发送使用的名字、邮箱类型、收件人邮箱地址;
<3> 填写完成后点击 提交,即可设置成功并退出到主界面。需要注意的是这里笔者并没有实现邮箱密码校验功能,也就是说,加入你输入错误的密码,在实际看到的现象是并不会收到邮件,不会提示任何错误信息。目前笔者仅添加了两种发送邮箱的类型,一种是微软家的 Outlook
邮箱,一种是腾讯家的企业微信邮箱。
这里有一个 初次通知的选项,用于设置是否需要向所有用户发送软件介绍。
<4> 提交完成后,到主界面点击左下角的 邮件通知,即表示开启邮件服务。
若在邮件设置中勾选了 初次通知,则在点击 邮件通知 的同时会向所有收件人发送软件介绍的邮件。如下:
倘若没有勾选 初次通知,则只会给邮件的发送人发送软件开启提醒邮件,用来提示使用人软件的邮件服务已正常运转。如下:
在菜单中还有其它功能,工具 > 重连,该功能是在服务器掉线后重启并启动服务端程序后,点击此按钮则会重连所有断开连接的服务器并恢复监控显示功能。
帮助 > 关于,点击则会弹出软件的基本信息介绍。
6. 邮件功能介绍
邮件通知功能有以下三种:
- 服务器日常通知: 用于在每天的指定时间定时汇报服务器当前状态;
-
紧急通知邮件: 当服务器温度超过
65℃
时即刻发送邮件通知管理人和所有收件人,此时风险等级为Lv:1
。当服务器持续温度超过65℃
半个小时后,连续发送两次 “紧急通知邮件” 此时风险等级为Lv:2
,每次间隔5 min
,若在此期间没有管理人员操作使得机器温度降低,那么在总共收到3
封紧急通知邮件,软件将会强制使服务器关机; -
风险解除通知邮件: 当风险等级达到
Lv:2
时仍无人处理,软件会强制服务器关机,在执行完关机动作后会向收件人群发送 “风险解除,高温机器已关机” 的邮件。
6.1. 服务器日常通知示例邮件
该软件会在每天的指定时间向邮件接收人发送服务器的实时状态,以及当日的最高温度,其效果如下。
6.2. 服务器高温测试示例
在此例演示时,笔者将服务器的预警温度设为 25℃
,以便快速达到预警温度值。以服务器 Server30-122
为例,当其第一次温度超过 25℃
时,系统会向收件人发送 “紧急通知邮件”,此时风险等级为 Lv:1
。
持续 25℃
以上运行半小时以后继续再次发送 “紧急通知邮件”,此时风险等级为 Lv:2
。
当第接收到第二封风险等级为 Lv:2
的 “紧急通知邮件” 时,之后系统将执行强制关机指令,稍后将会收到 “风险解除通知”。
7. 补充
事实上在代码中笔者已经完善了邮件发送的文字,目前发送的邮件中,温度若低于预警值则会以 绿色 显示,若高于预警值则会以 红色 显示,若该服务器以关机则以不显眼的 淡紫色 显示。文章来源:https://www.toymoban.com/news/detail-430877.html
#完
觉得这篇文章对你有帮助的话,就留下一个赞吧^v^*
请尊重作者,转载还请注明出处!感谢配合~
[作者]: Imagine Miracle
[版权]: 本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
[本文链接]: https://blog.csdn.net/qq_36393978/article/details/126369492文章来源地址https://www.toymoban.com/news/detail-430877.html
到了这里,关于【开源软件】服务器状态监控通知平台的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!