- 论坛徽章:
- 0
|
一,引言
本文取这个名字可能过于大气了,不过还是定下来了,目的是希望有更多的朋友参与进来,给出更好的意见和看法,对文中的方法进行不断的改进.
一直很佩服老外的文章,写的很通俗易懂,而且多原创佳作;而国内的文献,好多都是翻译或者copy的,希望国人的原创思想能够更多的表现出来. 本文中要提到的技术很容易理解,不过觉得还是蛮有创新思想的,而且是自己的感受与不断改进得出的经验之谈(在技术上并没有多高深,不过觉得对实际开发很有实用价值),所以才敢发出来,当中肯定不乏错误种种,欢迎指出!
二, 进程维护简介
提到进程维护,概念很简单,就是想办法让用户的程序不被随意结束掉,或者说即使结束了,也能再次起来,当然,我们这里讨论的程序退出不包括你的代码错误引起的异常,而是被意外的外界条件结束,比如数据库短暂断掉,用户随意错误结束进程等(比如我测试代码时常用的kill命令).我想进程维护最笨的方法就是管理员通过PS命令查看进程是否正常运行,如果结束掉了,则再次手动启动程序,当然这样的情况并不多见,而且这样的做法肯定是不行的,因为没有人愿意守着机子不停的观看,看他启动的程序是否结束掉了,所以最好将这样无聊的任务交给机器自己去做.
(1):进程信息
为了能够在进程结束后再次启动某个进程,我们需要保存程序运行所必须的信息,包括运行路径,运行参数,权限等信息,(如果你的目的不仅仅是再次重启进程的话,其它的信息或许更重要些). 所以在进程运行还没有被结束之前,我们最好将这些信息保存起来,保存进程信息的方式很多,如果不太容易丢失的话,可以选择存在内存中,也可以存在文件中或者共享内存中
(2):进程队列
你不可能花很多力气发明一种技术去维护一个进程,那样确实有些大材小用.现实中往往需要对很多进程进行维护,所以,为了对这么多进程进行统一管理,必须存在一个进程队列.
(3):进程维护者
要维护进程队列中的进程就需要一个程序专门去检测所有被维护进程的状态,并在判断某个进程已经结束后将它重新启动.在这里,我们称这个进程为进程维护服务器,其实就是一个很简单的程序,它拥有所有被维护进程的信息,而且能够对进程的状态作出简单的判断或者猜测,当目标进程被确认已经阻塞住或者死掉时,它能够再次启动目标进程.
(4)进程之间的消息机制
进程维护服务器要获取被维护进程的最新状态,就需要同每一个被维护的进程进行通讯.关于进程通信,有好多种机制,在此,我们选择了Socket的方式,因为这样可以将进程维护服务器部署在网络上的任意一台机子上(前提是它有权限远程结束并且启动另外一台机子上的进程),在本文的实例代码种,只是单机维护,因为我对于远程进程结束和启动的方法还不了解.
(5)进程之间的时间戳同步
为了更新每个进程的最新时间和方便进程维护服务器检测出每一个进程最近一次与其通讯的时刻到当前的时间差,被维护进程每向进程维护服务器发送一个消息,里面都包含了当前的时间,在进程维护服务器端,服务器会将这个进程的最新时间更新为消息中的时间,当然,对于网络上的机子,可能会有不同的本地时间,所以应该选择消息中的时间作为当前的时间,而对于本机上的进程发过来的消息,只需要取本地的时间即可,因为它们拥有相同的时间值.在本文中,我们让进程维护服务器另外启动一个进程,每隔一秒向进程维护服务器发送一个消息,这样对于以阻塞方式接收消息的服务器来说,每隔一秒,它都能收到至少一个消息(那就是它所创建的进程发送过来的时间更新消息),在收到时间更新消息后,进程维护服务器便会根据当前时间去遍历进程节点列表,根据当前时间和每个进程的最新时间计算一个时间差,如果这个时间差大于此进程注册的超时上限,那么我们就认为此进程已经阻塞掉或者被结束了,此时,只需要根据原来此进程注册发送的PID简单的结束此进程(很可能这个进程已经被结束掉了),然后根据此进程注册的路径和参数,再次重启此进程,并删除此节点,因为重启的进程会再次注册一个结点的.
以上就是整个进程维护服务器和每个被维护进程合作的方式,下面给出具体的数据结构和函数列表.
三, 数据结构及流程简介
首先,需要抽象出一个进程节点类,保存必须的信息
- class CProcNode
- {
- public:
- void Update(const CProcNode &node);//更新进程信息
- bool MayBeDead( );//判断是否死掉或者阻塞
- CProcNode(const CProcNode &node);//构造函数,复制一份
- void Stop( );//停止进程
- void Start( );//开始运行
- void SetPID(unsigned int uId); //设置PID
- void SetCurrentTime(long iTime);//设置当前的时间
- bool Restart( );// 重新启动进程
- void SetProgram( const char * szProg); //设置程序名
- void SetIP(const char * szIP);// 设置这个进程所在客户机的IP地址
- void SetTimeOut(int nRestartTimeOut); // 设置进程重启的时间限额
- void Register(const char * szMsg); //通过消息来注册
- CProcNode();
- bool operator ==(const CProcNode & Node);//相等的判断
- void show();//打印进程信息
- char * GetPath();//获取路径
- virtual ~CProcNode();
- private:
- long m_lRegisterTime;//注册的时间
- long m_lCurrentTime;//当前时间
- char m_szIP[IP_SIZE];//IP地址
- unsigned int m_uPid;//PID
- int m_nRestartTimeOut;//重启时限
- char m_szProgram[MAX_PROGRAM_NAME];//程序名
- char m_szParameter[MAX_PARAMETER][PARAM_LENGTH];//程序运行参数
- int m_nArgc;//参数个数
- };
复制代码
为了保持各个被维护进程与进程维护服务器的畅通通讯,我们通过心跳信息来让被维护进程通知进程维护器这个被维护的进程依然运行着,在消息里面保存了进程最新的路径,参数,IP地址,还有最新的时间戳.每当进程维护器收到一个消息,如果是一个没有注册进程的消息,它便会将这个进程节点注册为一个新的维护节点,如果是已经注册过进程发来的消息,则更新已注册进程的信息.所以在CProcNode类中有几个特别的属性和方法,如重启时限和更新函数等.
当然,要对好多进程进行管理,就需要一个进程列表,对我来说,这个概念主要受启发于<<Linux内核设计与实现>>中下半部机制中的工作队列的数据结构.
- class CProNodeList
- {
- public:
- int RestartDeadProcs( );//重启所有超时进程,在收到一个更新时间消息时,进程维护服务器会在这个操作中检测每个进程的时差,判断它是否死掉,如果死掉,就重启这个进程
- int RegisterHost( const CProcNode & node);//注册一个进程到进程列表
- CProcNode* Found(const CProcNode & Node);//查找进程节点是否已经在队列中
- int GetSize();//获取进程列表大小
- void show();//打印信息
- bool MayRegMoreNodes();//能否接受更多的注册,一开始我们为进程队列分配有限的容量(比如,最多容纳100个注册,这样就需要检测是否有空间接收更多注册)
- void Enlarge(int);//如果进程注册数目已经超过上限,则扩大容量
- bool UnRegisterHost(CProcNode & node);//注销一个进程节点,简单的从列表中删除这个节点
- CProcNode ** m_List;;//进程节点列表
- int m_nLength;//列表长度
- };
复制代码
由于使用UDP的socket方式进行通讯,而且有不同的消息,所以我们规定每个message的第一个字段都写入消息类型,在本文中暂时只分两种消息类型,时间更新消息和心跳消息,时间更新消息就是进程维护服务器启动的另外一个进程每秒发送出的时间消息,心跳消息包括了已注册和未注册进程发送的消息.
- int GetMsgType(const char * szMsg);//收到一个消息,进程维护服务器首先分析出这个消息的类型
- int HandleMessage_HeartBeat(const char *,CProNodeList &);//处理被维护进程发送的心跳消息.
- int HandleMessage_Timer(const char*,CProNodeList &);//处理本地子进程发送的更新时间消息(一秒一次)
复制代码
作为进程维护服务器要做的事情就很少了
- ..........main()
- //创建一个子进程接收所有的消息并处理,通过run函数来实现
- if(fork() == 0)
- {
- return run(argc, argv);
- }else
- {
- //再创建一个子进程向run启动的进程发送时间更新消息,在Timer中实现
- if(fork() == 0)
- {
- return timer();
- }
- ...........
复制代码
- ..int run(int argc, char *argv[])..........
- CSimpleUDP udp;//发送消息的UDP类
- CProNodeList List;//进程列表
- udp.InitRcvSock(m_nPort);
- printf("Message handler staring.....\n");
- while(1)//读取消息
- {
- int nSize = 0;
- char * szMsg = NULL;
- szMsg = udp.RcvMsg(nSize);
- int nType = GetMsgType(szMsg);//获取消息类型
-
- switch(nType)
- {
- case MESSAGE_TYPE_HEART_BEAT://注册进程发送的时间更新消息
- HandleMessage_HeartBeat(szMsg, List);
- //List.show();
- break;
- case MESSAGE_TYPE_TIMER://Timer消息,判断每个进程是超时
- HandleMessage_Timer(szMsg, List);
- break;
- default:
- printf("Unkown Message %s\n", szMsg);
- break;
- }
- ....
复制代码
- 每隔一秒时间发送一个时间戳消息给进程维护服务器
- int timer(void)
- {
- CSimpleUDP udp;
- udp.InitSendSock(LOCAL_IP, m_nPort);
- char szMsg[256];
- printf("Timer starting.......\n");
- while(1)
- {
- memset(szMsg, 0, 256);
- sprintf(szMsg, "%d %d",
- MESSAGE_TYPE_TIMER, CURRENT_TIME_LONG());
- udp.SendMsg(szMsg, 256);
- sleep(1);
- }
- }
复制代码
附录:例子代码(在AS4上编译通过并运行,进行测试)
A:文件结构说明
constdefs.h 定义了所有公用的常量和一些函数
MsgHandle.h & MsgHandle.cpp 定义了消息处理的两个函数
ProcNode.h & ProcNode.cpp 是进程节点的定义与实现
ProNodeList.h & ProNodeList.cpp 定义并实现进程节点队列
SimpleUDP.h & SimpleUDP.cpp封装了简单的多平台UDP通讯类,是一个比较实用的类
ProcWatchServer.cpp 进程维护服务器的实现
TestProc1.cpp 实现了一个注册的进程
你也可以自己再添加其它的注册进程
实现TestProc2....TestProcn,只是进程的创建尽量符合TestProc1的风格,在启动时通过
if(fork() == 0)
{
//执行这个进程的任务
}
的方式来启动
另外还有Makefile.
目前对于此种的方法的优缺点,我也正在进一步的分析,不过测试的例子还是运行的不错,你可以在TestProc1注册成功后,通过Ps看到它的PID,然后手动kill掉TestProc1,过一会,它又会被重新启动,欢迎大家提出意见和自己的更新更好的想法
[ 本帖最后由 duanjigang 于 2006-10-23 21:16 编辑 ] |
|