当前位置: 首页 > news >正文

08 定时器(下)

08 定时器(下)

本文内容

定时器处理非活动连接模块,分为定时方法与信号通知流程;定时器及其容器设计、定时任务的处理。
定时器设计,将连接资源与定时事件等封装起来,具体包括连接资源、超时时间和回调函数,回调函数指向定时事件。
定时器容量设计,将多个定时器串联组织起来统一处理,具体包括升序链表设计。
定时任务处理函数,该函数封装在容量类中,函数遍历升序链表容器,根据超时时间,处理对应的定时器。
代码分析-使用定时器,通过代码分析,如何在项目中使用定时器。

定时器设计

项目中将连接资源、定时事件和超时时间封装为定时器类,具体的,

  • 连接资源包括客户端套接字地址、文件描述符和定时器
  • 定时事件为回调函数,将其封装起来由用户自定义,这里是删除非活动socket上的注册事件,并关闭
  • 定时器超时时间=浏览器和服务器连接时刻+固定时间(TIMESLOT),定时器使用绝对时间作为超时值,alarm设置为5秒,连接超时为15秒。
//连接资源结构体成员需要用到定时器类
//需要前向声明
class util_timer;//连接资源
struct client_data
{//客户端socket地址sockaddr_in address;//socket文件描述符int sockfd;//定时器util_timer* timer;
};//定时器类
class util_timer
{
public:util_timer():prev(NULL),next(NULL){}
public://超时时间time_t expire;//回调函数void (*cb_func)(client_data*);//连接资源client_data* user_data;//前向定时器util_timer* prev;//后继定时器util_timer* next;
};

定时事件,从内核事件表删除事件,关闭文件描述符,释放连接资源。

//定时器回调函数
void cb_func(client_data *user_data)
{//删除非活动连接在socket上的注册事件epoll_ctl(epollfd,EPOLL_CTL_DEL,user_data->sockfd,0);assert(user_data);//关闭文件描述符close(user_data->sockfd);//减少连接数http_conn::m_user_count--;
}

定时器容器设计

定时器容器是带头尾结点的升序双向链表。为每个连接创建一个定时器,将其添加到链表中,并按照超时事件升序排列。执行定时任务时,将到期的定时器从链表中删除。
涉及双向链表的插入,删除操作,添加定时器的时间复杂度o(n),删除定时器的时间复杂度是o(1)
升序双向链表主要逻辑:

  • 创建头尾节点(没有意义,统一方便调整)

  • add_timer函数,将目标定时器添加到链表中,添加时按照升序添加
    若当前链表中只有头尾节点,直接插入
    否则,将定时器按升序插入

  • adjust_timer函数,当定时任务发生变化,调整对应定时器在链表中的位置
    客户端在设定时间内有数据收发,则当前时刻对该定时器重新设定时间(往后延长超时时间)
    被调整的目标定时器在尾部,或定时器新的超时值仍然小于下个定时器的超时,不用调整
    否则先将定时器从链表取出,重新插入链表

  • del_timer函数将超时的定时器从链表中删除
    常规双向链表删除结点

