muduo网络库定时器的实现

来源:互联网 发布:ubuntu 退出用户登录 编辑:程序博客网 时间:2024/06/05 02:36

一:函数介绍

常见的与时间相关的函数有:sleep,alarm,usleep,nanosleep,clock_nanosleep,gettimer/settitimer,timer_create/timer_settime/timer_gettime/timer_delete,还有muduo使用的timerfd_create/timerfd_gettime/timerfd_settime函数。

为什么选择timerfd_*函数呢?

>>>sleep/alarm/usleep在实现时有可能用了信号SIGALRM,在多线程程序中处理信号是个相当麻烦的事情,应该尽量避免。

>>>nanosleep和clock_nanosleep是线程安全的,但是在非阻塞网络编程中,绝对不能用让线程挂起的方式来等待一段时间。程序会失去响应,正确的做法是注册一个时间回调函数。

>>>getitimer和timer_create也是用信号来deliver超时,在多线程程序中也会有麻烦。

>>>timer_create可以指定信号的接收方是进程还是线程,算是一个进步,不过在信号处理函数(signal handler)中能做的事情实在是很受限。

>>>timerfd_create把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便的融入到select/poll框架中,用统一的方式来处理I/O和超时事件,这正是Reactor模式的长处。实际上如果非要处理信号的话,使用signalfd将信号转变成文件描述符处理。

比较:libevent定时器采用本地unix域通信,当时间最小堆中超时事件发生后,写一个字节给本地socket,使得描述符可读,epoll检测到并处理该事件。


首先看timerfd_create()函数:

 int timerfd_create(int clockid, int flags);


1.参数1的clockid是指,CLOCK_REALTIME or CLOCK_MONOTONIC时间。前者是我们平时所看到的时间,后者是石英钟时间也就是晶振决定的时间。所以假如我们制订了3分钟的定时,如果我们直接修改时间到三分钟后,那么前者会触发,而后者不会触发,它是晶振决定的相对时间,不取决于外部更改。所以一般定时器采用后者

2.参数2有两个选项TFD_NONBLOCK和TFD_CLOEXEC,这个就不用解释了。


第二个函数是timerfd_settime()函数

  int timerfd_settime(int fd, int flags,                           const struct itimerspec *new_value,                           struct itimerspec *old_value);
1.参数1是文件描述符。

2.参数2是参加timerfd_create()函数。

3.参数3是新值,itermerspec结构体有两个字段,解释见下面。

4.参数4是旧值,结构体见下面。

           struct timespec { 
               time_t tv_sec;                /* Seconds */ 
               long   tv_nsec;               /* Nanoseconds */ 
           }; 


           struct itimerspec { 
               struct timespec it_interval;  /* Interval for periodic timer */   //定时间隔,如果只发生一次,可设置为0 
               struct timespec it_value;     /* Initial expiration */  //最初的过期值 
           }; 


       new_value.it_value  specifies  the  initial  expiration  of the timer, in seconds and nanoseconds.  Setting either field of

       new_value.it_value to a nonzero value arms the timer.  Setting both fields of new_value.it_value to zero disarms the timer.


二:定时器

为什么网络编程中需要定时器呢? 

在开发Linux网络程序时,通常需要维护多个定时器,如维护客户端心跳时间、检查多个数据包的超时重传等。如果采用Linux的SIGALARM信号实现,则会带来较大的系统开销,且不便于管理。 

Muduo 的 TimerQueue 采用了最简单的实现(链表)来管理定时器,它的效率比不上常见的 binary heap 的做法,如果程序中大量(10 个以上)使用重复触发的定时器,或许值得考虑改用更高级的实现。由于目前还没有在一个程序里用过这么多定时器,暂时也不需要优化 TimerQueue。 

mudo的定时器由三个类实现,TimerId,Timer,TimerQueue,用户只能看到第一个类,其它两个类都是内部实现细节。

(1)TimerId被设计用来取消Timer的,它的结构很简单,只有一个Timer指针和其序列号。其中还声明了TimerQueue为其友元,可以操作其私有数据。

它的实现如下:

