那谁的技术博客

感兴趣领域:高性能服务器编程,存储,算法,Linux内核
随笔 - 210, 文章 - 0, 评论 - 1183, 引用 - 0
数据加载中……

服务器公共库开发--定时器管理器模块

在写定时器管理器时,参考了几篇博文:
1)【原创】技术系列之 定时器(一)
2) libevent 源码分析:min_heap带来的超时机制

最后决定采用<<用TCP/IP进行网际互连>>第二卷中讲述TCP定时器一章中的策略, 使用一个定时器链表, 根据定时器触发的时间的升序排列, 每个定时器中有一个字段存放距离链表中上一个定时器触发之后的相对时间值.这样, 每次定时器到时的时候, 都去查询定时器链表的第一个节点, 如果没有到触发时间就直接返回;否则就依次遍历该链表, 查看哪些定时器是可以触发的, 触发定时器事件之后将这些定时器从链表中取出, 然后重新插入到定时器链表中, 并且更新相对时间字段.这个实现想法很简单,但是实现的时候有不少细节需要考虑,我折腾了两天在调试!!-_-.

另外,这个定时器管理器也是一个单件.

我的这个实现还是有局限性的:
1) 每个定时器有一个ID, 定时器与ID一一对应, 在定时器中有一个字段保存当前分配的ID计数, 每新增一个定时器就加一, 但是删除定时器却不回收.这个字段是int型, 我想应该没有哪个系统会加入这么多定时器以至于溢出吧.
2) 采用setitimer进行定时, 每次触发生成ALARM信号, 这要求在使用这个定时器的时候注意处理被信号中断的情况(比如收发网络数据时).在参考的第一篇博文中, 采用的是生成一个新的线程, 专门处理定时操作, 使用的是select函数, 但是我认为如果这样的话, 那么就用在代码中注意多线程安全,这对于我这种基本不写多线程服务器的人来说是个噩梦....
3)目前只做到精确到秒,不过,对我来说也够用了.

timermanager.h:
/********************************************************************
    created:    2008/08/10
    filename:     timermanager.h
    author:        Lichuang
                
    purpose:    定时器管理器
********************************************************************
*/

#ifndef __TIMER_MANAGER_H__
#define __TIMER_MANAGER_H__

#include 
"singleton.h"
#include 
"threadmutex.h"
#include 
<map>
#include 
<list>
#include 
<signal.h>

using namespace std;

enum ETimeEventType
{
    TIMER_ONCE 
= 0,             //一次型
    TIMER_CIRCLE                //循环型
};

struct TTimeEvent
{
    
int nId;                    // 定时器ID
    int nInterval;              // 定时器时间间隔(秒)
    int nSecondsLeft;           // 相对定时器链表中前一个节点触发后的相对时间(秒)
    ETimeEventType nType;       // 事件类型
    void (*pFn)(void* pArg);    // 回调函数
    void* pArg;                 // 回调函数参数
};

class CTimerManager
    : 
public CSingleton<CTimerManager>
{
public:
    
int Init(int nInterval);

    
int AddTimer(TTimeEvent& tTimeEvent);
    
int DelTimer(int nId);

    
void PrintTimerList();

    
int Start();
    
int Stop();

    
int Process();
private:
    CTimerManager();
    
virtual ~CTimerManager();

private:
    
int SetTimer(sighandler_t pFn);
    
int AddTimer(list<TTimeEvent>::iterator& tPos, TTimeEvent& tTimeEvent, bool bIsNewEvent);

private:
    
int m_nInterval;        
    unsigned 
int m_nIdCount;
    list
<TTimeEvent> m_lEvent;

    DECLARE_SINGLETON_CLASS(CTimerManager)    
};

#endif /* __TIMER_MANAGER_H__ */



timermanager.cpp:
/********************************************************************
    created:    2008/08/10
    filename:     timermanager.cpp
    author:        Lichuang
                
    purpose:    定时器管理器
********************************************************************
*/

#include 
"timermanager.h"
#include 
<sys/time.h>

static void Process(int nSigNo);

CTimerManager::CTimerManager()
    : m_nInterval(
0)
    , m_nIdCount(
0)      
{
}

CTimerManager::
~CTimerManager()
{
}

int CTimerManager::AddTimer(TTimeEvent& tTimeEvent)
{
    list
<TTimeEvent>::iterator Iter1 = m_lEvent.begin();
    
return AddTimer(Iter1, tTimeEvent, true);
}

int CTimerManager::DelTimer(int nId)
{
    list
<TTimeEvent>::iterator Iter1 = m_lEvent.begin(), Iter2 = m_lEvent.end();
    
for (; Iter1 != Iter2; ++Iter1)
    {
        
if (Iter1->nId == nId)
        {
            
break;
        }
    }
    
if (Iter1 == Iter2)
    {
        
return -1;
    }

    
int nSecondsLeft = Iter1->nSecondsLeft;
    Iter2 
= Iter1;
    
++Iter2;
    m_lEvent.erase(Iter1);
    
// 删除一个定时器还要更新下一个节点(如果存在的话)的相对时间
    if (m_lEvent.end() != Iter2)
    {
        Iter2
->nSecondsLeft += nSecondsLeft;
    }

    
return 0;
}