//定时器容器类
class sort_timer_lst
{
public:sort_timer_lst():head(NULL),tail(NULL){}//常规销毁链表~sort_timer_lst(){util_timer* tmp=head;while(tmp){head=tmp->next;delete tmp;tmp=head;}}//添加定时器,内部调用私有成员add_timervoid add_timer(util_timer* timer){if(!timer){return;}if(!head){head=tail=timer;return;}//如果新的定时器超时时间小于小于当前头部结点//直接将当前定时器结点作为头结点if(timer->expire<head->expire){timer->next=head;head->prev=timer;head=timer;return;}//否则,调用私有成员,调整内部结点add_timer(timer,head);}//调整定时器,任务发生变化时,调整定时器在链表中的位置void adjust_timer(util_timer* timer){if(!timer){return;}util_timer* tmp=timer->next;//被调整的定时器在链表尾部//定时器超时值仍然小于小一个定时器超时值,不调整if(!tmp||(timer->expire<tmp->expire)){return;}//被调整定时器是链表头结点,把定时器取出,重新插入if(timer==head){head=head->next;head->prev=NULL;timer->next=NULL;add_timer(timer,head);}//被调整定时器在内部,将定时器取出,重新插入else{timer->prev->next=timer->next;timer->next->prev=timer->prev;add_timer(timer,timer->next);}}//删除定时器void del_timer(util_timer* timer){if(!timer){return;}//链表中只有一个定时器,需要删除该定时器if((timer==head)&&(timer==tail)){delete timer;head=NULL;tail=NULL;return;}//被删除的定时器为头结点if(timer==head){head=head->next;head->prev=NULL;delete timer;return;}//被删除的定时器为尾结点if(timer==tail){tail=tail->prev;tail->next=NULL'delete timer;return;}//被删除的定时器在链表内部,常规链表结点删除timer->prev->next=timer->next;timer->next->prev=timer->prev;delete timer;}private://私有成员,被公有成员add_timer和adjust_time调用//主要用于调整链表内部结点void add_timer(util_timer* timer,util_timer* lst_head){util_timer* prev=lst_head;util_timer* tmp=prev->next;//遍历当前结点之后的链表,按照超时时间找到目标定时器对应的位置,常规双向链表插入操作while(tmp){if(timer->expire<tmp->expire){prev->next=timer;timer->next=tmp;tmp->prev=timer;timer->prev=prev;break;}prev=tmp;tmp=tmp->next;}//遍历完发现,目标定时器需要放在尾结点处if(!tmp){prev->next=timer;timer->prev=prev;timer->next=NULL;tail=timer;}}private://头尾结点util_timer* head;util_timer* tail;
};

定时任务处理函数

使用统一事件源,SIGALRM信号每次被触发,主循环中调用一次定时任务处理函数,处理链表容器中到期的定时器。

  • 遍历定时器升序链表容器,从头结点开始依次处理每个定时器,直到遇到尚未到期的定时器
  • 若当前时间小于定时器超时时间,跳出循环,即未找到到期的定时器
  • 若当前时间大于定时器超时时间,即找到了到期的容器,执行回调函数,然后将它从链表中删除,然后继续遍历。
//定时任务处理函数
void tick()
{if(!head){return;}//获取当前时间time_t cur=time(NULL);util_timer* tmp=head;//遍历定时器链表while(tmp){//链表容器为升序排列//当前时间小于定时器的超时时间,后面的定时器也没有到期if(cur<tmp->expire){break;}//当前定时器到期,则调用回调函数,执行定时事件tmp->cb_func(tmp->user_data);//将处理后的定时器从链表容器中删除,并重置头结点head=tmp->next;if(head){head->prev=NULL;}delete tmp;tmp=head;}
}

代码分析-如何使用定时器

服务器首先创建定时器容器链表,然后用统一事件源1将异常事件,读写事件和信号事件统一处理,根据不同事件的对应逻辑使用定时器。

  • 浏览器与服务器连接时,创建该连接对应的定时器,并将该定时器添加到链表上
  • 处理异常事件,执行定时事件,服务器关闭连接,从链表上移除对应定时器
  • 处理定时信号时,将定时标志设置为true
  • 处理读事件时,若某连接上发生读事件,将对应定时器向后移动,否则,执行定时事件
  • 处理写事件时,若服务器通过某连接给浏览器发送数据,将对应定时器向后移动,否则,执行定时事件