class TimerId : public muduo::copyable{ public:  TimerId()    : timer_(NULL),      sequence_(0)  {  }  TimerId(Timer* timer, int64_t seq)    : timer_(timer),      sequence_(seq)  {  }  // default copy-ctor, dtor and assignment are okay  friend class TimerQueue; private:  Timer* timer_;  int64_t sequence_;};


(2)Timer类

Timer是对定时器的高层次抽象,封装了定时器的一些参数,例如超时回调函数、超时时间、超时时间间隔、定时器是否重复、定时器的序列号。其函数大都是设置这些参数,run()用来调用回调函数,restart()用来重启定时器(如果设置为重复)。

主要实现:

class Timer : boost::noncopyable{ public:  Timer(const TimerCallback& cb, Timestamp when, double interval)    : callback_(cb),      expiration_(when),      interval_(interval),      repeat_(interval > 0.0),   //如果大于0就重复      sequence_(s_numCreated_.incrementAndGet())  //先加后获取,由于初始值s_numCreated为0,所以序号这里从1开始  { }#ifdef __GXX_EXPERIMENTAL_CXX0X__  Timer(TimerCallback&& cb, Timestamp when, double interval)    : callback_(std::move(cb)),      expiration_(when),      interval_(interval),      repeat_(interval > 0.0),      sequence_(s_numCreated_.incrementAndGet())  { }#endif  void run() const  //调用回调函数  {    callback_();  }  Timestamp expiration() const  { return expiration_; }  bool repeat() const { return repeat_; }  int64_t sequence() const { return sequence_; }  void restart(Timestamp now);  static int64_t numCreated() { return s_numCreated_.get(); } private:  const TimerCallback callback_;  //定时器回调函数,TimerCallback在callback.h文件中定义,类型void()  Timestamp expiration_;   //下一次的超时时刻  const double interval_;     //超时时间间隔,如果是一次定时器,该值为0  const bool repeat_;          //是否重复  const int64_t sequence_;     //定时器序号  static AtomicInt64 s_numCreated_;   //定时器计数,当前已创建的定时器数量,原子int64_t类型,初始值为0};
#include <muduo/net/Timer.h>using namespace muduo;using namespace muduo::net;AtomicInt64 Timer::s_numCreated_;void Timer::restart(Timestamp now)   //重启{  if (repeat_)   //如果是重复的,那么就从当前时间计算下一次的超时时刻  {    expiration_ = addTime(now, interval_);  //当前时间加上时间间隔?  }  else  {    expiration_ = Timestamp::invalid();   //获取一个无效事件戳,即值为0  }}

实际上最重要的是接下来要介绍的这个类。

(3)TimerQueue数据结构的选择 

TimerQueue的接口很简单,只有两个函数addTimer()和cancel()。它的内部有channel,和timerfd相关联。添加新的Timer后,在超时后,timerfd可读,会处理channel事件,之后调用Timer的回调函数;在timerfd的事件处理后,还有检查一遍超时定时器,如果其属性为重复还有再次添加到定时器集合中。

时序图:


TimerQueue需要高效地组织目前尚未到期的Timer,能快速地根据当前时间找到已经到期的Timer,也要能高效地添加和删除Timer。因而可以用二叉搜索树(例如std::set/std::map),把Timer按到期时间先后排好序,其操作的复杂度是O(logN),但我们使用时还要处理两个Timer到期时间相同的情况(map不支持key相同的情况),做法如下: 
//两种类型的set,一种按时间戳排序,一种按Timer的地址排序 
//实际上,这两个set保存的是相同的定时器列表 

typedef std::pair<Timestamp, Timer*> Entry;typedef std::set<Entry> TimerList;typedef std::pair<Timer*, int64_t> ActiveTimer;typedef std::set<ActiveTimer> ActiveTimerSet;看它的头文件:class TimerQueue : boost::noncopyable{ public:  TimerQueue(EventLoop* loop);  ~TimerQueue();  ///  /// Schedules the callback to be run at given time,  /// repeats if @c interval > 0.0.  ///  /// Must be thread safe. Usually be called from other threads.  //一定是线程安全的,可以跨线程调用。通常情况下被其他线程调用  TimerId addTimer(const TimerCallback& cb,                   Timestamp when,                   double interval);#ifdef __GXX_EXPERIMENTAL_CXX0X__  TimerId addTimer(TimerCallback&& cb,                   Timestamp when,                   double interval);#endif  void cancel(TimerId timerId);  //可以跨线程调用 private:  // FIXME: use unique_ptr<Timer> instead of raw pointers.  //下面两个set可以说保存的是相同的东西,都是定时器,只不过排序方式不同  typedef std::pair<Timestamp, Timer*> Entry;  //set的key,是一个时间戳和定时器地址的pair  typedef std::set<Entry> TimerList;    //按照时间戳排序  typedef std::pair<Timer*, int64_t> ActiveTimer;  //定时器地址和序号  typedef std::set<ActiveTimer> ActiveTimerSet;  //按照定时器地址排序  //以下成员函数只可能在其所属的I/O线程中调用,因而不必加锁  //服务器性能杀手之一就是锁竞争,要尽可能少使用锁  void addTimerInLoop(Timer* timer);  void cancelInLoop(TimerId timerId);  // called when timerfd alarms  void handleRead();  //定时器事件产生回调函数  // move out all expired timers  std::vector<Entry> getExpired(Timestamp now);  //返回超时的定时器列表  void reset(const std::vector<Entry>& expired, Timestamp now);   //对超时的定时器进行重置,因为超时的定时器可能是重复的定时器  bool insert(Timer* timer);  //插入定时器  EventLoop* loop_;    //所属的event_loop  const int timerfd_;    //就是timefd_create()所创建的定时器描述符?  Channel timerfdChannel_;   //这是定时器事件的通道  // Timer list sorted by expiration  TimerList timers_;   //定时器set,按时间戳排序  // for cancel()  ActiveTimerSet activeTimers_;  //活跃定时器列表,按定时器地址排序  bool callingExpiredTimers_; /* atomic */  //是否处于调用处理超时定时器当中  ActiveTimerSet cancelingTimers_;    //保存的是被取消的定时器};


#ifndef __STDC_LIMIT_MACROS#define __STDC_LIMIT_MACROS#endif#include <muduo/net/TimerQueue.h>#include <muduo/base/Logging.h>#include <muduo/net/EventLoop.h>#include <muduo/net/Timer.h>#include <muduo/net/TimerId.h>#include <boost/bind.hpp>#include <sys/timerfd.h>namespace muduo{namespace net{namespace detail{int createTimerfd(){  int timerfd = ::timerfd_create(CLOCK_MONOTONIC,                                 TFD_NONBLOCK | TFD_CLOEXEC);  if (timerfd < 0)  {    LOG_SYSFATAL << "Failed in timerfd_create";  }  return timerfd;}//计算超时时刻与当前时间的时间差struct timespec howMuchTimeFromNow(Timestamp when){  int64_t microseconds = when.microSecondsSinceEpoch()                         - Timestamp::now().microSecondsSinceEpoch();  //超时时刻微秒数-当前时间微秒数  if (microseconds < 100)  //不能小于100,精确度不需要  {    microseconds = 100;  }  struct timespec ts;  //转换成这个结构体返回  ts.tv_sec = static_cast<time_t>(      microseconds / Timestamp::kMicroSecondsPerSecond);  ts.tv_nsec = static_cast<long>(      (microseconds % Timestamp::kMicroSecondsPerSecond) * 1000);  return ts;}//从timerfd读取,避免定时器事件一直触发void readTimerfd(int timerfd, Timestamp now){  uint64_t howmany;  ssize_t n = ::read(timerfd, &howmany, sizeof howmany);  //从timerfd读取4个字节,这样timerfd就不会一直触发了    LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString();  if (n != sizeof howmany)  {    LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8";  }}//重置定时器超时时刻void resetTimerfd(int timerfd, Timestamp expiration)  {  // wake up loop by timerfd_settime()  struct itimerspec newValue;     struct itimerspec oldValue;  bzero(&newValue, sizeof newValue);  bzero(&oldValue, sizeof oldValue);  newValue.it_value = howMuchTimeFromNow(expiration);   //将时间戳类转换成it_value的形式  int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);  //设置进去,到期之后会产生一个定时器事件  if (ret)  {    LOG_SYSERR << "timerfd_settime()";  }}}}}using namespace muduo;using namespace muduo::net;using namespace muduo::net::detail;TimerQueue::TimerQueue(EventLoop* loop)  : loop_(loop),    timerfd_(createTimerfd()),    //创建定时器,调用timerfd_create,返回timerfd    timerfdChannel_(loop, timerfd_),    timers_(),    callingExpiredTimers_(false){/*Channel timerfdChannel_;   //这是定时器事件的通道*///设置定时器类型通道的读回调函数。  timerfdChannel_.setReadCallback(      boost::bind(&TimerQueue::handleRead, this));     // we are always reading the timerfd, we disarm it with timerfd_settime.  timerfdChannel_.enableReading();  //注册,底层是一系列update,你懂的。}TimerQueue::~TimerQueue(){  timerfdChannel_.disableAll();  timerfdChannel_.remove();  ::close(timerfd_);  // do not remove channel, since we're in EventLoop::dtor();  for (TimerList::iterator it = timers_.begin();      it != timers_.end(); ++it)  {    delete it->second;  //析构函数只释放一次,因为两个set保存的是一样的  }}//增加一个定时器TimerId TimerQueue::addTimer(const TimerCallback& cb,                             Timestamp when,                             double interval){  Timer* timer = new Timer(cb, when, interval);   //构造一个定时器对象,interval>0就是重复定时器  loop_->runInLoop(      boost::bind(&TimerQueue::addTimerInLoop, this, timer));  return TimerId(timer, timer->sequence());}#ifdef __GXX_EXPERIMENTAL_CXX0X__TimerId TimerQueue::addTimer(TimerCallback&& cb,                             Timestamp when,                             double interval){  Timer* timer = new Timer(std::move(cb), when, interval);  loop_->runInLoop(      boost::bind(&TimerQueue::addTimerInLoop, this, timer));  return TimerId(timer, timer->sequence());}#endif//执行线程退出的回调函数void TimerQueue::cancel(TimerId timerId){  loop_->runInLoop(      boost::bind(&TimerQueue::cancelInLoop, this, timerId)); }void TimerQueue::addTimerInLoop(Timer* timer){  loop_->assertInLoopThread();  //插入一个定时器游客能会使得最早到期的定时器发生改变,比如当前插入一个最早到期的,那就要重置定时器超时时刻  bool earliestChanged = insert(timer);  if (earliestChanged)  //如果改变了  {    resetTimerfd(timerfd_, timer->expiration());  //重置定时器超时时刻  }}void TimerQueue::cancelInLoop(TimerId timerId){  loop_->assertInLoopThread();  assert(timers_.size() == activeTimers_.size());  ActiveTimer timer(timerId.timer_, timerId.sequence_);  //查找该定时器  ActiveTimerSet::iterator it = activeTimers_.find(timer);  if (it != activeTimers_.end())  {  //删除该定时器    size_t n = timers_.erase(Entry(it->first->expiration(), it->first));    assert(n == 1); (void)n;    //如果用unique_ptr这里就不需要手工删除了    delete it->first; // FIXME: no delete please    activeTimers_.erase(it);  }  else if (callingExpiredTimers_)   //如果在定时器列表中没有找到,可能已经到期,且正在处理的定时器  {    //已经到期,并且正在调用回调函数的定时器    cancelingTimers_.insert(timer);  }  assert(timers_.size() == activeTimers_.size());}//可读事件处理void TimerQueue::handleRead(){  loop_->assertInLoopThread();  //断言I/O线程中调用  Timestamp now(Timestamp::now());  readTimerfd(timerfd_, now);   //清除该事件,避免一直触发,实际上是对timerfd做了read  //获取该时刻之前所有的定时器列表,即超时定时器列表,因为实际上可能有多个定时器超时,存在定时器的时间设定是一样的这种情况  std::vector<Entry> expired = getExpired(now);  callingExpiredTimers_ = true;  //处于处理定时器状态中  cancelingTimers_.clear();    // safe to callback outside critical section  for (std::vector<Entry>::iterator it = expired.begin();      it != expired.end(); ++it)  {    it->second->run();   //调用所有的run()函数,底层调用Timer类的设置了的超时回调函数  }  callingExpiredTimers_ = false;  reset(expired, now);   //如果移除的不是一次性定时器,那么重新启动它们}//返回当前所有超时的定时器列表//返回值由于rvo优化,不会拷贝构造vector,直接返回它std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now){  assert(timers_.size() == activeTimers_.size());  std::vector<Entry> expired;  Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));//创建一个时间戳和定时器地址的集合  //返回第一个未到期的Timer的迭代器  //lower_bound返回第一个值>=sentry的  //即*end>=sentry,从而end->first > now,而不是>=now,因为pair比较的第一个相等后会比较第二个,而sentry的第二个是UINTPTR_MAX最大  //所以用lower_bound没有用upper_bound  TimerList::iterator end = timers_.lower_bound(sentry);  assert(end == timers_.end() || now < end->first);  //now < end->first  std::copy(timers_.begin(), end, back_inserter(expired));  //将到期的定时器插入到expired中  timers_.erase(timers_.begin(), end);  //删除已到期的所有定时器  //从activeTimers_中也要移除到期的定时器  for (std::vector<Entry>::iterator it = expired.begin();      it != expired.end(); ++it)  {    ActiveTimer timer(it->second, it->second->sequence());    size_t n = activeTimers_.erase(timer);    assert(n == 1); (void)n;  }  assert(timers_.size() == activeTimers_.size());  return expired;}void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now){  Timestamp nextExpire;  for (std::vector<Entry>::const_iterator it = expired.begin();      it != expired.end(); ++it)  {    ActiveTimer timer(it->second, it->second->sequence());    //如果是重复的定时器,并且是未取消定时器,则重启该定时器    if (it->second->repeat()        && cancelingTimers_.find(timer) == cancelingTimers_.end())    {      it->second->restart(now);  //restart()函数中会重新计算下一个超时时刻      insert(it->second);    }    else    {    //一次性定时器或者已被取消的定时器是不能重置的,因此删除该定时器      // FIXME move to a free list      delete it->second; // FIXME: no delete please      }  }  if (!timers_.empty())   {    //获取最早到期的超时时间    nextExpire = timers_.begin()->second->expiration();  }  if (nextExpire.valid())  {  //重新设定timerfd的超时时间    resetTimerfd(timerfd_, nextExpire);  }}bool TimerQueue::insert(Timer* timer){  loop_->assertInLoopThread();  assert(timers_.size() == activeTimers_.size());  //这两个存的是同样的定时器列表,成员函数中分析过了  bool earliestChanged = false;  //检测最早到期时间是否改变  Timestamp when = timer->expiration();  TimerList::iterator it = timers_.begin();   //第一个定时器,timers是set实现的,所以第一个就是最早,空的也算  if (it == timers_.end() || when < it->first)  {    earliestChanged = true;  //如果插入定时器时间小于最早到期时间  }  //下面两个插入的set保存的是一样的,都是定时器,只不过对组的另一个辅助成员不一样  {    //利用RAII机制    //插入到timers_中,result是临时对象,需要用它来保证插入成功    std::pair<TimerList::iterator, bool> result      = timers_.insert(Entry(when, timer));    assert(result.second); (void)result;  }  {    //插入到activeTimers中    std::pair<ActiveTimerSet::iterator, bool> result      = activeTimers_.insert(ActiveTimer(timer, timer->sequence()));    assert(result.second); (void)result;  }  assert(timers_.size() == activeTimers_.size());  return earliestChanged;  //返回是否最早到期时间改变}

三:定时器的使用

主要是在EventLoop中使用,EventLoop中为我们提供了四个函数,供用户使用,我们来看一下:


/在时间戳为time的时间执行,0.0表示一次性不重复TimerId EventLoop::runAt(const Timestamp& time, const TimerCallback& cb){  return timerQueue_->addTimer(cb, time, 0.0);  }//延迟delay时间执行的定时器TimerId EventLoop::runAfter(double delay, const TimerCallback& cb){  Timestamp time(addTime(Timestamp::now(), delay));  //合成一个时间戳  return runAt(time, cb);}//间隔性的定时器,起始就是重复定时器,间隔interval需要大于0TimerId EventLoop::runEvery(double interval, const TimerCallback& cb){  Timestamp time(addTime(Timestamp::now(), interval));  return timerQueue_->addTimer(cb, time, interval);}//直接调用timerQueue的canclevoid EventLoop::cancel(TimerId timerId){  return timerQueue_->cancel(timerId);  }而timerQueue是EventLoop类的成员: boost::scoped_ptr<TimerQueue> timerQueue_;所以用户操作这几个函数就可以利用定时器实现相应的功能。



原创粉丝点击