气象项目数据中心开发-服务器-3

守护进程模块的开发

目标:解决服务程序死机(挂起)的问题,守护进程将终止它,完成善后工作

此后,服务程序被终止后,调度程序将可以重启它

这将保证了系统的正常运行

  • 心跳机制

    每个服务程序在共享内存中占用一段空间作为自己的心跳,给出心跳结构

    struct st_procinfo
    {
        int pid; 		//进程id
        char pname[51]; //进程名称
        int timeout;	//超时时间
        time_t atime;	//最后一次心跳的时间   
    }
    

    系统周期巡检每个服务程序的心跳,若超时(超时时间=系统当前时间 - 最后一次心跳时间),则没有心跳的进程可被终止,调度程序将重启该进程。

  • heartBeat.cpp

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <signal.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include "_public.h"
    
    #define MAXNUMP_ 1000   // 最大进程数量
    #define SHMKEYP_ 0x5095 // 共享内存的key
    
    struct st_pinfo // 心跳结构
    {
        int pid;        // 进程id
        char pname[51]; // 进程名称
        int timeout;    // 超时时间
        time_t atime;   // 最后一次心跳的时间
    };
    
    int main(int argc, char *argv[])
    {
        if (argc < 2)
        {
            printf("Using:./book procname\n");
            return 0;
        }
    
        int m_shmid = 0;
        if ((m_shmid = shmget(MAXNUMP_, SHMKEYP_ * sizeof(struct st_pinfo), 0640 | IPC_CREAT)) == -1) // 创建/获取共享内存,大小为n*sizeof(struct st_pinfo)
        {
            printf("shmget(%x) failed\n", MAXNUMP_);
            return -1;
        }
    
        CSEM m_sem;
        if (m_sem.init(SHMKEYP_) == false)
        {
            printf("m_sem.init(%x) failed\n", SHMKEYP_);
            return -1;
        }
    
        struct st_pinfo *m_shm; // 1.将共享内存连接到当前进程的地址空间
        m_shm = (struct st_pinfo *)shmat(m_shmid, 0, 0);
    
        struct st_pinfo stpinfo;
        memset(&stpinfo, 0, sizeof(struct st_pinfo));
        stpinfo.pid = getpid();
        STRNCPY(stpinfo.pname, sizeof(stpinfo.pname), argv[1], 50); // 进程名称
        stpinfo.timeout = 30;                                        // 超时时间30s
        stpinfo.atime = time(0);
    
        int m_pos = -1; // 2.共享内存中查找一个空位置,把当前进程的心跳信息存入共享内存中
    
        
        for (int ii = 0; ii < SHMKEYP_; ii++)// [修复2]如果共享内存中存在当前进程编号,一定是其他进程残留的数据,当前进程就重用该位置。
        {
            if (m_shm[ii].pid == stpinfo.pid)
            {
                m_pos = ii;
                break;
            } /* code */
        }
    
        if (m_pos == -1)
        {
            for (int ii = 0; ii < SHMKEYP_; ii++)
            {
                if ((m_shm + ii)->pid == 0) // 3.找到空位置
                {
    
                    m_pos = ii;
                    break;
                }
            }
        }
        if (m_pos == -1)
        {
            printf("共享内存空间已用完\n");
            return -1;
        }
        memcpy(m_shm + m_pos, &stpinfo, sizeof(struct st_pinfo));
    
        while (true)
        {
    
            m_shm[m_pos].atime = time(0);//更新共享内存中本进程的心跳时间
            sleep(10); /* code */
        }
    
        
        memset(m_shm + m_pos, 0, sizeof(struct st_pinfo));// m_shm[m_pos].pid=0 //4.把当前进程从共享内存中移除
    
      
        shmdt(m_shm);  // 5.把共享内存从当前进程中分离
    
        return 0;
    }
    

    下面进行gdb调试,进程1运行 pname =“aaa”,进程2运行pname=“bbb”,在3号窗口调试,断点设置在

    **memcpy(m_shm + m_pos, &stpinfo, sizeof(struct st_pinfo));**处

在这里插入图片描述

可见,两服务程程序(进程1&进程2)的心跳信息已经录入

  • 进程在独立访问共享内存的时候,为了保证其稳定占用这个空间(即心跳结构体为该进程开辟的一片内存)需要加锁,访问完共享内存解锁

给出互斥锁的定义

