添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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

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();
    //添加定时器,内部调用私有成员add_timer
    //若当前链表中只有头尾节点,直接插入
    //否则,将定时器按升序插入
    void add_timer(util_timer *timer);
    //adjust_timer函数,当定时任务发生变化,调整对应定时器在链表中的位置
    //客户端在设定时间内有数据收发,则当前时刻对该定时器重新设定时间,这里只是往后延长超时时间
    //被调整的目标定时器在尾部,或定时器新的超时值仍然小于下一个定时器的超时,不用调整
    //否则先将定时器从链表取出,重新插入链表
    void adjust_timer(util_timer *timer);
    //del_timer函数将超时的定时器从链表中删除
    //常规双向链表删除结点
    void del_timer(util_timer *timer);
    //定时任务处理函数
    void tick();
private:
    void add_timer(util_timer *timer, util_timer *lst_head);
    util_timer *head;
    util_timer *tail;
class Utils
public:
    Utils() {}
    ~Utils() {}
    void init(int timeslot);
    //对文件描述符设置非阻塞
    int setnonblocking(int fd);
    //将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT
    void addfd(int epollfd, int fd, bool one_shot, int TRIGMode);
    //信号处理函数
    static void sig_handler(int sig);
    //设置信号函数
    void addsig(int sig, void(handler)(int), bool restart = true);
    //定时处理任务,重新定时以不断触发SIGALRM信号
    void timer_handler();
    void show_error(int connfd, const char *info);
public:
    static int *u_pipefd;
    sort_timer_lst m_timer_lst;
    static int u_epollfd;
    int m_TIMESLOT;
void cb_func(client_data *user_data);
#endif
#include "lst_timer.h"
#include "../http/http_conn.h"
sort_timer_lst::sort_timer_lst()
    head = NULL;
    tail = NULL;
//常规销毁链表
sort_timer_lst::~sort_timer_lst()
    util_timer *tmp = head;
    while (tmp)
        head = tmp->next;
        delete tmp;
        tmp = head;
//添加定时器,内部调用私有成员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::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);
    //被调整定时器在内部,将定时器取出,重新插入
        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;
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)
            head->prev = NULL;
        delete tmp;
        tmp = 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;
void Utils::init(int timeslot)
    m_TIMESLOT = timeslot;
//对文件描述符设置非阻塞
int Utils::setnonblocking(int fd)
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
//将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT
void Utils::addfd(int epollfd, int fd, bool one_shot, int TRIGMode)
    epoll_event event;
    event.data.fd = fd;
    if (1 == TRIGMode)
        event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
        event.events = EPOLLIN | EPOLLRDHUP;
    if (one_shot)
        event.events |= EPOLLONESHOT;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
//信号处理函数
//信号处理函数中仅仅通过管道发送信号值,不处理信号对应的逻辑,缩短异步执行时间,减少对主程序的影响。
void Utils::sig_handler(int sig)
    //为保证函数的可重入性,保留原来的errno
    //可重入性表示中断后再次进入该函数,环境变量与之前相同,不会丢失数据
    int save_errno = errno;
    int msg = sig;
    //将信号值从管道写端写入,传输字符类型,而非整型
    send(u_pipefd[1], (char *)&msg, 1, 0);
    //将原来的errno赋值为当前的errno
    errno = save_errno;
//设置信号函数
//项目中设置信号函数,仅关注SIGTERM和SIGALRM两个信号
void Utils::addsig(int sig, void(handler)(int), bool restart)
    //创建sigaction结构体变量
    struct sigaction sa;
    memset(&sa, '\0', sizeof(sa));
    //信号处理函数中仅仅发送信号值,不做对应逻辑处理
    sa.sa_handler = handler;
    if (restart)
        sa.sa_flags |= SA_RESTART;
    //将所有信号添加到信号集中
    sigfillset(&sa.sa_mask);
    //执行sigaction函数
    assert(sigaction(sig, &sa, NULL) != -1);
//定时处理任务,重新定时以不断触发SIGALRM信号
void Utils::timer_handler()
    m_timer_lst.tick();
    alarm(m_TIMESLOT);
void Utils::show_error(int connfd, const char *info)
    send(connfd, info, strlen(info), 0);
    close(connfd);
