在web服务器中加入定时器
定时器主要结构:
client_data结构体:记录客户端链接信息,以及每个客户端连接时的时间参数,主要记录超时时间等
util_timer类:记录timer里的一些数据,如回调函数,超时时间以及文件描述符等
sort_timer_lst类:有序链表,记录timer位置,按照超时时间(记录的绝对时间)从小到大,这样在处理的时候,只要遇到不超时的就可以结束循环,不用全部遍历。
执行流程:
首先是直接使用alarm(5);在5秒钟之后触发SIGALRM信号
该信号会调用与其绑定的信号处理函数sig_handle()
sig_handle()函数会将信号类型写入pipefd[1],另一端在epoll树上被监听
监听到该信号的主循环判断sig类型,若为SIGALRM,则执行timer_handle()函数
timer_handle():先调用sort_timer_lst::tick()函数,遍历有序链表,将超时的timer找出来,并执行他们的回调函数cb_func()传输参数为当前user_data因为user_data中保存着链接的文件描述符,cb_func()函数将此文件描述符下树,不再监听,并close掉
之后定时五秒触发一次SIGALRM信号,之后有被监听不断循环。
值得注意的是:
计时器只有在链接新建立的时候会开始计时,如果一个链接反复的发送请求,应该也要更新计时器的时间,不仅如此,最重要的是在读取的时候也要更新,考虑到一些大文件会一直持续读取长达几个小时(比如看视频的时候,如果不加修改,可能视频还没加载完,链接就被计时器打断),所以每次读取操作的时候也要更新计时器记录的时间。
以下是源代码,使用方法可以参照之前文章
https://blog.csdn.net/ad838931963/article/details/118518506?spm=1001.2014.3001.5501
makefie:
CXX ?= g++
DEBUG ?= 1
ifeq ($(DEBUG), 1)
CXXFLAGS += -g
else
CXXFLAGS += -O2
endif
server: main.cpp http_conn.cpp
$(CXX) -o server $^ $(CXXFLAGS) -lpthread -g
clean:
rm -r server
threadpool.h
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <list>
#include <cstdio>
#include <exception>
#include <pthread.h>
#include "locker.h"
//线程池类,将他定义为模板类是为了代码复用
template<typename T>
class threadpool
{
public:
/*thread_number是线程池中线程的数量,max_requests是请求队列中最多允许的、等待处理的请求的数量*/
threadpool( int thread_number = 8, int max_request = 10000);
~threadpool();
bool append(T *request);
private:
/*工作线程运行的函数,它不断从工作队列中取出任务并执行之*/
static void *worker(void *arg);
void run();
private:
int m_thread_number; //线程池中的线程数
int m_max_requests; //请求队列中允许的最大请求数
pthread_t *m_threads; //描述线程池的数组,其大小为m_thread_number
std::list<T *> m_workqueue; //请求队列
locker m_queuelocker; //保护请求队列的互斥锁
sem m_queuestat; //是否有任务需要处理
bool m_stop; //是否结束线程
};
template <typename T>
threadpool<T>::threadpool(int thread_number, int max_requests) : m_thread_number(thread_number), m_max_requests(max_requests), m_stop(false),m_threads(NULL)
{
printf("初始化线程池!\n");
if (thread_number <= 0 || max_requests <= 0)
throw std::exception();
m_threads = new pthread_t[m_thread_number];
if (!m_threads)
throw std::exception();
for (int i = 0; i < thread_number; ++i)
{
if (pthread_create(m_threads + i, NULL, worker, this) != 0)
{
delete[] m_threads;
throw std::exception();
}
if (pthread_detach(m_threads[i]))
{
delete[] m_threads;
throw std::exception();
}
}
}
template <typename T>
threadpool<T>::~threadpool()
{
printf("线程池析构!\n");
delete[] m_threads;
}
template <typename T>
bool threadpool<T>::append(T *request)
{
printf("append()\n");
m_queuelocker.lock();
if (m_workqueue.size() > m_max_requests)
{
m_queuelocker.unlock();
return false;
}
m_workqueue.push_back(request);
m_queuelocker.unlock();
m_queuestat.post();
return true;
}
template <typename T>
void *threadpool<T>::worker(void *arg)
{
printf("work()!\n");
threadpool *pool = (threadpool *)arg;
pool->run();
return pool;
}
template <typename T>
void threadpool<T>::run()
{
printf("run()!\n");
while(!m_stop)
{
m_queuestat.wait();
m_queuelocker.lock();
if(m_workqueue.empty())
{
m_queuelocker.unlock();
continue;
}
T * request = m_workqueue.front();
m_workqueue.pop_front();
m_queuelocker.unlock();
if(!request)
{
continue;
}
printf("process()\n");
request->process();
}
}
#endif
lst_time.h
/*************************************************************************
> File Name: lst_time.h
> Author:
> Mail:
> Created Time: Fri 09 Jul 2021 11:49:19 AM CST
************************************************************************/
#ifndef _LST_TIME_H
#define _LST_TIME_H
#include<time.h>
#define BUFFER_SIZE 64
class util_timer;//向前声明
//客户端链接的数据,每一个客户端练上来都被记录着fd和定时器
struct client_data
{
sockaddr_in address;
int sockfd;
char buf[BUFFER_SIZE];
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;//指向后一个定时器
};
//定时器链表。他是一个升序、双向链表,且带有头节点和尾节点
class sort_timer_lst
{
public:
sort_timer_lst():head(NULL),tail(NULL) {
}//初始化
~sort_timer_lst();//链表被销毁时,删除其中所有节点
void add_timer(util_timer * timer);//按顺序添加计时器
void adjust_timer(util_timer * timer); //当某一个任务发生变换时,应该调整对应的定时器在链表中的位置。这个函数只考虑被调整的定时器的超过时间延长的情况,即该定时器需要往尾部移动
void del_timer(util_timer* timer); //将目标定时器timer从链表中删除
/*也就是遍历链表,超时的全部执行回调函数*/
void tick();//SIGALRM信号每次被触发就在其信号处理函数(如果使用统一事件源,那就是主函数)中执行一次tick函数,已处理链表上到期的任务
private:
//一个重载的辅助函数,当add_timer不能完成时,会调用该函数进行添加
void add_timer(util_timer* timer, util_timer* lst_head);
private:
util_timer* head;
util_timer* tail;
};
sort_timer_lst::~sort_timer_lst()
{
util_timer* temp = head;
while(temp)
{
head = head->next;
delete temp;
temp = head;
}
}
void sort_timer_lst::add_timer(util_timer * timer)
{
if(!timer)
{
return;
}
if(!head)
{
head = tail = timer;
return;
}
//为了链表的有序性;我们根据目标定时器的超过时间来排序,(按照超时时间从小到大排序)如果超时时间小于head,成为head,负责根据顺序插入
if(timer->expire < head->expire)
{
timer->next = head;
head->prev = timer;
head = timer;
return ;
}
add_timer(timer,head);//将timer插入head中,这是一个重载函数,注意参数!
}
void sort_timer_lst::adjust_timer(util_timer * timer)
{
if(!timer)
{
return;
}
util_timer * temp = timer->next;
//不用调整的情况:1.处于尾部。2.虽有变化,但不影响排位
if(!temp || timer->expire < temp->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,head);
}
}
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;
}
//如果链表至少有三个定时器,并且目标节点是在链表的中间位置
else
{
timer->prev->next = timer->next;
timer->next->prev = timer->prev;
delete timer;
return;
}
}
void sort_timer_lst::tick()
{
if(!head)
{
return;
}
printf("timer tick !\n");
time_t cur = time(NULL);//获取系统当前时间
util_timer* temp = head;
//从头节点开始一次处理每一个定时器,直到遇到一个尚未到期的定时器,这就是定时器的核心逻辑
//说白了就是遍历
while(temp)
{
//因为每个定时器都是用绝对时间作为超时值,所以我们可以吧定时器的超时值和系统当前时间,比较一判断定时器是否到期
if(cur < temp->expire)
{
break;
}
//调用定时器的回调函数,已执行定时任务
temp->cb_func(temp->user_data);
//执行完定时任务以后,就将它从链表中删除,并且重置链表头节点
head = temp->next;
//链表操作的时候一定一定要注意!先检查是否为空
if(head)
{
head->prev = NULL;
}
delete temp;
temp = head;
}
}
void sort_timer_lst::add_timer(util_timer* timer, util_timer* lst_head)
{
util_timer* prev = lst_head;
util_timer* temp = prev->next;
//遍历head之后的所有节点,知道找到一个节点的值大于timer的位置插入
while (temp)
{
if(timer->expire < temp->expire)
{
prev->next = timer;
timer->next = temp;
temp->prev = timer;
timer->prev = prev;
break;