CSEM m_sem;
    if ((m_sem.init(SHMKEYP_) )== false)
    {
        printf("m_sem.init(%x) failed\n", SHMKEYP_);
        return -1;
    }

heartBeat已经能完成对将服务程序的心跳记录写入共享内存

下面将遍历共享内存,将没有心跳的进程终止

/* checkproc.cpp 遍历共享内存,终止没有心跳的进程*/
#include "_public.h"

// 程序运行的日志。
CLogFile logfile;

int main(int argc,char *argv[])
{
  // 程序的帮助。
  if (argc != 2)
  {
    printf("\n");
    printf("Using:./checkproc logfilename\n");

    printf("Example:/project/tools1/bin/procctl 10 /project/tools1/bin/checkproc /tmp/log/checkproc.log\n\n");

    printf("本程序用于检查后台服务程序是否超时,如果已超时,就终止它。\n");
    printf("注意:\n");
    printf("  1)本程序由procctl启动,运行周期建议为10秒。\n");
    printf("  2)为了避免被普通用户误杀,本程序应该用root用户启动。\n");
    printf("  3)如果要停止本程序,只能用killall -9 终止。\n\n\n");

    return 0;
  }

  // 忽略全部的信号和IO,不希望程序被干扰。
  CloseIOAndSignal(true);

  // 打开日志文件。
  if (logfile.Open(argv[1],"a+")==false)
  { printf("logfile.Open(%s) failed.\n",argv[1]); return -1; }

  int shmid=0;

  // 创建/获取共享内存,键值为SHMKEYP,大小为MAXNUMP个st_procinfo结构体的大小。
  if ( (shmid = shmget((key_t)SHMKEYP, MAXNUMP*sizeof(struct st_procinfo), 0666|IPC_CREAT)) == -1)
  {
    logfile.Write("创建/获取共享内存(%x)失败。\n",SHMKEYP); return false;
  }

  // 将共享内存连接到当前进程的地址空间。
  struct st_procinfo *shm=(struct st_procinfo *)shmat(shmid, 0, 0);

  // 遍历共享内存中全部的记录。
  for (int ii=0;ii<MAXNUMP;ii++)
  {
    // 如果记录的pid==0,表示空记录,continue;
    if (shm[ii].pid==0) continue;

    // 如果记录的pid!=0,表示是服务程序的心跳记录。

    // 程序稳定运行后,以下两行代码可以注释掉。
    //logfile.Write("ii=%d,pid=%d,pname=%s,timeout=%d,atime=%d\n",\
    //               ii,shm[ii].pid,shm[ii].pname,shm[ii].timeout,shm[ii].atime);

    // 向进程发送信号0,判断它是否还存在,如果不存在,从共享内存中删除该记录,continue;
    int iret=kill(shm[ii].pid,0);
    if (iret==-1)
    {
      logfile.Write("进程pid=%d(%s)已经不存在。\n",(shm+ii)->pid,(shm+ii)->pname);
      memset(shm+ii,0,sizeof(struct st_procinfo)); // 从共享内存中删除该记录。
      continue;
    }

    time_t now=time(0);   // 取当前时间。

    // 如果进程未超时,continue;
    if (now-shm[ii].atime<shm[ii].timeout) continue;

    // 如果已超时。
    logfile.Write("进程pid=%d(%s)已经超时。\n",(shm+ii)->pid,(shm+ii)->pname);

    // 发送信号15,尝试正常终止进程。
    kill(shm[ii].pid,15);     

    // 每隔1秒判断一次进程是否存在,累计5秒,一般来说,5秒的时间足够让进程退出。
    for (int jj=0;jj<5;jj++)
    {
      sleep(1);
      iret=kill(shm[ii].pid,0);     // 向进程发送信号0,判断它是否还存在。
      if (iret==-1) break;     // 进程已退出。
    } 

    // 如果进程仍存在,就发送信号9,强制终止它。
    if (iret==-1)
      logfile.Write("进程pid=%d(%s)已经正常终止。\n",(shm+ii)->pid,(shm+ii)->pname);
    else
    {
      kill(shm[ii].pid,9);  // 如果进程仍存在,就发送信号9,强制终止它。
      logfile.Write("进程pid=%d(%s)已经强制终止。\n",(shm+ii)->pid,(shm+ii)->pname);
    }
    
    // 从共享内存中删除已超时进程的心跳记录。
    memset(shm+ii,0,sizeof(struct st_procinfo)); // 从共享内存中删除该记录。
  }

  // 把共享内存从当前进程中分离。
  shmdt(shm);

  return 0;
}

