当前位置: 首页 > 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.dtcms.com/a/265784.html

相关文章:

  • 深度解析:venv和conda如何解决依赖冲突难题
  • 使用netstat与grep命令结合批量查找特定内容
  • Class3图像分类数据集代码
  • 数学建模_时间序列
  • CTF Web PHP弱类型与进制绕过(过滤)
  • 【云计算】企业项目 策略授权
  • 网络层:ip协议 与数据链路层
  • C++反射之获取可调用对象的详细信息
  • 《Spring 中上下文传递的那些事儿》Part 2:Web 请求上下文 —— RequestContextHolder 与异步处理
  • 低代码实战训练营教学大纲 (10天)
  • Linux之Socket 编程 UDP
  • 自然光实时渲染~三维场景中的全局光照
  • osg加入实时光照SilverLining 天空和3D 云
  • 租车小程序电动车租赁小程序php方案
  • Flutter 3.29+使用isar构建失败
  • 创客匠人视角:知识变现与创始人 IP 打造的破局之道
  • centos7源码编译安装python3
  • SSM和SpringBoot框架的关系
  • 关于微前端框架micro,子应用设置--el-primary-color失效的问题
  • FPGA从零到一实现FOC(一)之PWM模块设计
  • 火语言 RPA:突破企业自动化瓶颈,释放数字生产力​
  • Linux基本命令篇 —— zip/unzip命令
  • Apache Commons Pool中的GenericObjectPool详解
  • 华为Freebuds 6i新音效,设置后音质敲好!
  • Nginx安全配置漏洞修复实战指南
  • 百度文心智能体平台x小米应用商店:联手打造行业首个智能体与应用市场跨端分发模式
  • React 强大的表单验证库formik之集成Yup、React Hook Form库
  • 使用 Dockerfile 构建基于 .NET9 的跨平台基础镜像
  • 安卓开机自启动方案
  • Kafka生态整合深度解析:构建现代化数据架构的核心枢纽