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

TinyWebserver学习(8)-定时器

定时器

原理解析

如果一个客户端与服务器长时间连接但是之间无资源的交互,那么就会浪费占据的服务器的资源,在这种情况下,服务器就会采取一种手段来检测这种无意义的连接,并对这些连接进行处理,如超过一定的时间无反应则对其进行清除;
除了处理非活跃的连接之外,服务器还有一些定时事件,比如关闭文件描述符等。服务器程序通常管理着众多定时事件, 因此有效地组织这些定时事件, 使之能在预期的时间点被触发且不影响服务器的主要逻辑, 对于服务器的性能有着至关重要的影响。 为实现这些功能,服务器就需要为各事件分配一个定时器。
为此,我们要将每个定时事件分别封装成定时器,并使用某种容器类数据结构, 比如链表、排序链表和时间轮, 将所有定时器串联起来,以实现对定时事件的统一管理。 不过, 在讨论如何组织定时器之前, 我们先要介绍定时的方法。
Linux提供了三种定时方法, 它们是:

  • socket选项SO_RCVTIMEO和SO_SNDTIMEO。
  • SIGALRM信号。
  • I/O复用系统调用的超时参数
    在该项目中,采用的是SIGALRM信号来作为定时器的实现方法,首先每一个定时事件都处于一个升序链表上,通过alarm()函数周期性触发SIGALRM信号,而后信号回调函数利用管道通知主循环,主循环接收到信号之后对升序链表上的定时器进行处理,若查询到长时间无连续的事件,则将其删除。

定时器框架图:
在整个项目中主要就是通过维持一个双向的升序链表来实现定时器的增删查改的,然后定时器的结构包含客户端数据、超时时间、上/下节点指针。 然后程序中会设置一个定时发送器,每隔一段时间发送一个SIGARLM,其回调函数会通过pip管道向主线程eventloop发送信号,主线程就会执行相关函数来检测是否有超时的时间。
在这里插入图片描述
接下来具体的看一下:

在eventlisten()函数中,首先设置了一个alarm定时器来发送信号

	....utils.addsig(SIGPIPE, SIG_IGN);utils.addsig(SIGALRM, utils.sig_handler, false);utils.addsig(SIGTERM, utils.sig_handler, false);alarm(TIMESLOT); //每隔一段时间向主循环发送一次SIGARLM信号//工具类,信号和描述符基础操作Utils::u_pipefd = m_pipefd;Utils::u_epollfd = m_epollfd;

dealclientdata()函数

	...if (0 == m_LISTENTrigmode){int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength); //addr是储存客户端地址的变量,它的长度用指针len指向if (connfd < 0){LOG_ERROR("%s:errno is:%d", "accept error", errno);return false;}if (http_conn::m_user_count >= MAX_FD){utils.show_error(connfd, "Internal server busy");LOG_ERROR("%s", "Internal server busy");return false;}timer(connfd, client_address);}......

在主程序处理新连接的时候,最后会将新的连接与一个定时器绑定,代码如下:

void WebServer::timer(int connfd, struct sockaddr_in client_address)
{   //创建并初始化http_conn对象users[connfd].init(connfd, client_address, m_root, m_CONNTrigmode, m_close_log, m_user, m_passWord, m_databaseName);//初始化client_data数据//创建定时器,设置回调函数和超时时间,绑定用户数据,将定时器添加到链表中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;utils.m_timer_lst.add_timer(timer);//将定时器加入链表中
}

客户端数据和定时器结构如下所示:

struct client_data
{sockaddr_in address;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;
};

接着再回到主线程中,如果主线程接收到了定时器发送的信号,则会执行dealwithsignal()函数,如下:

eventloop()函数

			...
//处理信号else if ((sockfd == m_pipefd[0]) && (events[i].events & EPOLLIN)){bool flag = dealwithsignal(timeout, stop_server);if (false == flag)LOG_ERROR("%s", "dealclientdata failure");}.........if (timeout){utils.timer_handler(); //重复发送SIGRALM信号LOG_INFO("%s", "timer tick");timeout = false;}
bool WebServer::dealwithsignal(bool &timeout, bool &stop_server)
{int ret = 0;int sig;char signals[1024];ret = recv(m_pipefd[0], signals, sizeof(signals), 0);//接受信号if (ret == -1){return false;}else if (ret == 0){return false;}else{for (int i = 0; i < ret; ++i){switch (signals[i]){case SIGALRM:{timeout = true;break;}case SIGTERM:{stop_server = true;break;}}}}return true;
}