运行前,清空共享内存段
在这里插入图片描述

使用heartBeat创建三个进程,aaa,bbb,ccc,超时时间设置为50 ,60 ,70s

查看内存,共享内存中目前由三个进程

在这里插入图片描述

再利用checkproc遍历共享内存

查看日志,进程状态可见

在这里插入图片描述

开发小工具1:压缩文件

#include "_public.h"

// 程序退出和信号2、15的处理函数。
void EXIT(int sig);

int main(int argc,char *argv[])
{
  // 程序的帮助。
  if (argc != 4)
  {
    printf("\n");
    printf("Using:/project/tools1/bin/gzipfiles pathname matchstr timeout\n\n");

    printf("Example:/project/tools1/bin/gzipfiles /log/idc \"*.log.20*\" 0.02\n");
    printf("        /project/tools1/bin/gzipfiles /tmp/idc/surfdata \"*.xml,*.json\" 0.01\n");
    printf("        /project/tools1/bin/procctl 300 /project/tools1/bin/gzipfiles /log/idc \"*.log.20*\" 0.02\n");
    printf("        /project/tools1/bin/procctl 300 /project/tools1/bin/gzipfiles /tmp/idc/surfdata \"*.xml,*.json\" 0.01\n\n");

    printf("这是一个工具程序,用于压缩历史的数据文件或日志文件。\n");
    printf("本程序把pathname目录及子目录中timeout天之前的匹配matchstr文件全部压缩,timeout可以是小数。\n");
    printf("本程序不写日志文件,也不会在控制台输出任何信息。\n");
    printf("本程序调用/usr/bin/gzip命令压缩文件。\n\n\n");

    return -1;
  }

  // 关闭全部的信号和输入输出。
  // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程。
  // 但请不要用 "kill -9 +进程号" 强行终止。
  CloseIOAndSignal(true); signal(SIGINT,EXIT);  signal(SIGTERM,EXIT);

  // 获取文件超时的时间点。
  char strTimeOut[21];
  LocalTime(strTimeOut,"yyyy-mm-dd hh24:mi:ss",0-(int)(atof(argv[3])*24*60*60));

  CDir Dir;
  // 打开目录,CDir.OpenDir()
  if (Dir.OpenDir(argv[1],argv[2],10000,true)==false)
  {
    printf("Dir.OpenDir(%s) failed.\n",argv[1]); return -1;
  }

  char strCmd[1024]; // 存放gzip压缩文件的命令。

  // 遍历目录中的文件名。
  while (true)
  {
    // 得到一个文件的信息,CDir.ReadDir()
    if (Dir.ReadDir()==false) break;
  
    // 与超时的时间点比较,如果更早,就需要压缩
    if ( (strcmp(Dir.m_ModifyTime,strTimeOut)<0) && (MatchStr(Dir.m_FileName,"*.gz")==false) )
    {
      // 压缩文件,调用操作系统的gzip命令。
      SNPRINTF(strCmd,sizeof(strCmd),1000,"/usr/bin/gzip -f %s 1>/dev/null 2>/dev/null",Dir.m_FullFileName);
      if (system(strCmd)==0) 
        printf("gzip %s ok.\n",Dir.m_FullFileName);
      else
        printf("gzip %s failed.\n",Dir.m_FullFileName);
    }
  }

  return 0;
}

void EXIT(int sig)
{
  printf("程序退出,sig=%d\n\n",sig);

  exit(0);
}

  • 启用自动化调用服务程序

在这里插入图片描述

  • 再运行gzipfiles实现自动压缩

在这里插入图片描述

查看文件 /tmp/idc/surfdata目录,应该只有xml和json数据文件被压缩
在这里插入图片描述

开发小工具2:清理历史数据文件模块

#include "_public.h"

void EXIT(int sig);// 程序退出和信号2、15的处理函数。

