首页 > 其他分享 >8. 定时器 / 信号处理

8. 定时器 / 信号处理

时间:2024-11-04 18:20:06浏览次数:3  
标签:定时器 epoll 信号处理 timer lst 事件 Utils

有关定时器的详细内容,见10. 定时器

简而言之,web 服务器需要处理定时事件,如定期检测一个客户连接的活动状态。服务器程序通常管理着众多定时事件,有效地组织这些定时事件,使其在预期的时间被触发且不影响服务器的主要逻辑,对于服务器的性能有至关重要的影响。为此,我们要将每个定时事件分别封装成定时器,并使用某种容器类数据结构,如链表、排序链表、时间轮,将所有定时器串联起来,以实现对定时事件的统一管理。

项目中的定时器实现在 timer 文件夹中:

timer
  |------ lst_timer.cpp
  |------ lst_timer.h
  |------ README.md

主要功能是实现了一个基于双向链表的定时器管理系统(sort_timer_lst类)。下面具体分析一下每个类的作用,lst_timer.h 中主要定义了三个类:

lst_timer.h
  |------ util_timer
  |------ sort_timer_lst
  |------ Utils
  1. util_timer 类,主要实现了一个定时器类,类的内部成员包括一个定时器的过期时间,指向前一个定时器的指针和指向后一个定时器的指针;
  2. sort_timer_lst,这是一个双向链表,用于管理定时器,链表的每一个节点代表一个定时器,定时器按照到期时间升序排序,类的操作包括:添加、删除定时器,调整定时器在链表中的位置,tick()函数:用于删除到期的定时器节点
  3. Utils类,用于管理定时任务、信号处理等,在服务器中承担了辅助管理功能,为高效的事件驱动服务器提供了一些重要的工具和支持,比如两个静态成员变量:
static int *u_pipefd;
static int u_epollfd;

前者是一个用于存储管道的文件描述符数组,在信号处理函数 sig_handler 中,将接收到的信号写入 u_pipefd[1] ,而事件主循环监听管道另一端u_pipefd[0] 的可读事件,以异步处理信号

后者用于存储 epoll实例的文件描述符, 整个服务器中共享一个 epoll实例,通过 u_epollfd 监控所有连接的事件。

除此之外还有一些其他函数,比如:

  • addfd 用于将 fd 注册到内核事件表;
  • sig_handler 用于将接收到的信号写入管道;
  • addsig 用于设置 sig 信号的信号处理函数;
  • timer_handler 用于调用定时器链表的 tick(),删除到期的定时器,并重置定时器信号。

epoll 实例和事件存储

这个应该是在下一节描述的内容,但是因为这章的内容涉及到了这部分,所以就提前写了。

当调用 epoll_createepoll_create1 创建 epoll 实例时,系统会分配一个内核空间的数据结构,这一数据结构被称为内核事件表(epoll实例),用于管理和存储事件。

使用 epoll_ctl 函数可以将文件描述符和对应的事件注册到 epoll 实例中。例如,将套接字 fd 注册到 epoll 实例 epollfd 时,可以设置需要监听的事件类型,如 EPOLLIN(可读)、EPOLLOUT(可写)、EPOLLERR(错误)等。

epoll 的核心机制是事件触发。当文件描述符的状态发生(边缘触发)变化且与注册的事件匹配时,epoll 会将该事件标记为就绪。调用 epoll_wait 时,epoll 会返回所有当前就绪的事件,供应用程序处理。

epoll 实例中会存储所有注册的文件描述符和事件,并在事件就绪时通过 epoll_wait 返回。这样的设计让 epoll 能够高效地处理高并发连接,并且仅在事件发生时返回,避免了不必要的轮询。

为什么要重置定时器?

/* 处理定时任务,并重新设置定时器,以确保定时信号(SIGALRM)能够持续触发 */
void Utils::timer_handler() {
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGALRM);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    m_timer_lst.tick();                         /* 调用定时器链表 m_timer_lst 的 tick 方法,处理所有到期的定时器 */
    alarm(m_TIMESLOT);                          /* 使用 alarm 函数重新设置定时器,使得下一个 SIGALRM 信号在 m_TIMESLOT 秒后触发 */

    sigprocmask(SIG_UNBLOCK, &mask, NULL);
}

Utils::timer_handler 中重新设置定时器的目的是持续触发 SIGALRM 信号

  1. 定时任务的周期性执行
  • timer_handler 中的 m_timer_lst.tick() 用于处理到期的定时器任务。通过调用 tick,可以遍历定时器链表,检查并执行所有到期的定时器任务
  • 每当 SIGALRM 信号触发时,都会调用 timer_handler 进行一次定时任务处理。如果不重新设置定时器,SIGALRM 信号只会触发一次,定时任务将无法周期性执行。
  1. 使用 alarm(m_TIMESLOT) 来重新设置定时器
  • alarm(m_TIMESLOT) 将使 SIGALRM 信号在 m_TIMESLOT 秒后再次触发。
  • 通过每次在 timer_handler 中重新调用 alarm,实现了一个循环定时机制:定时器会在每 m_TIMESLOT 秒触发 SIGALRM 信号,系统捕获到信号后,timer_handler 会被调用,再次处理到期任务并重新设定定时器。