int *Utils::u_pipefd = 0;
int Utils::u_epollfd = 0;
class Utils;
void cb_func(client_data *user_data)
    epoll_ctl(Utils::u_epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0);
    assert(user_data);
    close(user_data->sockfd);
    http_conn::m_user_count--;
                    TinyWebServer代码详细讲解(timer模块)基础知识设计思路定时器触发方式定时器的数据结构代码详解基础知识非活跃,是指客户端(这里是浏览器)与服务器端建立连接后,长时间不交换数据,一直占用服务器端的文件描述符,导致连接资源的浪费。定时事件,是指固定一段时间之后触发某段代码,由该段代码处理一个事件,如从内核事件表删除事件,并关闭文件描述符,释放连接资源。定时器,是指利用结构体或其他形式,将多种定时事件进行封装起来。具体的,这里只涉及一种定时事件,即定期检测非活跃连接,这里将该定时事件与连接
				
TinyWebServer代码详细讲解(http模块)http模块设计思路http_conn.cppread业务函数集process_read函数parse_request_line函数总结 这里的参照的代码是https://github.com/qinguoyi/TinyWebServer 对于原代码的不足之处,我会在之后的文章中给出改进代码 在笔者fork的这版中,原代码作者对于代码作出了更细化的分类 细节问题可以参考《APUE》《Linux高性能服务器编程》或者我之前的博客 毋庸置疑,http模块
Web服务器---TinyWebServer代码详细讲解(log模块)基础知识单例模式经典的线程安全懒汉模式局部静态变量之线程安全懒汉模式饿汉模式异步日志block_queue.h 代码解析log.cpp代码解析 log是日志模块,一个合格的服务器当然少不了日志来记录错误异常等等信息。我们想设计一个日志模块,他能顺利写日志但是又不要占用主线程时间去写,所以我们设计异步写日志的模块。 日志,由服务器自动创建,并记录运行状态,错误信息,访问数据的文件。 同步日志,日志写入函数与工作线程串行执行,由于涉
此博客记录对于TinyWebServer项目的学习,并根据自己的理解做出些许更改。 原项目地址:https://github.com/qinguoyi/TinyWebServer 网络程序通常需要处理定时事件,例如定期检测客户连接的活动状态,因为非活跃连接占用了连接资源,需要定期检测释放非活跃连接。通常将定时事件封装为定时器类,然后使用排序链表、时间轮等数据结构管理定时器。 linux提供了三种定时方法, 1.socket选项SO_RCVTIMEO和SO_SNDTIMEO 2.SIGALRM信
文章目录项目介绍一、服务器编程基本框架一、WebServer类详解1.初始化2.启动WebServer二、I/O处理的具体流程三、线程池四、HTTP请求报文解析与响应报文生成1.请求报文2.响应报文3.process()函数五、缓冲区六、定时器1.定时器的组成2.定时器的管理七、数据库连接池八、压力测试总结 Linux下C++轻量级Web服务器,使用线程池+非阻塞socket+epoll(ET模式)+事件处理(Reactor)的并发模型 使用状态机解析HTTP报文请求,支持解析GET、P
首先来了解一下GET与POST GET是想获取server数据,将请求的数据添加到URL中,以?分割URL和传输数据,参数值之间以&相连,因此GET不安全。GET产生一个TCP数据包,浏览器将HTTP header和data一起发送给server,server响应状态码200(请求正常处理完毕)(返回数据) POST是想修改server数据, public class SecurityDemo { private static int count = 0; public static void main(String[] args) throws InterruptedException { for (int i = 0... self-timer.js是用于javascript的轻量级回调运行器库。 self-timer.js的名称源自“ self-timer”,它是相机上的设备。 您可以在要执行时的时间运行回调,就像self-timer一样。 例如,如果今天是工作日(星期一至星期五),请运行一些功能, self-timer.js可以编写simple 。 var st = new SelfTimer ( new Date ( ) ) ; st . on ( ) . Weekdays ( function ( ) { // callback console . log ( "this method run on Monday to Friday!" ) ; } ) ; 如果您使用服务器端模板(exp:nunjucks,EJS ..等等)。 您可以使用
eureka.server.eviction-interval-timer-in-ms 是一个配置属性,它用于指定 Eureka 服务器清理过期实例的时间间隔。具体来说,它表示清理任务执行的时间间隔,单位是毫秒(ms)。 当 Eureka 服务器运行时,它会维护一个注册表,记录着各个服务实例的信息。如果一个服务实例在一段时间内没有发送心跳给 Eureka 服务器,Eureka 会将其标记为过期实例,并在清理任务中将其从注册表中删除。 eureka.server.eviction-interval-timer-in-ms 属性可以用来配置清理任务的执行间隔。较短的间隔意味着 Eureka 服务器会更加及时地清理过期实例,但同时也会增加服务器的负载。较长的间隔则会减少服务器负载,但可能导致过期实例在注册表中停留更久。 根据具体的需求和系统负载情况,你可以根据需要调整这个属性的值。默认情况下,该属性的值为 60,000 毫秒(即 1 分钟)。