线程池web服务器(5) 定时器

基础知识

●非活跃:是指客户端(这里是浏览器)与服务器端建立连接后,长时间不交换数据,一直占用服务器端的文件描述符,导致连接资源的浪费。

●定时事件:是指固定一段时间之后触发某段代码,由该段代码处理一个事件,如从内核事件表删除事件,并关闭文件描述符,释放连接资源。

●定时器:是指利用结构体或其他形式,将多种定时事件进行封装起来。具体的,这里只涉及一种定时事件,即定期检测非活跃连接,这里将该定时事件与连接资源封装为一个结构体定时器。

●定时器容器:是指使用某种容器类数据结构,将上述多个定时器组合起来,便于对定时事件统一管理。具体的,项目中使用升序链表将所有定时器串联组织起来。

定时方法

Linux下提供了三种定时的方法:

●socket选项SO_RECVTIMEO和SO_SNDTIMEO
●SIGALRM信号
●I/O复用系统调用的超时参数

 三种方法没有一劳永逸的应用场景,也没有绝对的优劣。由于项目中使用的是SIGALRM信号,这里仅对其进行介绍,另外两种方法可以查阅游双的Linux高性能服务器编程 第11章 定时器。

项目中定时器概述

 项目概述:本项目中,服务器主循环为每一个连接创建一个定时器,并对每个连接进行定时。另外,利用升序时间链表容器将所有定时器串联起来,若主循环接收到定时通知,则在链表中依次执行定时任务。

 具体的,主函数利用alarm函数周期性地触发SIGALRM信号,信号处理函数利用管道通知主循环,主循环接收到该信号后调用tick()处理到期的定时器,如果到期,调用该定时器的cb_func函数删除资源。

 从上面的简要描述中,可以看出定时器处理非活动连接模块,主要分为两部分,其一为定时方法与信号通知流程,其二为定时器及其容器设计与定时任务的处理。

第一部分:定时方法与信号通知流程

 利用sigaction等信号相关的函数,指定SIGALRM信号的处理函数通过管道将信号值发给主进程

alarm函数

#include <unistd.h>;

unsigned int alarm(unsigned int seconds);

 设置信号传送闹钟,即用来设置信号SIGALRM在经过参数seconds秒数后发送给目前的进程。如果未设置信号SIGALRM的处理函数,那么alarm()默认处理终止进程.

socketpair函数

#include <sys/types.h>
#include <sys/socket.h>

int socketpair(int domain, int type, int protocol, int sv[2]);

 domain表示协议族,PF_UNIX或者AF_UNIX
 type表示协议,可以是SOCK_STREAM或者SOCK_DGRAM,SOCK_STREAM基于TCP,SOCK_DGRAM基于UDP
 protocol表示类型,只能为0
 sv[2]表示套节字柄对,该两个句柄作用相同,均能进行读写双向操作
 返回结果, 0为创建成功,-1为创建失败

send函数

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

 当套接字发送缓冲区变满时,send通常会阻塞,除非套接字设置为非阻塞模式,当缓冲区变满时,返回EAGAIN或者EWOULDBLOCK错误,此时可以调用select函数来监视何时可以发送数据。

第二部分:定时器设计(升序链表)

 要点:
●定时器设计,将连接资源和定时事件等封装起来,具体包括连接资源、超时时间和回调函数,这里的回调函数指向定时事件。

●定时器容器设计,将多个定时器串联组织起来统一处理,具体包括升序链表设计。

●定时任务处理函数,该函数封装在容器类中,具体的,函数遍历升序链表容器,根据超时时间,处理对应的定时器。

●代码分析-使用定时器,通过代码分析,如何在项目中使用定时器。

定时器设计

 思路:将连接资源、定时事件和超时时间封装为定时器类,具体有:
●连接资源包括客户端套接字地址、文件描述符和定时器
●定时事件为回调函数,将其封装起来由用户自定义,这里是删除非活动socket上的注册事件,并关闭
●定时器超时时间 = 浏览器和服务器连接时刻 + 固定时间(TIMESLOT),可以看出,定时器使用绝对时间作为超时值,这里alarm设置为5秒,连接超时为15秒。

/*连接资源类需要用到定时器类,前向声明*/
class util_time;

/*连接资源结构体*/
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;
}
/*定时事件,从内核事件表删除事件,关闭fd,释放连接资源*/
void cb_func(client_data *user_data)
{
   
	epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0);
	assert(user_data);
	close(user_data->sockfd);
	http_conn::m_user_count--;
}

定时器容器设计

 容器类型:带头尾节点的升序双向链表,具体的为每个连接创建一个定时器,添加至链表,并按照超时时间升序排列。

 主要逻辑:
●创建头尾节点:头尾节点无意义,仅统一方便调整
●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;
        }
    }
    
    /*添加定时器*/
    void add_timer(util_timer* timer)
    {
   
        if(!timer)
            return;
        if(!head)
        {
   
            head = tail = timer;
            return;
        }
        /*如果新的定时器expire时间小于当前头部节点
          直接将当前节点作为头部节点*/
        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;
        /*被调整的定时器在链表尾部或者
         定时器expire仍然小于next的expire,不调整*/
        if(!tmp || timer->expire < tmp->expire)
            return;
        /*被调整定时器在头部,且大于后面的expire,取出重新插入*/
        if(timer == head)
        {
   
            head = head->next;
            head->prev = NULL;
            //timer原为头节点,要清空next
            timer->next = NULL
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值