//定时处理任务,重新定时以不断触发SIGALRM信号
void timer_handler()
{timer_lst.tick();alarm(TIMESLOT);
}//创建定时器容器链表
static sort_timer_lst timer_lst;//创建连接资源数组
client_date *users_timer=new client_data[MAX_FD];//超时默认为False
bool timeout=false;//alarm定时触发SIGALRM信号
alarm(TIMESLOT);while(!stop_server)
{int number=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);if(number<0&&errno!=EINTER){break;}for(int i=0;i<number;i++){int sockfd=events[i].data.fd;//处理新到的客户连接if(sockfd==listenfd){//初始化客户端连接地址struct sockaddr_in client_address;socklen_t client_addrlength=sizeof(client_address);//该连接分配的文件描述符int connfd=accept(listenfd,(struct sockaddr *)&client_address,&client_addrlength);//初始化该连接对应的连接资源users_timer[connfd].address=client_address;users_timer[connfd].sockfd=connfd;//创建定时器临时变量util_timer *timer=new util_timer;//设置定时器对应的连接资源timer->user_data=&users_timer[connfd];//设置回调函数timer->cb_func=cb_func;time_t cur=time(NULL);//设置绝对超时时间timer->expire=cur+3*TIMESLOT;//创建该连接对应的定时器,初始化为前述临时变量users_timer[connfd].timer=timer;//将该定时器添加到链表中timer_lst.add_timer(timer);}//处理异常事件else if(events[i].events&(EPOLLRDHUP|EPOLLHUP|EPOLLERR)){//服务器端关闭连接,移除对应的定时器cb_func(&users_timer[sockfd]);util_timer *timer=users_timer[sockfd].timer;if(timer){timer_lst.del_timer(timer);}}//处理定时器信号else if((sockfd==pipefd[0])&&(events[i].events&EPOLLIN)){//接收到SIGALRM信号,timeout设置为TRUE}//处理客户连接上接收到的数据else if(events[i].events&EPOLLIN){//创建定时器临时变量,将该连接对应的定时器取出来util_timer *timer=users_timer[sockfd].timer;if(users[sockfd].read_once()){//若监测到读事件,将该事件放入请求队列pool->append(users+sockfd);//若有数据传输,则将定时器往后延迟3个单位//对其在链表上的位置进行调整if(timer){time_t cur=time(NULL);timer->expire=cur+3*TIMESLOT;timer_lst.adjust_timer(timer);}}else{//服务器关闭连接,移除对应的定时器cb_func(&users_timer[sockfd]);if(timer){timer_lst.del_timer(timer);}}}else if(events[i].events&EPOLLOUT){util_timer *timer=users_timer[sockfd].timer;if(users[sockfd].write()){//若有数据传输,则将定时器往后延迟3个单位//并对新的定时器在链表上的位置进行调整if(timer){time_t cur=time(NULL);timer->expire=cur+3*TIMESLOT;timer_lst.adjust_timer(timer);}}else{//服务器关闭连接,移除对应的定时器cb_func(&users_timer[sockfd]);if(timer){timer_lst.del_timer(timer);}}}}
//处理定时器为非必须事件,收到信号并不是立马处理
//完成读写事件,再进行处理
if(timeout)
{timer_handler();;timeout=false;
}
}

相关文章:

08 定时器(下)

08 定时器&#xff08;下&#xff09; 本文内容 定时器处理非活动连接模块&#xff0c;分为定时方法与信号通知流程&#xff1b;定时器及其容器设计、定时任务的处理。 定时器设计&#xff0c;将连接资源与定时事件等封装起来&#xff0c;具体包括连接资源、超时时间和回调函…...

C++设计模式之适配器设计模式

文章目录 C适配器设计模式什么是适配器设计模式该模式有什么优缺点优点缺点 如何使用 C适配器设计模式 什么是适配器设计模式 适配器设计模式是一种行为型设计模式&#xff0c;它允许你将两个不兼容的接口组合在一起&#xff0c;使它们能够协同工作。 该模式有什么优缺点 优…...

Maven项目解决cannot resolve plugin maven-deploy-plugin:2.7

导入maven项目后&#xff0c;编辑的时候提示一些插件加载失败&#xff01;大概率是你的网络有问题&#xff0c;插件下载失败。 如下图&#xff1a;&#xff08;网络突然好了&#xff0c;我想截图但是没有复现&#xff0c;用网上找到的截图代替&#xff0c;明白意思就行&#x…...