前置声明

class Utils;
/* 定时器回调函数 */
void cb_func(client_data *user_data) {

    if (!user_data) {
        std::cerr << "cb_func received null user_data" << std::endl;
        return;
    }

    /* 从 epoll 中删除文件描述符 */
    if (epoll_ctl(Utils::u_epollfd, EPOLL_CTL_DEL, user_data->sockfd, nullptr) == -1) {
        std::cerr << "epoll_ctl DEL failed: " << strerror(errno) << std::endl;
    }

    assert(user_data);

    if (close(user_data->sockfd) == -1) {
        std::cerr << "close failed: " << strerror(errno) << std::endl;
    }

    http_conn::m_user_count--;                  /* 减少用户计数 */
}

在函数 cb_func 上方声明 class Utils; 的目的是为了向编译器前置声明Utils 类。这样做的原因是:Utils::u_epollfdUtils 的静态成员,静态成员的访问不依赖于 Utils 类的实例,因此可以直接通过 Utils::u_epollfd 访问这个静态变量,前置声明通常用于减少编译依赖。前置声明告诉编译器,Utils 是一个类,但不需要立即包含其完整的定义。这样做可以减少编译时间。

shutdownclose的区别

  • shutdown 用于关闭连接的传输方向,而不直接释放文件描述符。可以通过设置不同的标志来选择关闭的方向。
  • close 是释放文件描述符的操作,无论它是套接字、文件还是其他资源。当文件描述符的引用计数降到零时,close 会彻底终止套接字连接。

统一事件源

什么是统一事件源?

统一事件源是一种设计模式/机制,用于集中处理不同类型的事件,如 I/O 事件、信号事件、定时器事件等。在统一事件源的设计中,所有类型的事件(如网络连接、定时任务、信号等)都被封装成文件描述符,并被统一注册到 epoll 等 I/O 多路复用接口上。这样,程序只需监听 epoll 的事件,不论是网络连接、信号还是定时任务等事件,均可以通过 epoll_wait 等接口统一管理,避免了单独为每种事件类型编写独立的处理逻辑。


统一事件源的好处?

通过将不同类型的事件统一到同一个事件处理机制(通常是epoll,可以简化事件的管理和处理流程,从而提升系统的性能和可维护性。

改进

  1. Utils::timer_handler()函数中,如果 SIGALRM 信号在 tick 方法执行期间再次触发,可能导致定时器处理函数被重入,导致数据竞争或逻辑错误。
    改进: 在处理定时器前,临时屏蔽 SIGALRM 信号,确保 tick 方法不会被中断。
void Utils::timer_handler() {
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGALRM);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    m_timer_lst.tick();                         /* 调用定时器链表 m_timer_lst 的 tick 方法,处理所有到期的定时器 */
    alarm(m_TIMESLOT);                          /* 使用 alarm 函数重新设置定时器,使得下一个 SIGALRM 信号在 m_TIMESLOT 秒后触发 */

    sigprocmask(SIG_UNBLOCK, &mask, NULL);
}
  1. show_error 函数用于向客户端发送错误信息,并随后关闭连接,send 可能会因为网络问题而失败(返回 -1),添加检查可以帮助诊断发送失败的情况。在调用 close 之前,可以使用 shutdown(connfd, SHUT_WR) 先优雅关闭连接的写传输方向,然后再关闭连接,避免数据丢失并确保错误信息成功发送。
/* 向客户端发送错误信息,并关闭相应的连接 */
void Utils::show_error(int connfd, const char *info)
{
    ssize_t bytes_sent = send(connfd, info, strlen(info), 0);

    if (bytes_sent == -1) {
        std::cerr << "send failed: " << strerror(errno) << std::endl;
    }
              
    /* 同时关闭连接的读和写两个方向 */
    if (shutdown(connfd, SHUT_RDWR) == -1) {
        std::cerr << "shutdown failed: " << strerror(errno) << std::endl;
    }

    if (close(connfd) == -1) {
        std::cerr << "close failed: " << strerror(errno) << std::endl;
    }
}

