Web服务器---TinyWebServer代码详细讲解(timer模块)

TinyWebServer代码详细讲解(timer模块)

基础知识

非活跃,是指客户端(这里是浏览器)与服务器端建立连接后,长时间不交换数据,一直占用服务器端的文件描述符,导致连接资源的浪费。
定时事件,是指固定一段时间之后触发某段代码,由该段代码处理一个事件,如从内核事件表删除事件,并关闭文件描述符,释放连接资源。
定时器,是指利用结构体或其他形式,将多种定时事件进行封装起来。具体的,这里只涉及一种定时事件,即定期检测非活跃连接,这里将该定时事件与连接资源封装为一个结构体定时器。
定时器容器,是指使用某种容器类数据结构,将上述多个定时器组合起来,便于对定时事件统一管理。具体的,项目中使用升序链表将所有定时器串联组织起来。

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

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

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

具体的,利用alarm函数周期性地触发SIGALRM信号,信号处理函数利用管道通知主循环,主循环接收到该信号后对升序链表上所有定时器进行处理,若该段时间内没有交换数据,则将该连接关闭,释放所占用的资源。

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

设计思路

定时器的功能是踢出无反应的客户端。

定时器触发方式

这个问题其实很有意思,通常我们以前学习到处理信号的方式是把信号发生之后的要处理的逻辑全部放在信号的回调函数中。在这时候我们也许忽略了一个事实:在Linux环境下当我们回调一个信号的回调函数时候这段时间系统会忽略至少这个同样的信号(这是当然的不然就有可能死循环等出错),那么我们为了不让这些被忽略的信号被忽略太久,我们得想尽办法尽量缩短这个回调函数的执行时间。那么怎样才能做到这样呢?

一个理所当然的思路是:把回调函数的逻辑搬到主函数执行。那么怎样做到这一点:统一事件源。原理很简单,这时我们的信号回调函数不要处理逻辑,而是在回调函数中通过管道给主函数发送信息,那么当主函数监听到读时间并且判断到是从管道读端来的,那就知道这个信号到了我主函数应该处理了。
在这里插入图片描述

定时器的数据结构

在游双的《高性能服务器编程》这本书里面写到三种定时器的存储结构:链表、时间轮、时间堆。这个TinyWebServer使用的是最好实现的链表定时器。

我们有一个定时器结点类util_timer,每个结点表示一个客户连接,它保存了双向链表的前后指针,客户数据client_data和回调函数。如果我们判断到这个结点长时间无反应,所以我们调用这个回调函数传入client_data,然后回调函数就会把这个客户断开,并且做一些善后工作。

我们还有链表类sort_timer_lst,这个链表是一个时间递增的结点链表,即从链表头到尾这个客户的最后一次反应时间是递增的。这个链表类当然有插入和删除结点函数。并且还有adjust_timer调整链表位置函数,作用是当一个客户有了反应,那么我们需要更新他的最后一次反应时间,那么为了维护链表的递增特性,我们需要这么一个调整位置的函数。此外,这个类还有一个检查函数(定时清扫),作用是我们上文提到统一了事件源,把信号回调函数逻辑搬到主函数执行,所以这个定时清扫检查逻辑就是在这个检查函数。主函数判断到信号来了,就执行这个函数进行检查链表中长时间无反应的结点进行清扫。

代码详解

#ifndef LST_TIMER
#define LST_TIMER

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/uio.h>

#include <time.h>
#include "../log/log.h"

//连接资源结构体成员需要用到定时器类
//需要前向声明
class util_timer;

//开辟用户socket结构 对应于最大处理fd
//连接资源
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;
};

//项目中的定时器容器为带头尾结点的升序双向链表,具体的为每个连接创建一个定时器,
//将其添加到链表中,并按照超时时间升序排列。执行定时任务时,将到期的定时器从链表中删除。
//从实现上看,主要涉及双向链表的插入,删除操作,其中添加定时器的事件复杂度是O(n),
//删除定时器的事件复杂度是O(1)。
class sort_timer_lst
{
   
public:
    sort_timer_lst();
    ~sort_timer_lst()
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值