cpp随笔——如何实现一个简单的进程心跳功能

什么是进程的心跳

在我们日常后台服务程序运行中,一般是调度模块,进程心跳以及进程监控共同工作,进而实现实现服务的稳定运行,在前面我们介绍过如何去实现一个简单的调度模块,而今天我们所要介绍的就是如何实现进程的心跳,首先什么是进程的心跳呢?进程的心跳机制是一种监控进程健康状况的方法,通常用于服务端应用或者需要长期运行的后台守护进程(daemon,我们在后台服务程序在运行中由于有很多功能需要运行,所以一般是以多进程的方式来同时运行多个后台服务程序,那么问题来了,当有多个后台服务程序在运行时我们如何来确定它们的运行状态呢?这就是进程心跳的功能了,进程心跳,顾名思义,就是我们用来判断进程是否正常运行的一种手段,我们首先定义一个心跳信息结构体,如下:

struct stprocinfo  //存储进程心跳信息的结构体
{int pid;  //进程编号char name[50]={0}; //进程名称int timeout;  //超时时间time_t atime; //最后一次心跳时间stprocinfo() =default;  //默认构造函数 stprocinfo(int pid,char* name,int timeout,time_t atime):pid(pid),timeout(timeout),atime(atime){strcpy(this->name,name);}
};

结构体中存储了进程的相关信息,同时我们会通过atimetimeout来判断结构体是否是正常运行,下面我们来看一下具体的实现过程。

进程心跳的初步实现

在这里插入图片描述
上面是一个进程心跳的初步实现,主要是以下几步:

  1. 创建共享内存
 //创建共享内存shmid=shmget((key_t)0X5550,sizeof(struct stprocinfo)*1000,IPC_CREAT|0666);if (shmid==-1){perror("创建共享内存失败");return -1;}cout<<"shmid="<<shmid<<endl;
  1. 将共享内存连接到当前进程的地址空间
    //将共享内存连接到当前进程的地址空间m_shm=(stprocinfo*)shmat(shmid,nullptr,0);if(m_shm==(void*)-1){perror("共享内存连接失败");return -1;}cout<<"shmat ok"<<endl;
  1. 遍历共享内存,寻找空闲位置
//遍历当前共享内存的占用情况,主要用来调试for(int i=0;i<1000;i++){if(m_shm[i].pid!=0){cout<<"pid="<<m_shm[i].pid<<" "<<"name="<<m_shm[i].name<<" "<<"timeout="<<m_shm[i].timeout<<" "<<"atime="<<m_shm[i].atime<<endl;}}stprocinfo info(getpid(),"server1",30,time(0));//在共享内存中查找是否有空闲的地址for(int i=0;i<1000;i++){if(m_shm[i].pid==0){m_pos=i;cout<<"找到空闲地址,m_pos="<<m_pos<<endl;break;}}if(m_pos==-1){cout<<"共享内存已满"<<endl;Exit(-1);}memcp
  1. 程序运行并且更新进程心跳
    while(1){//写入程序的运行逻辑sleep(10);  //模拟程序运行m_shm[m_pos].atime=time(0);  //更新当前进程的心跳时间}

完整代码如下:

#include "../../public/_public.h"using namespace idc;   //自己封装的命名空间struct stprocinfo  //存储进程心跳信息的结构体
{int pid;  //进程编号char name[50]={0}; //进程名称int timeout;  //超时时间time_t atime; //最后一次心跳时间stprocinfo() =default;  //默认构造函数 stprocinfo(int pid,char* name,int timeout,time_t atime):pid(pid),timeout(timeout),atime(atime){strcpy(this->name,name);}
};int shmid=-1; //共享内存的ID
stprocinfo* m_shm=nullptr;//指向共享内存的地址
int m_pos=-1; //记录当前进程在共享内存的位置
void Exit(int sig); //信号处理函数int main()
{//信号处理signal(SIGINT,Exit);signal(SIGTERM,Exit);//创建共享内存shmid=shmget((key_t)0X5550,sizeof(struct stprocinfo)*1000,IPC_CREAT|0666);if (shmid==-1){perror("创建共享内存失败");return -1;}cout<<"shmid="<<shmid<<endl;//将共享内存连接到当前进程的地址空间m_shm=(stprocinfo*)shmat(shmid,nullptr,0);if(m_shm==(void*)-1){perror("共享内存连接失败");return -1;}cout<<"shmat ok"<<endl;//遍历当前共享内存的占用情况,主要用来调试for(int i=0;i<1000;i++){if(m_shm[i].pid!=0){cout<<"pid="<<m_shm[i].pid<<" "<<"name="<<m_shm[i].name<<" "<<"timeout="<<m_shm[i].timeout<<" "<<"atime="<<m_shm[i].atime<<endl;}}stprocinfo info(getpid(),"server1",30,time(0));//在共享内存中查找是否有空闲的地址for(int i=0;i<1000;i++){if(m_shm[i].pid==0){m_pos=i;cout<<"找到空闲地址,m_pos="<<m_pos<<endl;break;}}if(m_pos==-1){cout<<"共享内存已满"<<endl;Exit(-1);}memcpy(&m_shm[m_pos],&info,sizeof(struct stprocinfo));  //将当前进程的心跳信息存入共享内存while(1){//写入程序的运行逻辑sleep(10);  //模拟程序运行m_shm[m_pos].atime=time(0);  //更新当前进程的心跳时间}return 0;
}void Exit(int sig)
{cout<<"收到信号:"<<sig<<endl;//清除共享内存中的心跳信息结构体if(m_pos!=-1){memset(m_shm+m_pos,0,sizeof(struct stprocinfo));m_pos=-1;}//将共享内存分离出来if(m_shm!=nullptr){shmdt(m_shm);}exit(0);
}

编译代码如下:

# 开发框架头文件路径
PUBINCL = -I/root/mylib/project/public# 开发框架cpp文件名,这里直接包含进来,没有采用链接库,是为了方便调试。
PUBLICPP = /root/mylib/project/public/_public.cpp
# 编译参数
CFLAGS= -gall: demo1demo1:demo1.cppg++ $(CFLAGS) -o demo1 demo1.cpp $(PUBLICPP)clean:rm -f demo1

注明
以上仅供参考,大家根据自己的需求来进行修改。

测试并且运行,这里我们开启三个命令行来测试:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们看到基本实现了我们想的功能,将带有进程心跳的信息储存到了结构体中,后面我们会介绍我们如何基于守护进程来对进程的心跳来实现对进程状态的监控,不过这是后话了。

进程心跳代码的优化

上面我们已经实现了一个简单的进程心跳代码,但是现在有几个实际情况需要我们来考虑,分别是:

  • 这里我们关于退出信号的处理是无法处理kill -9这种异常退出的,这个时候我们的Exit函数是无法正常发挥功能的,同时也会导致该进程的心跳信息残留在共享内存中,所以我们要对代码进行优化:
    //在共享内存中查找是否有空闲的地址for(int i=0;i<1000;i++)  //如果有与该进程pid相同,说明有进程未清理干净{if(m_shm[i].pid==info.pid){cout<<"找到旧进程"<<endl;m_pos=i;break;}}if(m_pos==-1){for(int i=0;i<1000;i++){if(m_shm[i].pid==0){m_pos=i;cout<<"找到空闲地址,m_pos="<<m_pos<<endl;break;}}}if(m_pos==-1){cout<<"共享内存已满"<<endl;Exit(-1);}
  • 这里我们要求的使用场景是多个进程在同时运行,那么问题来了,如果同时多个程序运行同时向共享内存的一块地址写怎么办?这里我们可以基于进程同步来解决这个问题,而向实现进程同步,就需要信号量了,这里我使用的是已经封装好的信号量,代码如下:
    class csemp{private:union semun // 用于操控共享内存的联合体{int value;struct semid_ds *buf;unsigned short *arry;};int m_semid; // 信号量id/*如果将m_semflg设为SEM_UNOD,操作系统将跟踪进程对信号量的修改,在全部修改过信号量的进程终止后将信号量设置为初始值m_semflag=1时用于互斥锁,m_semflag=0时用于生产消费者模型*/short m_semflg;                           // 信号量值csemp(const csemp &) = delete;            // 禁用拷贝构造函数csemp &operator=(const csemp &) = delete; // 禁用赋值运算符public:csemp() : m_semid(-1) {}/*如果信号量已存在,就获取信号量如果信号量不存在,就创建信号量并将其初始化为value互斥锁时,value=1,semflag=SEM_UNOD生产消费者模型时,value=0,semflag=0*/bool init(key_t key, unsigned short value = 1, short semflg = SEM_UNDO);bool wait(short value = -1); // P操作bool post(short value = 1);  // V操作int getvalue();bool destroy();~csemp();};bool csemp::init(key_t key, unsigned short value, short semflg){if (m_semid != -1) // 信号量已经初始化了{return false;}m_semflg = semflg;if ((m_semid = semget(key, 1, 0666)) == -1) // 尝试获取信号量{if (errno == ENOENT) // 未找到信号量{if ((m_semid = semget(key, 1, IPC_CREAT | 0666 | IPC_EXCL)) == -1) // 创建信号量{if (errno == EEXIST) // 信号量已存在{if ((m_semid = semget(key, 1, 0666)) == -1){perror("init semget 1");return false;}return true;}else{perror("init semget 2");return false;}}union semun b;b.value = value;if (semctl(m_semid, 0, SETVAL, b) == -1) // 设置信号量初值{perror("init semctl");return false;}}else{perror("init semget 3");return false;}}return true;}bool csemp::wait(short value){if (m_semid == -1)return false;struct sembuf s;s.sem_num = 0;s.sem_op = value;s.sem_flg = m_semflg;if (semop(m_semid, &s, 1) == -1){perror("wait semop");return false;}return true;}bool csemp::post(short value){if (m_semid == -1)return false;struct sembuf s;s.sem_num = 0;s.sem_op = value;s.sem_flg = m_semflg;if (semop(m_semid, &s, 1) == -1){perror("post semop");return false;}return true;}int csemp::getvalue(){return semctl(m_semid, 0, GETVAL);}bool csemp::destroy(){if (m_semid == -1)return false;if (semctl(m_semid, 0, IPC_RMID) == -1){perror("destroy semctl");return false;}return true;}csemp::~csemp(){}

实现代码如下:

 csemp shmlock;if(shmlock.init(0x5550)==-1){cout<<"shmlock init error"<<endl;Exit(-1);}shmlock.wait(); //加锁//在共享内存中查找是否有空闲的地址for(int i=0;i<1000;i++){if(m_shm[i].pid==info.pid){cout<<"找到旧进程"<<endl;m_pos=i;break;}}if(m_pos==-1){for(int i=0;i<1000;i++){if(m_shm[i].pid==0){m_pos=i;cout<<"找到空闲地址,m_pos="<<m_pos<<endl;break;}}}if(m_pos==-1){cout<<"共享内存已满"<<endl;shmlock.post(); //解锁Exit(-1);}memcpy(&m_shm[m_pos],&info,sizeof(struct stprocinfo));  //将当前进程的心跳信息存入共享内存shmlock.post(); //解锁

最后我们就实现一个基本的可以实现多进程监控的进程心跳程序了,在最后我们可以将它封装成一个简单的类,供我们以后使用:

    // 进程心跳有关的类struct st_procinfo // 存储进程心跳信息的结构体{int pid;                // 进程编号char name[50] = {0};    // 进程名称int timeout;            // 超时时间time_t atime;           // 最后一次心跳时间st_procinfo() = default; // 默认构造函数st_procinfo(int pid, char *name, int timeout, time_t atime) : pid(pid), timeout(timeout), atime(atime){strcpy(this->name, name);}};#define MAXNUM 1000;   // 最大进程数#define SHMKEYP 0x5095 // 共享内存的key。#define SEMKEYP 0x5095 // 信号量的key。class cpactive // 实现进程心跳的类{private:int shmid;int m_pos;st_procinfo *m_shm;public:cpactive();bool init(int timeout,string pname="",clogfile *logfile=nullptr);  //这里传指针视为了选择是否使用日志打印bool update();  //更新心跳时间~cpactive();};cpactive::cpactive(){m_shmid=0;m_pos=-1;m_shm=0;}// 把当前进程的信息加入共享内存进程组中。bool cpactive::addpinfo(const int timeout,const string &pname,clogfile *logfile){if (m_pos!=-1) return true;// 创建/获取共享内存,键值为SHMKEYP,大小为MAXNUMP个st_procinfo结构体的大小。if ( (m_shmid = shmget((key_t)SHMKEYP, MAXNUMP*sizeof(struct st_procinfo), 0666|IPC_CREAT)) == -1){ if (logfile!=nullptr) logfile->write("创建/获取共享内存(%x)失败。\n",SHMKEYP); else printf("创建/获取共享内存(%x)失败。\n",SHMKEYP);return false; }// 将共享内存连接到当前进程的地址空间。m_shm=(struct st_procinfo *)shmat(m_shmid, 0, 0);/*struct st_procinfo stprocinfo;    // 当前进程心跳信息的结构体。memset(&stprocinfo,0,sizeof(stprocinfo));stprocinfo.pid=getpid();            // 当前进程号。stprocinfo.timeout=timeout;         // 超时时间。stprocinfo.atime=time(0);           // 当前时间。strncpy(stprocinfo.pname,pname.c_str(),50); // 进程名。*/st_procinfo stprocinfo(getpid(),pname.c_str(),timeout,time(0));    // 当前进程心跳信息的结构体。// 进程id是循环使用的,如果曾经有一个进程异常退出,没有清理自己的心跳信息,// 它的进程信息将残留在共享内存中,不巧的是,如果当前进程重用了它的id,// 守护进程检查到残留进程的信息时,会向进程id发送退出信号,将误杀当前进程。// 所以,如果共享内存中已存在当前进程编号,一定是其它进程残留的信息,当前进程应该重用这个位置。for (int ii=0;ii<MAXNUMP;ii++){if ( (m_shm+ii)->pid==stprocinfo.pid ) { m_pos=ii; break; }}csemp semp;                       // 用于给共享内存加锁的信号量id。if (semp.init(SEMKEYP) == false)  // 初始化信号量。{if (logfile!=nullptr) logfile->write("创建/获取信号量(%x)失败。\n",SEMKEYP); else printf("创建/获取信号量(%x)失败。\n",SEMKEYP);return false;}semp.wait();  // 给共享内存上锁。// 如果m_pos==-1,表示共享内存的进程组中不存在当前进程编号,那就找一个空位置。if (m_pos==-1){for (int ii=0;ii<MAXNUMP;ii++)if ( (m_shm+ii)->pid==0 ) { m_pos=ii; break; }}// 如果m_pos==-1,表示没找到空位置,说明共享内存的空间已用完。if (m_pos==-1) { if (logfile!=0) logfile->write("共享内存空间已用完。\n");else printf("共享内存空间已用完。\n");semp.post();  // 解锁。return false; }// 把当前进程的心跳信息存入共享内存的进程组中。memcpy(m_shm+m_pos,&stprocinfo,sizeof(struct st_procinfo)); semp.post();   // 解锁。return true;}// 更新共享内存进程组中当前进程的心跳时间。bool cpactive::uptatime(){if (m_pos==-1) return false;(m_shm+m_pos)->atime=time(0);return true;}cpactive::~cpactive(){// 把当前进程从共享内存的进程组中移去。if (m_pos!=-1) memset(m_shm+m_pos,0,sizeof(struct st_procinfo));// 把共享内存从当前进程中分离。if (m_shm!=0) shmdt(m_shm);}

至此一个简单的进程心跳类就封装号了,后面我会介绍如何基于守护进程,进程心跳和调度模块来实现对进程的监控,大家下篇见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/39440.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git上传文件

git init git add . git commit -m " " git remote add origin 仓库的地址 git push -u origin master 如果出现以下问题 可以用这一句强制上传 git push -f origin master

Centos下rpm和yum执行卡住问题(已解决)

问题描述 执行rpm和yum卡住&#xff0c; 没有任何报错信息&#xff0c;且无法 ctrl c 终止&#xff0c;只能通过后台 kill -9 杀死。 问题排查&#xff1a; 查看yum日志&#xff1a;yum -vv 软件包 会发现卡在 loading keyring from rpmdb&#xff0c;即load DB存在问题。 …

使用 llamaIndex 快速实现智能体

AI 智能体就是可以根据当前环境进行推理&#xff0c;并根据处理结果进行下一步的操作。简单来说 AI 智能体可以与外界环境进行交互&#xff0c;并根据结果执行更复杂的操作。本文将通过llamaIndex 实现一个简单的 Agent 实时获取数据&#xff0c;由于大模型是通过静态数据进行训…

收银系统源码分享-PHP可二开

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 私有化独立…

游戏工作室如何巧妙应对IP封禁风险?

游戏工作室在使用IP时&#xff0c;面临着封号的风险&#xff0c;因此需要采取一些防封技巧来保护自己的运营。以下是一些游戏工作室常用的防封技巧。 1. 多IP轮换 游戏工作室可以使用多个代理IP&#xff0c;并定期轮换它们。这样做可以减少单个IP被频繁访问同一游戏服务器而被…

C++_03

1、构造函数 1.1 什么是构造函数 类的构造函数是类的一种特殊的成员函数&#xff0c;它会在每次创建类的新对象时执行。 每次构造的是构造成员变量的初始化值&#xff0c;内存空间等。 构造函数的名称与类的名称是完全相同的&#xff0c;并且不会返回任何类型&#xff0c;也不…

Windows系统安装SSH服务结合内网穿透配置公网地址远程ssh连接

前言 在当今的数字化转型时代&#xff0c;远程连接和管理计算机已成为日常工作中不可或缺的一部分。对于 Windows 用户而言&#xff0c;SSH&#xff08;Secure Shell&#xff09;协议提供了一种安全、高效的远程访问和命令执行方式。SSH 不仅提供了加密的通信通道&#xff0c;…

路由的高级用法

多级路由 1.新建一个Mian组件 <template><div> <h1>我是Msg的子组件</h1></div> </template><script> export default {name: "Mian", } </script><style> </style> 2.在router中msg小新建一个路由 imp…

Canvas合集更更更之实现由画布中心向外随机不断发散的粒子效果

实现效果 1.支持颜色设置 2.支持粒子数量设置 3.支持粒子大小设置 写在最后&#x1f352; 源码&#xff0c;关注&#x1f365;苏苏的bug&#xff0c;&#x1f361;苏苏的github&#xff0c;&#x1f36a;苏苏的码云

实验九 存储过程和触发器

题目 创建并执行一个无参数的存储过程proc_product1&#xff0c;通过该存储过程可以查询商品类别名称为“笔记本电脑”的商品的详细信息&#xff1a;包括商品编号、商品名称、品牌、库存量、单价和上架时间信息 2、创建并执行一个带输入参数的存储过程proc_product2&#xff…

【软件测试】Postman接口测试基本操作

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;薪资嘎嘎涨 Postman-获取验证码 需求&#xff1a;使用Postman访问验证码接口&#xff0c;并查看响应结果…

图书管理系统(持久化存储数据以及增添新功能)

目录 一、数据库表设计 二、引入MyBatis 和MySQL 驱动依赖 三、配置数据库 & 日志 四、Model创建 五、枚举类 常量类用户登录 六、用户登录 七、添加图书 八、图书列表 九、修改图书 十、删除图书 十一、批量删除 十二、强制登录 十三、前端代码 &#xff0…

AI与测试相辅相成

AI助力软件测试 1.AI赋能软件测试 使用AI工具来帮助测试人员提高测试效率&#xff0c;提供缺陷分析和缺陷预测。 语法格式 设定角色 具体指示 上下文格式 例: 角色&#xff1a;你是一个测试人员 内容&#xff1a;请帮我生成登录案例的测试用例 ​ 1.只有输入正确账号和密码才…

生命在于学习——Python人工智能原理(3.2.1)

二、随机变量 2.1 随机变量及其分布 &#xff08;一&#xff09;基本概念 定义1 随机变量 随机变量表示随机试验各种结果的实值单值函数&#xff0c;即能用数学分析方法来研究随机现象&#xff0c;例如某一时间内公共汽车站等车的乘客人数、淘宝在一定时间内的交易次数等&am…

Shenandoah GC概述

文章目录 1_介绍2_原理1.0版本2.0版本3_ShenandoahGC的执行流程4_并发转移阶段 – 并发问题 1_介绍 Shenandoah 是由Red Hat开发的一款低延迟的垃圾收集器&#xff0c;Shenandoah 并发执行大部分 GC 工作&#xff0c;包括并发的整理&#xff0c;堆大小对STW的时间基本没有影响…

【pearcmd】通过pearcmd.php 进行GetShell

https://cloud.tencent.com/developer/article/2204400 关于PHP 配置 register_argc_argv 小结 的一些研究文章。 应用例题 [NewStarCTF 2023 公开赛道]Include &#x1f350; <?phperror_reporting(0);if(isset($_GET[file])) {$file $_GET[file];if(preg_match(/flag|l…

贪心 | Java | LeetCode 455, 376, 53 做题总结

贪心算法介绍 贪心算法&#xff1a;贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 说实话贪心算法并没有固定的套路。 一般解题步骤 贪心算法一般分为如下四步&#xff1a; ① 将问题分解为若干个子问题 ② 找出适合的贪心策略 ③ 求解每一个子问题的…

SQL Server数据库的组成

《SQL Server 2022从入门到精通&#xff08;视频教学超值版&#xff09;》图书介绍-CSDN博客 对于数据库的概念&#xff0c;没有一个完全固定的定义&#xff0c;随着数据库历史的发展&#xff0c;定义的内容也有很大的差异&#xff0c;其中一种比较普遍的观点认为&#xff0c;…

Winform中使用HttpClient实现调用http的post接口并设置传参content-type为application/json示例

场景 Winform中怎样使用HttpClient调用http的get和post接口并将接口返回json数据解析为实体类&#xff1a; Winform中怎样使用HttpClient调用http的get和post接口并将接口返回json数据解析为实体类_winform解析json-CSDN博客 上面使用HttpClient调用post接口时使用的HttpCon…

21.《C语言》——【位操作符】

&#x1f33b;开场语 亲爱的读者&#xff0c;大家好&#xff01;我是一名正在学习编程的高校生。在这个博客里&#xff0c;我将和大家一起探讨编程技巧、分享实用工具&#xff0c;并交流学习心得。希望通过我的博客&#xff0c;你能学到有用的知识&#xff0c;提高自己的技能&a…