int main(int argc,char *argv[])
{
  
  if (argc != 4)
  {
    printf("\n");// 程序的帮助。
    printf("Using:/project/tools1/bin/deletefiles pathname matchstr timeout\n\n");

    printf("Example:/project/tools1/bin/deletefiles /log/idc \"*.log.20*\" 0.02\n");
    printf("        /project/tools1/bin/deletefiles /tmp/idc/surfdata \"*.xml,*.json\" 0.01\n");
    printf("        /project/tools1/bin/procctl 300 /project/tools1/bin/deletefiles /log/idc \"*.log.20*\" 0.02\n");
    printf("        /project/tools1/bin/procctl 300 /project/tools1/bin/deletefiles /tmp/idc/surfdata \"*.xml,*.json\" 0.01\n\n");

    printf("这是一个工具程序,用于压缩历史的数据文件或日志文件。\n");
    printf("本程序把pathname目录及子目录中timeout天之前的匹配matchstr文件全部删除,timeout可以是小数。\n");
    printf("本程序不写日志文件,也不会在控制台输出任何信息。\n");
    return -1;
  }

 
  CloseIOAndSignal(true); signal(SIGINT,EXIT);  signal(SIGTERM,EXIT); // 关闭全部的信号和输入输出。 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程。但请不要用 "kill -9 +进程号" 强行终止。

  
  char strTimeOut[21];// 获取文件超时的时间点。
  LocalTime(strTimeOut,"yyyy-mm-dd hh24:mi:ss",0-(int)(atof(argv[3])*24*60*60));

  CDir Dir;
  
  if (Dir.OpenDir(argv[1],argv[2],10000,true)==false)// 打开目录,CDir.OpenDir()
  {
    printf("Dir.OpenDir(%s) failed.\n",argv[1]); return -1;
  }

  char strCmd[1024]; // 存放gzip压缩文件的命令。

  
  while (true)// 遍历目录中的文件名。
  {
    
    if (Dir.ReadDir()==false) break;// 得到一个文件的信息,CDir.ReadDir()
  
    
    if (strcmp(Dir.m_ModifyTime,strTimeOut)<0) 
    {
      
     if (REMOVE(Dir.m_FullFileName)==true) 
        printf("REMOVE %s ok.\n",Dir.m_FullFileName);
      else
        printf("REMOVE %s failed.\n",Dir.m_FullFileName);
    }
  }

  return 0;
}

void EXIT(int sig)
{
  printf("程序退出,sig=%d\n\n",sig);

  exit(0);
}

服务程序运行策略

封装启动脚本start.sh

###########################################################################################
#启动数据中心后台服务程序的脚本
###########################################################################################

#检查服务程序是否超时,配置在/etc/rc.local中由root用户执行
/project/tools1/bin/procct1 30 /project/tools1/bin/checkproc

#压缩数据中心后台服务程序的备份日志 压缩20年之后的,0.04天前的日志信息
/project/tools1/bin/procct1 300 /project/tools1/bin/gzipfiles /log/idc "*.log.20*" 0.04

#生成用于测试的全国气象站点观测的分钟数据
/project/tools1/bin/procct1 60 /project/idc/bin/crtsurfdata /project/idc/ini/stcode.ini /tmp/idc/surfdata /log/idc/crtsurfdata.log xml,json,csv

#清理原始的全国气象站点观测的分钟数据目录 /tmp/idc/surfdata中的历史数据文件
/project/tools1/bin/procct1 300 /project/tools1/bin/deletefiles /tmp/idc/surfdata "*" 0.04

在这里插入图片描述

结束脚本killall.sh

#先发送9的信号让procct1调度程序强制终止
killall -9 procct1
#再让服务程序退出
killall gzipfiles crtsurfdata deletefiles

sleep 3
#保证服务程序强制退出
killall -9 gzipfiles crtsurfdata deletefiles

在这里插入图片描述

配置/etc/rc.local 文件,实现开机自启

#!/bin/bash
# THIS FILE IS ADDED FOR COMPATIBILITY PURPOSES
#
# It is highly advisable to create own systemd services or udev rules
# to run scripts during boot instead of using this file.
#
# In contrast to previous versions due to parallel execution during boot
# this script will NOT be run after all other services.
#
# Please note that you must run 'chmod +x /etc/rc.d/rc.local' to ensure
# that this script will be executed during boot.

touch /var/lock/subsys/local

# 检查服务程序是否超时
/project/tools/bin/procct1 30 /project/tools/bin/checkproc

#启动数据中心的后台服务程序
su - kiana100 -c "/bin/sh /project/idc1/c/start.sh"

技术小结

  • 生成测试数据
  • 调度程序
  • 守护程序
  • 文件的压缩和清理
  • 信号、多进程、共享信号量

工作态度

  • 把课程内容当成工作
  • 养成严谨的西瓜
  • 稳定、高效、简洁
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值