从零开始实现一个C++高性能服务器框架----定时器模块

news/2025/2/12 0:18:36/

此项目是根据sylar框架实现,是从零开始重写sylar,也是对sylar丰富与完善
项目地址:https://gitee.com/lzhiqiang1999/server-framework

简介

项目介绍:实现了一个基于协程的服务器框架,支持多线程、多协程协同调度;支持以异步处理的方式提高服务器性能;封装了网络相关的模块,包括socket、http、servlet等,支持快速搭建HTTP服务器或WebSokcet服务器。
详细内容:日志模块,使用宏实现流式输出,支持同步日志与异步日志、自定义日志格式、日志级别、多日志分离等功能。线程模块,封装pthread相关方法,封装常用的锁包括(信号量,读写锁,自旋锁等)。IO协程调度模块,基于ucontext_t实现非对称协程模型,以线程池的方式实现多线程,多协程协同调度,同时依赖epoll实现了事件监听机制。定时器模块,使用最小堆管理定时器,配合IO协程调度模块可以完成基于协程的定时任务调度。hook模块,将同步的系统调用封装成异步操作(accept, recv, send等),配合IO协程调度能够极大的提升服务器性能。Http模块,封装了sokcet常用方法,支持http协议解析,客户端实现连接池发送请求,服务器端实现servlet模式处理客户端请求,支持单Reator多线程,多Reator多线程模式的服务器。

定时器

  • 采用最小堆设计,所有定时器根据绝对的超时时间点进行排序,每次取出离当前时间最近的一个超时时间点,计算出超时需要等待的时间,然后等待超时。超时时间到后,获取当前的绝对时间点,然后把最小堆里超时时间点小于这个时间点的定时器都收集起来,执行它们的回调函数。
  • 超时等待基于epoll_wait,精度只支持毫秒级,因为epoll_wait的超时精度也只有毫秒级。
  • IO协程调度器继承定时器,当添加一个定时任务(如果添加在了第一个位置,需要tikle一下,通知epoll_wait,这样就会重新设置epoll_wait的超时时间),idle方法中,拿到最先执行的定时器的等待时间(getNextTime),给epoll_wait,这样epoll_wait可以一边等事件,一边等定时器,当时间到了,就会把需要执行(时间已经到了listExpiredCb())的定时器拿出来(如果是循环定时器,就会又放回到定时器管理对象中),加入任务协程队列,有事件触发,将事件处理函数加入任务协程,idle协程暂停(YieldToHoldBySwap())。

1. 主要功能

  • 使用最小堆完成定时器封装
  • 支持循环定时器,执行循环定时任务
  • 支持创建条件定时器,定时器触发与否依赖于绑定的条件
  • 配合IO协程调度模块可以完成基于协程的定时任务调度

2. 功能演示

