当前位置: 首页 > 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 视频…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案

在大数据时代&#xff0c;海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构&#xff0c;在处理大规模数据抓取任务时展现出强大的能力。然而&#xff0c;随着业务规模的不断扩大和数据抓取需求的日益复杂&#xff0c;传统…...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...

《Offer来了:Java面试核心知识点精讲》大纲

文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...

TCP/IP 网络编程 | 服务端 客户端的封装

设计模式 文章目录 设计模式一、socket.h 接口&#xff08;interface&#xff09;二、socket.cpp 实现&#xff08;implementation&#xff09;三、server.cpp 使用封装&#xff08;main 函数&#xff09;四、client.cpp 使用封装&#xff08;main 函数&#xff09;五、退出方法…...