博主这一段时间都没有更新博客,因为去抄 写了几个小的项目。接下来的几篇博客就当作是项目的总结吧。其中一个项目就是来自github的C++11版本WebServer。我实现的则放到了gitee上my_webserver 。
该项目应该也是吸取了最经典的TinyWebServer,主要由以下几个模块组成:
接下来我会逐一分析markparticle的C++11版本的这些模块。在开始此系列博客之前,强烈推荐大家阅读游双大佬的《Linux高性能服务器编程》,关于这些模块的条条杠杠,不敢说100%,一大半的基础知识都来自这本书。
服务器中的定时器和我们日常生活中的定时器概念一样,**即将一个事件与一个时间点绑定,时间点一到,就执行该事件。**比如,我想明天8点起床,就定了一个8点的定时器。当闹钟一响,我就会执行起床这个事件。那么,在编程中,时间点很容易表示,如何刻画一个事件呢?没错,就是函数。
void get_up(){}
这样就能在特定的时间点,执行特定的事件。
当然了,基于不同的任务,我们的时间点设置也会不同,也可能会有半小时以后,2天以后这样的相对时间点,也可能会有绝对时间点。
服务器往往需要很多个定时任务,这时候就需要一种数据结构管理它们,这就是服务器需要时间管理器的原因。
如果有很多个定时任务,应该怎样管理它们呢?
显然,我们需要基于它们触发的时间先后进行排序,排在前面的事件先触发。而触发的规则又有不同。比如,可以让时间管理器按照一定的周期进行触发,每隔5s触发一次之类的。但是,这样的坏处就是,可能每次触发不一定有事件就绪,白白触发。基于这样一种考虑,我们设法获得下一次触发任务到现在的时间t,然后让时间管理器经过t之后去触发任务,此时至少有一个任务会被触发。
作者实现的时间管理器使用的是堆。利用堆的性质,每次可以选取出最值的特点,每次选择时间节点最小的定时器出来执行,以此往复。
而关于时间方面,使用的是C++11引入的chrono库。
typedef std::function<void()> TimeoutCallBack; //回调函数
typedef std::chrono::high_resolution_clock Clock; //时钟
typedef std::chrono::milliseconds MS; //毫秒
typedef Clock::time_point TimeStamp; //时间戳
struct TimerNode{ //时间节点
int id;
TimeStamp expires; //绝对时间
TimeoutCallBack cb;
bool operator<(const TimerNode& rhs){ //用于比较
return expires < rhs.expires;
}
};
这里,先typedef了一些类型用于后续方便使用。也定义了时间结点。你可能会注意到,回调函数的类型是function
我们默认回调函数没有返回值。因为回调函数可能带有任意类型的参数,所以干脆将其变成没有参数的,如果你的回调函数带有参数,你需要自己使用bind或者lambda进行封装。这个手法经常使用。
对于时间堆,我们采用数组形式。在插入,调整,删除时间堆时,往往需要获得它们的下标,所以使用哈希存储时间节点id到数组下标的映射关系。
class Timer{
public:
//...
private:
std::vector<TimerNode> heap_;
std::unordered_map<int, int> ref_; //id -> index
};
而对于堆,自然要提供向下调整和向上调整的函数。而为了更好的调整ref_,手写了一个swap函数。
//向下调整法,在[index, n)中调整。不包括n!!!
bool Timer::SiftDown_(int index, size_t n);
//向上调整,原作者的size_t类型的index,可能在这里会有一个bug。
void Timer::SiftUp_(int index);
//交换下标i,j处的值,并调整ref_的映射。
void Timer::swap(int i, int j);
//增加定时器,如果定时器已经存在,则用new_expires和cb去更新该定时器。
void add(int id, int new_expires, const TimeoutCallBack& cb);
//调整定时器的触发时间
void adjust(int id, int timeout);
//删除堆顶节点
void pop();
//删除位置为index的节点。私有函数,不会被外部调用。
void del_(int index);
void worker(int id);
void tick(); //心跳函数
int GetNextTick();