在web服务器中加入定时器

本文介绍了如何在web服务器中加入定时器,包括client_data结构体、util_timer类和sort_timer_lst有序链表的设计。执行流程涉及alarm信号、信号处理函数、定时任务处理。在链接建立、请求发送和读取时需要更新计时器时间,防止过早中断连接。提供了源代码链接及相关文件列表。
摘要由CSDN通过智能技术生成

在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;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值