void test_timer()
{johnsonli::IOManager iom(1);iom.schedule(&test_fiber1);// 1s触发一次s_timer = iom.addTimer(1000, [](){static int i = 0;LOG_INFO(g_logger) << "timer test " << i;if(++i == 3){s_timer->reset(2000, true);	// 3s后设为2s触发一次定时器s_timer->cancel();}}, true);}

3. 模块介绍

3.1 Timer

  • 定时器。主要是封装了定时器绝对时间,相对时间,是否重复执行,定时任务回调函数,以及定时器的相关方法。imer的构造函数被定义成私有方式,只能通过TimerManager类来创建Timer对象。除此外,Timer类还提供了一个仿函数Comparator,用于比较两个Timer对象,比较的依据是绝对超时时间。
class TimerManager;
//定时器类
class Timer : public std::enable_shared_from_this<Timer> 
{friend TimerManager;
public:typedef std::shared_ptr<Timer> ptr;bool cancel();		// 取消定时器bool refresh();		// 刷新设置定时器的执行时间/*** @brief 重置定时器时间* @param[in] ms 定时器执行间隔时间(毫秒)* @param[in] from_now 是否从当前时间开始计算*/bool reset(uint64_t ms, bool from_now);private:Timer(uint64_t ms, std::function<void()> cb, bool recurring, TimerManager* manager);Timer(uint64_t next);private://比较器,给set用,按时间从小到大排序struct Comperator{bool operator()(const Timer::ptr& lhs, const Timer::ptr& rhs) const;};private:bool m_recurring = false;           //是否循环定时器uint64_t m_ms = 0;                  //执行周期,等待的时间,ms后执行uint64_t m_next = 0;                //下一次要执行的时间,当前时间 + m_ms周期std::function<void()> m_cb;         //回调函数TimerManager* m_manager = nullptr;  //当前timer在哪个timer管理对象内};

3.2 TimerManager

  • 基于最小堆的定时器管理类。所有的Timer对象都由TimerManager类进行管理,TimerManager包含一个std::set类型的Timer集合,这个集合就是定时器的最小堆结构,因为set里的元素总是排序过的,所以总是可以很方便地获取到当前的最小定时器。TimerManager提供创建定时器,获取最近一个定时器的超时时间,以及获取全部已经超时的定时器回调函数的方法,并且提供了一个onTimerInsertedAtFront()方法,这是一个虚函数,由IOManager继承时实现,当新的定时器插入到Timer集合的首部时,TimerManager通过该方法来通知IOManager立刻更新当前的epoll_wait超时。
//定时器管理类
class TimerManager
{friend Timer;
public:typedef RWMutex RWMutexType;TimerManager();virtual ~TimerManager();Timer::ptr addTimer(uint64_t ms, std::function<void()> cb,bool recurring = false);//weak_cond:满足条件再执行,智能指针=0时,就不触发Timer::ptr addConditionTimer(uint64_t ms, std::function<void()> cb,std::weak_ptr<void> weak_cond,bool recurring = false);//获取最近一次定时器执行,还需要等待的时间uint64_t getNextTimer();//将那些应该执行,但没有执行的定时器删除,并将其回调方法添加到cbsvoid listExpiredCb(std::vector<std::function<void()>>& cbs);protected:/*** @brief 当有新的定时器插入到定时器的首部,执行该函数*/virtual void onTimerInsertedAtFront() = 0;/*** @brief 将定时器添加到管理器中,如果加在第一个,就会触发onTimerInsertedAtFront*/void addTimer(Timer::ptr val, RWMutexType::WriteLock& lock);private:/*** @brief 检测服务器时间是否被调前了*/bool detectClockRollover(uint64_t now_ms);private:RWMutexType m_mutex;std::set<Timer::ptr, Timer::Comperator> m_timers;		// 最小堆bool m_tickled = false; 								// 是否触发了onTimerInsertedAtFrontuint64_t m_previouseTime = 0;		
};
  • 支持创建条件定时器addConditionTimer。在创建定时器时绑定一个变量,在定时器触发时判断一下该变量是否仍然有效,如果变量无效,那就取消触发。
 static void OnTimer(std::weak_ptr<void> weak_cond, std::function<void()> cb){//当wealk ptr还有计数时,lock才会返回其智能指针,否则tmp为空std::shared_ptr<void> tmp = weak_cond.lock();if(tmp){cb();}}//weak_cond:满足条件再执行,智能指针=0时,就不触发Timer::ptr TimerManager::addConditionTimer(uint64_t ms, std::function<void()> cb,std::weak_ptr<void> weak_cond,bool recurring){return addTimer(ms, std::bind(&OnTimer, weak_cond, cb), recurring);}

3.3 IOManager继承TimerManager

  • IOManager通过继承的方式获得TimerManager类的所有方法,这种方式相当于给IOManager外挂了一个定时器管理模块。
class IOManager : public Scheduler, public TimerManager {
...
}
  • 为支持定时器功能,需要重新改造idle协程的实现,epoll_wait应该根据下一个定时器(最小堆的第一个定时器)的超时时间来设置超时参数。同时,还需要将到期的定时器任务加入到任务协程队列,由调度器统一调度。
void IOManager::idle() {// 一次epoll_wait最多检测256个就绪事件,如果就绪事件超过了这个数,那么会在下轮epoll_wati继续处理const uint64_t MAX_EVNETS = 256;epoll_event *events       = new epoll_event[MAX_EVNETS]();std::shared_ptr<epoll_event> shared_events(events, [](epoll_event *ptr) {delete[] ptr;});while (true) {// 获取下一个定时器的超时时间,顺便判断调度器是否停止uint64_t next_timeout = 0;if(stopping(next_timeout)) {next_timeout = getNextTimer();LOG_DEBUG(g_logger) << "name=" << getName() << "idle stopping exit";break;}// 阻塞在epoll_wait上,等待事件发生或定时器超时int rt = 0;do{// 默认超时时间5秒,如果下一个定时器的超时时间大于5秒,仍以5秒来计算超时,避免定时器超时时间太大时,epoll_wait一直阻塞static const int MAX_TIMEOUT = 5000;if(next_timeout != ~0ull) {next_timeout = std::min((int)next_timeout, MAX_TIMEOUT);} else {next_timeout = MAX_TIMEOUT;}rt = epoll_wait(m_epfd, events, MAX_EVNETS, (int)next_timeout);if(rt < 0 && errno == EINTR) {continue;} else {break;}} while(true);// 收集所有已超时的定时器,执行回调函数std::vector<std::function<void()>> cbs;listExpiredCb(cbs);if(!cbs.empty()) {for(const auto &cb : cbs) {schedule(cb);}cbs.clear();}...
}

http://www.ppmy.cn/news/39590.html

相关文章

【C语言】关于我回头学的那些输入输出等(四)

前言 我的第一门语言就是C&#xff0c;但是学艺不精&#xff0c;中途跑去学了C#和Java后&#xff0c;感觉到了C的重要性&#xff0c;毕竟是最接近底层的语言&#xff0c;又跑回来学C。 毕竟前两门的控制语句&#xff0c;变量什么的都是类似的&#xff0c;回到C后只需要学习一…

腾讯云CVM云服务器评测:标准型S5、S6

一、腾讯云CVM云服务器评测&#xff1a;标准型S5、S6 腾讯云服务器CVM标准型S5是次新一代云服务器规格&#xff0c;标准型S6是最新一代的云服务器&#xff0c;S6实例的CPU处理器主频性能要高于S5实例&#xff0c;同CPU内存配置下的标准型S6实例要比S5实例性能更好一些&#xf…

会员卡应用管理系统源码 支持收银+积分管理+商城营销功能 含详细搭建教程

分享一个会员卡应用管理系统源码&#xff0c;收银积分管理商城营销系统源码&#xff0c;含完整的程序包和搭建教程。 系统功能一览&#xff1a; 1、精简强悍&#xff0c;会员卡&#xff0c;积分&#xff0c;在线充值&#xff0c;商家核销&#xff0c;在线下单&#xff0c;优惠…

Ceph部署

1. 简介 Ceph是一个高性能、可扩容的分布式存储系统&#xff0c;它提供三大功能&#xff1a; 对象存储&#xff1a;提供RESTful接口&#xff0c;也提供多种编程语言绑定。兼容S3、Swift块存储&#xff1a;由RBD提供&#xff0c;可以直接作为磁盘挂载&#xff0c;内置了容灾机…

Python开发技术—进程和线程1(第1关:求素数的个数 + 第2关:求合数的个数 + 第3关:交替打印foobarpython)

目录 第1关&#xff1a;求素数的个数 第2关&#xff1a;求合数的个数 第3关&#xff1a;交替打印foobarpython 关于第一第二题思考&#xff1a; &#xff08;一&#xff09;起始数字结束数字 如何判断&#xff1f; &#xff08;二&#xff09;main函数执行的代码功能 &am…

使用串口重定向为服务器安装linux操作系统

在不借助显卡,通过串口来完成安装过程中的配置等选项。总结整个流程如下,方法很简单。在信创x86的设备上所使用的是redhat 7.4以及kylinOS的操作系统,串口工具是secureCRT。 首先进入Bios将串口重定向打开,并选择boot management,进入安装盘的启动界面 然后在启动界面的…

全国青少年信息素养大赛2023年python·选做题模拟五卷

目录 下载打印文档做题: 全国青少年电子信息智能创新大赛 python选做题模拟五卷 一、单选题 1. 对于数列3,8,11,15,17,19,25,30,44,采用“二分查找”法查找8,需要查找多少次?( ) A、5

Javase学习文档------面象对象初探

引入面向对象 面向对象的由来: 面向对象编程&#xff08;Object-Oriented Programming, OOP&#xff09;是一种编程范型&#xff0c;其由来可以追溯到20世纪60年代。在此之前&#xff0c;主流编程语言采用的是“过程化编程”模式&#xff0c;即面向过程编程模式。在这种模式下&…