void CTimerManager::PrintTimerList()
{
    list
<TTimeEvent>::iterator Iter1 = m_lEvent.begin(), Iter2 = m_lEvent.end();
    
for (; Iter1 != Iter2; ++Iter1)
    {
        printf(
"id = %d, interval = %d, secondleft = %d\n", Iter1->nId, Iter1->nInterval, Iter1->nSecondsLeft);
    }
}

int CTimerManager::Start()
{
    
return SetTimer(::Process);
}

int CTimerManager::Stop()
{
    m_nInterval 
= 0;
    
return SetTimer(SIG_DFL);
}

int CTimerManager::Init(int nInterval)
{
    m_nInterval 
= nInterval;

    
return 0;
}

int CTimerManager::SetTimer(sighandler_t pFn)
{
    
struct itimerval interval;
        
    interval.it_interval.tv_sec 
= m_nInterval;
    interval.it_interval.tv_usec 
= 0;
    interval.it_value.tv_sec 
= m_nInterval;
    interval.it_value.tv_usec 
= 0;

    
if (0 != ::setitimer(ITIMER_REAL, &interval, NULL)) 
    {
        
return -1;
    }    

    
if (0 != ::signal(SIGALRM, pFn))
    {
        
return -1;
    }

    
return 0;
}

int CTimerManager::AddTimer(list<TTimeEvent>::iterator& tPos, TTimeEvent& tTimeEvent, bool bIsNewEvent)
{
    
// 根据触发时间的升序排列, 将定时器放在链表的合适位置
    list<TTimeEvent>::iterator Iter1 = tPos, Iter2 = m_lEvent.end();
    
int nSecondsLeft = 0;
    
for (; Iter1 != Iter2; nSecondsLeft += Iter1->nSecondsLeft, ++Iter1)
    {
        
if (Iter1->nSecondsLeft + nSecondsLeft > tTimeEvent.nInterval)
        {
            
break;
        }
    }

    tTimeEvent.nSecondsLeft 
= tTimeEvent.nInterval - nSecondsLeft;
    
// 如果插入节点不是定时器链表的最后一个节点, 那么要修改它下一个节点的相对时间值
    if (Iter1 != Iter2)
    {
        Iter1
->nSecondsLeft = Iter1->nSecondsLeft - tTimeEvent.nSecondsLeft;
    }
    
if (bIsNewEvent)
    {
        tTimeEvent.nId 
= m_nIdCount++;
    }

    m_lEvent.insert(Iter1, tTimeEvent);

    
return tTimeEvent.nId;
}

int CTimerManager::Process()
{
    
// 如果定时器链表是空的, 立即返回
    if (m_lEvent.empty())
    {
        
return 0;
    }

    
// 如果定时器链表的头结点没有到该触发的时间, 更新已经过去的时间计数器后返回
    list<TTimeEvent>::iterator Iter1 = m_lEvent.begin();
    Iter1
->nSecondsLeft -= m_nInterval;
    
if (0 < Iter1->nSecondsLeft)
    {
        
return 0;
    }

    
// 定时器链表的头结点已经可以触发, 从头结点开始遍历链表, 看看哪些结点也可以触发事件的
    list<TTimeEvent>::iterator Iter2 = m_lEvent.end();
    list
<TTimeEvent> lEvent;
    
while (Iter1 != Iter2)
    {
        
if (0 >= Iter1->nSecondsLeft)
        {
            
// 触发定时事件
            Iter1->pFn(Iter1->pArg);
            
if (TIMER_CIRCLE == Iter1->nType)
            {
                lEvent.push_back(
*Iter1);
            }
            
++Iter1;
            m_lEvent.pop_front();
        }
        
else
        {
            
break;
        }
    }

    
// 将本次触发的定时器事件重新插入到定时器链表中
    
// 这里有一个优化策略: nLastInterval 保存上一个重新插入到定时器链表中的时间间隔
    
// 如果这个值比当前节点的时间间隔小, 那么从上一个插入的位置开始搜索
    
// 否则从定时器节点的开始位置搜索
    int nLastInterval = 0;
    list
<TTimeEvent>::iterator Iter3;
    
for (Iter1 = lEvent.begin(), Iter2 = lEvent.end(); Iter1 != Iter2; nLastInterval = Iter1->nInterval, ++Iter1)
    {
        
if (0 == nLastInterval || nLastInterval > Iter1->nInterval)
        {
            Iter3 
= m_lEvent.begin();
        }

        AddTimer(Iter3, 
*Iter1, false);
    }

    
return 0;
}

void Process(int nSigNo)
{
    
if (nSigNo == SIGALRM)
    {
        CTimerManager::GetInstance()
->Process();
    }
}



posted on 2008-08-13 15:36 那谁 阅读(2927) 评论(2)  编辑 收藏 引用 所属分类: 服务器设计Linux/Unix

评论

# re: 服务器公共库开发--定时器管理器模块  回复  更多评论   

比较猥琐.. 你参考一下ACE吧. perfect

你要做服务器编程.不可能总是避讳多线程环境. 尝试一下select吧.
2008-08-14 09:43 | bugs_killer

# re: 服务器公共库开发--定时器管理器模块[未登录]  回复  更多评论   

@bugs_killer
select我当然有用,只是不用多线程罢了.我写的服务器都是多进程+多路复用IO的模式.

2008-08-14 10:21 |

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理