Postgresql源码(110)分析dsm动态共享内存分配与共享内存mq实例

相关 《Postgresql源码&#xff08;90&#xff09;共享内存申请CreateSharedMemoryAndSemaphores》 《Linux内存映射函数mmap与匿名内存块》 《Linux共享内存与子进程继承》 0 概念 数据结构含义&#xff1a; dsm_segment&#xff08;动态共享内存段&#xff09;&#xff1a;…...

51单片机学习--蜂鸣器播放音乐

由原理图可知&#xff0c;蜂鸣器BEEP与P1_5 相关&#xff0c;但其实这个原理图有错&#xff0c;实测接的是P2_5 下面这个代码就是以500HZ的频率响500ms的例子 sbit Buzzer P2^5;unsigned char KeyNum; unsigned int i;void main() {while(1){KeyNum Key();if(KeyNum){for(i …...

【Vue组件eval方法的使用】

Vue页面中条件可以放在当前vue页面中而无需影响到组件 如 这是我的表格操作列按钮&#xff0c;需求是第四个按钮如果表格当前数据的is_execl字段为0则显示否则隐藏 这种条件判断很频繁 如果像之前一样给一个标识&#xff0c;页面多了就难以维护&#xff0c;而且判断条件如果不…...

C++ 多文件结构和编译预处理命令

1. C程序的一般组织结构 C源程序的结构基本上都是由3个部分构成&#xff1a;类的定义、类的成员的实现和主函数。因为所编写的程序比较小&#xff0c;所以这三个部分都写在了同一个文件当中。在规模比较大的项目中&#xff0c;往往需要多个源程序文件&#xff0c;每个源程序文…...

QT实现中英文键盘

使用Qt中实现中英文键盘&#xff0c;支持各种linux嵌入式设备。 实现思路&#xff1a;需要一个中文字体库&#xff0c;将字体库加载到一个Hash容器&#xff0c;字母和拼音作为key值&#xff0c;对应的中文作为value值。 核心代码&#xff1a; #include "UKeyBoard.h"…...

java中并发编程CompletableFuture和supplyAsync的用法

在Java中&#xff0c;并发编程可以使用CompletableFuture类来实现异步操作和并行任务。其中&#xff0c;supplyAsync是CompletableFuture类的一个静态方法&#xff0c;用于执行一个有返回值的异步任务。 supplyAsync方法的语法如下&#xff1a; public static <U> Comp…...

chrony服务器

目录 1.chrony简介 2.chrony安装配置 2.1 chrony安装及配置 非常重要&#xff1a;在配置之前&#xff0c;检查防火墙和selinux是否关闭 3.将chrony配置为其他主机的时间服务器 3.1 修改chrony配置文件 4.chronyc命令 1.chrony简介 chrony 是开源的遵循网络时间协议&…...

春秋云镜 CVE-2021-24762

春秋云镜 CVE-2021-24762 WordPress Plugin Perfect Survey 注入 靶标介绍 WordPress Perfect Survey plugin在1.5.2之前版本存在SQL注入漏洞&#xff0c;该漏洞源于基于数据库的应用缺少对外部输入SQL语句的验证。攻击者可利用该漏洞执行非法SQL命令。 启动场景 漏洞利用 …...

K8s中的Service

Service 1.Service存在的意义&#xff1f; &#xff08;1&#xff09;pod注册在service里面&#xff0c;做服务发现使用 &#xff08;2&#xff09;定义一组Pod访问策略&#xff08;负载均衡&#xff09; 2.Pod和Service关系 通过service实现Pod的负载均衡 3.常用Service类…...

[软件工程] 全局分析规格说明书模板

1 价值需求 描述目标系统的价值需求&#xff0c;可以附上商业模式画布。 1.1 利益相关者 描述目标系统的利益相关者&#xff0c;包括终端用户、企业组织、投资人等。 1.2 系统愿景 描述利益相关者共同达成一致的愿景&#xff0c;该愿景的描述需要对准企业的战略目标。 1.3 系统…...

