muduo 是一个基于 Reactor 模式的现代 C++ 网络库,作者陈硕。它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程网络应用程序。
muduo网络库的核心代码只有数千行,在网络编程技术学习的进阶阶段,muduo是一个非常值得学习的开源库。目前我也是刚刚开始学习这个网络库的源码,希望将这个学习过程记录下来。这个网络库的源码已经发布在GitHub上,可以点击这里阅读。目前Github上这份源码已经被作者用c++11重写,我学习的版本是没有使用c++11版本的。不过二者大同小异,核心思想是没有变化的。点这里可以看我的源代码。从笔记十七开始记录muduo的net库的实现过程。如果你需要看一下基础库(base)的复现过程,可以点击这里:muduo的base库实现过程。而网络库的笔记在这里:
muduo网络库源码复现笔记(十七):什么都不做的EventLoop
muduo网络库源码复现笔记(十八):Reactor的关键结构
前面我们实现了一个初步的Reactor结构,这一节将加上定时器功能。举例来说,下面的代码实现了下面这样一个功能:定时10s之后执行task函数,和每隔2秒后执行task2函数。
EventLoop loop;
loop.runAfter(10,boost::bind(task,"once"));
loop.runAfter(2,boost::bind(task2,"every 2"));
loop.loop()
为了实现这样一个功能,需要TimerId,Timer,TimerQueue三个类。其中只有TimerId是对用户可见的。下面讲解一下这三个类。
TimeId不难,它只有两个属性,一个是它对应的定时器,一个是定时器的序号。
class TimerId : public muduo::copyable
{
public:
TimerId()
: timer_(NULL),
sequence_(0)
{
}
TimerId(Timer* timer,int64_t seq)
: timer_(timer),
sequence_(seq)
{
}
friend class TimerQueue;
private:
Timer* timer_;
int64_t sequence_;
};
Timer类是定时器类,它的相关属性均在注释中。我们将使用run函数来执行callback任务。在TimerQueue中我们将每个定时器绑定到定时器文件描述符timerfd上,到时的定时器会被拿出执行任务。
Timer(const TimerCallback& cb,Timestamp when,double interval)
: callback_(cb),
expiration_(when),
interval_(interval),
repeat_(interval > 0),
sequence_(s_numCreated_.incrementAndGet())
{
}
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_; //要执行的任务
Timestamp expiration_; //执行时间
const double interval_; //重复间隔
const bool repeat_; //是否需要重复
const int64_t sequence_; //序号
static AtomicInt64 s_numCreated_; //定时器数目统计
};
TimerQueue是实现定时功能的重点,它可以加入与去除定时器、取出到时的定时器。注意TimerQueue两个成员timerfd_和timerfdChannel_。前者是一个时间文件描述符,后者是一个Channel,它的作用是监听定时器的到时时刻,在TimerQueue初始化时加入Poller的Channels_。定时器所在的容器是TimerList和ActiveTimerSet,二者大致相似的。都是一个set,但是前者的元素类型是pair
class TimerQueue : boost::noncopyable
{
public:
TimerQueue(EventLoop* loop);
~TimerQueue();
TimerId addTimer(const TimerCallback& cb,
Timestamp when,
double interval);
void cancel(TimerId timerId);
private:
typedef std::pair<Timestamp,Timer*> Entry;
typedef std::set<Entry> TimerList;
typedef std::pair<Timer*,int64_t> ActiveTimer;
typedef std::set<ActiveTimer> ActiveTimerSet;
void addTimerInLoop(Timer* timer);
void cancelInLoop(TimerId timerId);
//called when timerfd alarms
void handleRead();
std::vector<Entry> getExpired(Timestamp now);
void reset(const std::vector<Entry>& expired,Timestamp now);
bool insert(Timer* timer);
EventLoop* loop_;
const int timerfd_;
Channel timerfdChannel_;
TimerList timers_; //timers sorted by time
ActiveTimerSet activeTimers_;
bool callingExpiredTimers_;
ActiveTimerSet cancelingTimers_;
};
getExpired用于取出到时的定时器,并加入TimerList和ActiveTimerSet。这个函数需要关注的地方是sentry这个哨兵。它的pair对的first是now,second是转换为Timer指针的最大内存地址。这样确保我们在使用low_bound时得到的end的first总是大于now。
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));
TimerList::iterator end = timers_.lower_bound(sentry);
assert(end == timers_.end() || now < end->first);
std::copy(timers_.begin(),end,back_inserter(expired));
timers_.erase(timers_.begin(),end);
for(std::vector<TimerQueue::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;
}
时序图如下,当我们调用loop函数时,得到activeChannels,然后处理各Channel,对于定时器Channel,拿到到时的定时器然后执行它所携带的任务函数。