这样做的好处有:

  • 如果服务器在 send 发送数据后立刻调用 close,由于网络传输存在延迟,客户端接收数据的速度可能较慢。服务器在客户端完成接收之前关闭连接,剩余数据可能会丢失。使用 shutdown(connfd, SHUT_WR),服务器可以等待客户端确认已接收完所有数据,避免因直接 close 导致的传输中断。
  • TCP 连接是双向的,shutdown 可以确保双方同步地进行连接关闭的过程。而直接 close 时,连接会立即释放,可能对方仍认为连接有效,导致“半关闭”状态。使用 shutdown(connfd, SHUT_WR) 表示服务器不再发送数据,但可以接收数据。这样,服务器可以等待客户端的 FIN,完成四次握手的完整关闭,确保数据安全传输。

标签:定时器,epoll,信号处理,timer,lst,事件,Utils
From: https://blog.csdn.net/Teriri_/article/details/143490797

相关文章

  • 梁山派入门指南4——定时器使用详解,包括定时器中断、PWM产生、输入捕获测量频率
    梁山派入门指南4——定时器使用详解,包括定时器中断、PWM产生、输入捕获测量频率1.定时器概览2.基本定时器2.1基本定时器介绍2.2梁山派上的基本定时器开发2.2.1.了解梁山派上的基本定时器资源(实际上我们以及在上面了解过了)2.2.2.配置定时器2.2.3.编写定时器中断服务......
  • 数字信号处理Python示例(3)生成三相正弦信号
    文章目录前言一、三相正弦信号的表示二、生成三相正弦信号的Python代码三、三相正弦信号的图示与分析四、生成幅度不相等的三相正弦信号的Python代码五、幅度不相等的三相正弦信号的图示与分析写在后面的话前言首先给出三相正弦信号的数学表达式,并给出生成三相正弦......
  • 初始JavaEE篇——多线程(7):定时器、CAS
    找往期文章包括但不限于本期文章中不懂的知识点:个人主页:我要学编程程(ಥ_ಥ)-CSDN博客所属专栏:JavaEE目录定时器的使用定时器的原理 模拟实现定时器 CAS介绍CAS的应用场景 解析AtomicInteger类实现自旋锁CAS的缺陷:ABA问题 现在我们来学习最后一个多线程......
  • 数字信号处理:自动增益控制(AGC)
    自动增益控制::自动增益控制(AutomaticGainControl,AGC)是一种信号处理技术,用于在接收端调整输入信号的增益(或放大系数),以保持信号在一个合适的强度范围内,从而防止信号过弱或过强。工作原理:AGC电路通过测量接收到的信号强度,将该强度与一个预设的理想水平进行比较,并根据两者的......
  • FPGA数字信号处理—1S上报一次解析数据
    数字信号结果处理完毕之后,需要定时上报,利用计数器完成定时上报;moduleError_bit_report(inputwireclk,//时钟信号inputwirerst_n,//复位信号,低有效inputwireerror_compare_ena,//误码比较使......
  • Android使用timer和thread实现定时器
    说明:两种方法实现android定时器,定时执行任务第一种方式:step1:packagecom.example.iosdialogdemo;importandroid.os.Bundle;importandroid.os.Handler;importandroidx.appcompat.app.AppCompatActivity;importjava.util.Timer;publicclassTimerActivityextends......
  • zynq7000 TTC定时器中断
    Note:本次使用pynqz2board作为硬件环境一.Zynq定时器概述在zynq7000中,定时器一共分为4个部分,参考手册:Ug585每颗armA9含有一个私有定时器以及一个看门狗定时器系统含有一个全局看门狗定时器系统含有一个全局定时器系统含有两个TTC模块,每个模块含有三路定时器从......
  • 2024年信号处理与神经网络应用国际学术会议(SPNNA 2024) 2024 International Conferenc
    @目录一、会议详情二、重要信息三、大会介绍四、出席嘉宾五、征稿主题六、咨询一、会议详情二、重要信息大会官网:https://ais.cn/u/vEbMBz提交检索:EICompendex、IEEEXplore、Scopus三、大会介绍2024年信号处理与神经网络应用国际学术会议(SPNNA2024)将于2024年12月13日......
  • 【JavaEE】【多线程】定时器
    目录一、定时器简介1.1Timer类1.2使用案例二、实现简易定时器2.1MyTimerTask类2.2实现schedule方法2.3构造方法2.4总代码2.5测试一、定时器简介定时器:就相当于一个闹钟,当我们定的时间到了,那么就执行一些逻辑。1.1Timer类Java的标准库中提供了在java.ut......
  • zynq7000使用私有定时器中断
    Zynq-7000系列SoC(SystemonChip)的定时器系统是由几个不同的定时器模块组成的,这些定时器可以满足广泛的嵌入式应用需求。主要包括:全局定时器(GlobalTimer)特点:全局定时器是一个64位的计时器,存在于Cortex-A9处理器内核中,提供一个全局的时间基准。用途:主要用于需要......