如果是定时器发送的信号,则会将timeout设置为true,然后在eventloop最后,就会执行time_handler()函数。

//定时处理任务,重新定时以不断触发SIGALRM信号
void Utils::timer_handler()
{m_timer_lst.tick();alarm(m_TIMESLOT);
}

tick()函数主要就是检测整个链表有没有超时的事件,如果有则将其删除,因为链表是升序链表,所以从头节点开始检测,如果头节点都没有超时,则整个链表都无超时,如果头节点超时了,则将其删除,并接着往下检查,知道都没有事件超时。

void sort_timer_lst::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) //如果是头节点,则将头节点的上一个设置为NULL{head->prev = NULL;}delete tmp;tmp = head;}
}

那现在有一个问题,超时时间应该是可以改变的,如果客户端和服务器有互动的话,超时时间就应该更新,那么再回到主线程,当事件为read/write的时候,我们可以看相应的处理程序dealwithread()/dealwithwrite()

void WebServer::dealwithread(int sockfd)
{util_timer *timer = users_timer[sockfd].timer;//reactor(反应堆),就是IO多路复用,收到事件后,根据事件类型分配给某个线程if (1 == m_actormodel){if (timer){adjust_timer(timer);//调整事件}//若监测到读事件,将该事件放入请求队列m_pool->append(users + sockfd, 0); //users是一个数组指针,sockfd是索引,因此这个表示的就是当前处理的客户端的对象//stat:0表示read事件,1表示write事件while (true){if (1 == users[sockfd].improv){if (1 == users[sockfd].timer_flag){deal_timer(timer, sockfd);users[sockfd].timer_flag = 0;}users[sockfd].improv = 0;break;}}}

adjust_timer()函数
会更新超时时间expire

void WebServer::adjust_timer(util_timer *timer)
{time_t cur = time(NULL);timer->expire = cur + 3 * TIMESLOT;utils.m_timer_lst.adjust_timer(timer);LOG_INFO("%s", "adjust timer once");
}

最后,看一下链表的增删查改的一些函数,也都比较好理解:
对于add_timer,这里有一个重载

void sort_timer_lst::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 sort_timer_lst::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;}
}

adjust_timer()函数主要就是把原来的删除了,然后把新的重新加入到链表中

void sort_timer_lst::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 sort_timer_lst::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;
}

这些就是定时器的整个内容了

http://www.lryc.cn/news/579429.html

相关文章:

  • 在 Jetson Orin 开发套件上使用 Hardware Encoder / Decoder 构建 FFmpeg
  • 仿真软件介绍 COMSOL Multiphysics 或 ANSYS Fluent 等 MATLAB OpenFOAM,和在化学上的应用实例
  • 2025年6月一区-田忌赛马优化算法Tianji’s horse racing optimization-附Matlab免费代码
  • Springboot3整合ehcache3缓存--XML配置和编程式配置
  • 【PyCharm 2025.1.2配置debug】
  • 【vmware虚拟机使用】 开始安装centos7操作系统
  • Navicat Premium 12连接Oracle时提示oracle library is not loaded的问题解决
  • 分布式部署下如何做接口防抖---使用分布式锁
  • macOS 26正式发布,全新Liquid Glass设计语言亮相
  • 旅游管理实训室:支撑实践教学的核心载体
  • 5118 API智能处理采集数据教程
  • 项目——视频共享系统测试
  • 【C++】状态模式
  • GitHub 解码指南:用 AI 赋能,五步快速掌握任意开源项目
  • MySQL 8.0 OCP 1Z0-908 题目解析(20)
  • MVC 架构设计模式
  • 【Linux仓库】进程优先级及进程调度【进程·肆】
  • 小黑黑日常积累大模型prompt句式2:【以段落的形式输出,不分点列举】【如果没有相关内容则不输出】【可读性强】【输出格式规范】
  • Java学习第八部分——泛型
  • git 中删除提交历史
  • 代码随想录算法训练营第四十五天|动态规划part12
  • Fiddler中文版抓包工具在后端API调试与Mock中的巧用
  • 应用在核电行业的虚拟现实解决方案
  • Laravel8中调取腾讯云文字识别OCR
  • 【前端开发】Uniapp分页器:新增输入框跳转功能
  • SpringCloud系列(49)--SpringCloud Stream消息驱动之实现生产者
  • Rubber Band Algorithm 应力及反作用力测试
  • 运维打铁: 企业运维开发痛点之解决方案
  • ModuleNotFoundError: No module named ‘onnxruntime‘
  • 【免费.NET方案】CSV到PDF与DataTable的快速转换