【JAVASE】封装

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈Java &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 封装 1. 什么是封装2. 访问限定符3. 封装…...

Java多线程(四)

目录 一、线程的状态 1.1 观察线程的所有状态 1.2 线程状态和状态转移的意义 1.2.1 NEW、RUNNABLE、TERMINATED状态转换 1.2.2 WAITING、BLOCKED、TIMED_WAITING状态转换 1.2.3 yield()大公无私让出cpu 一、线程的状态 1.1 观察线程的所有状态 public class Demo9 {public st…...

Linux 文件系统预留空间

在一次磁盘打满的场景下&#xff0c;使用了df -h命令来查看磁盘使用率。 [roottest tmp]# df -h 文件系统 容量 已用 可用 已用% 挂载点 devtmpfs 28G 0 28G 0% /dev tmpfs 28G 0 28G 0% /dev/shm tmpfs 28G 2.8G …...

篇一:单例模式:C++中的独一无二

篇一&#xff1a;“单例模式&#xff1a;C中的独一无二” 设计模式在软件开发中起到了至关重要的作用&#xff0c;其中单例模式是最为常用且基础的设计模式之一。单例模式确保一个类只有一个实例&#xff0c;并提供全局访问点&#xff0c;以保证系统中的某个对象只有唯一的存在…...

JVM之内存结构

1.程序计数器 定义&#xff1a;程序计数器&#xff08;Program Counter Register&#xff09;是JVM中一块较小的内存空间。解释器在解释JVM指令为机器码以供CPU执行时&#xff0c;会去程序计数器当中找到jvm指令的执行地址。 作用&#xff1a;记住下一条jvm指令的执行地址 特…...

C#实现结构体与字节流的相互转化

unity项目中&#xff0c;涉及到与C的相互通信&#xff0c;而通信接口为C封好的动态库。所以&#xff0c;传输信息时&#xff0c;需要向C端发送字节流信息。 对此&#xff0c;需将结构体数据转为字节流&#xff0c;其代码如下&#xff1a; public static byte[] StructToBytes(…...

用LangChain开源框架实现知识机器人

前言 Large Language Models (LLMs)在2020年OpenAI 的 GPT-3 的发布而进入世界舞台 。从那时起&#xff0c;他们稳步增长进入公众视野。 众所周知 OpenAI 的 API 无法联网&#xff0c;所以大家如果想通过它的API实现联网搜索并给出回答、总结 PDF 文档、基于某个 Youtube 视频…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…...

Unity中的transform.up

2025年6月8日&#xff0c;周日下午 在Unity中&#xff0c;transform.up是Transform组件的一个属性&#xff0c;表示游戏对象在世界空间中的“上”方向&#xff08;Y轴正方向&#xff09;&#xff0c;且会随对象旋转动态变化。以下是关键点解析&#xff1a; 基本定义 transfor…...

五子棋测试用例

一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏&#xff0c;有着深厚的文化底蕴。通过将五子棋制作成网页游戏&#xff0c;可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家&#xff0c;都可以通过网页五子棋感受到东方棋类…...

深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙

WebGL&#xff1a;在浏览器中解锁3D世界的魔法钥匙 引言&#xff1a;网页的边界正在消失 在数字化浪潮的推动下&#xff0c;网页早已不再是静态信息的展示窗口。如今&#xff0c;我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室&#xff0c;甚至沉浸式的V…...

在Zenodo下载文件 用到googlecolab googledrive

方法&#xff1a;Figshare/Zenodo上的数据/文件下载不下来&#xff1f;尝试利用Google Colab &#xff1a;https://zhuanlan.zhihu.com/p/1898503078782674027 参考&#xff1a; 通过Colab&谷歌云下载Figshare数据&#xff0c;超级实用&#xff01;&#xff01;&#xff0…...