Lazy loaded image
考研
tinyWebServer 学习笔记——三、定时器处理非活跃链接
字数 4326阅读时长≈ 11 分钟
2026-10-16
2026-10-16

一、基础知识

1. 概念

  • 非活跃:指客户端与服务器建立连接后,长时间不交换数据,一直占用服务器端的文件描述符,导致连接资源的浪费;
  • 定时事件:指固定一段时间之后释放某段代码,由该段代码处理一个事件,如从内核事件表删除事件,并关闭文件描述符,释放连接资源;
  • 定时器:指利用结构体或其他形式,将多种定时器事件进行封装,一个定时器用于处理一个事件;
  • 定时器容器:使用某种容器将多个定时器组合起来,便于对定时事件统一管理,本项目中使用升序链表作为容器。
  • SIGALRM 信号:Linux 中提供了三种定时方法,本项目中使用 SIGALRM 信号,即利用 alarm 函数周期性地触发 SIGALRM 信号,信号处理函数利用管道通知主循环,主循环接收到信号后对升序链表上的定时器进行处理,若该段时间内没有数据交换,则关闭连接,释放资源;
  • 信号通知:Linux 下的信号采用异步处理机制,信号处理函数和当前进程是两条不同的执行路线,即当进程收到信号时,操作系统会中断进程,转而进入信号处理函数,完成后再关闭中断:
  • 为避免信号竞态发生,信号处理期间将不会再次触发它,为使信号不被屏蔽太久,信号处理函数需要尽可能快地执行完毕;
  • 当信号处理逻辑较复杂时,信号处理函数仅仅发送信号通知程序主循环,将信号对应的逻辑放在程序主循环中,由主循环执行信号对应的逻辑代码,这样不会导致信号屏蔽太久;
  • 统一事件源:指将信号与其他事件一样被处理,即信号处理函数利用管道将信号传递给主循环,这样信号事件与其他文件描述符都可以通过 epoll 来监测; ## 2. API
  • sigaction 结构体:记录信号的处理方式,成员如下:
  • void (*sa_handler)(int); :指向信号处理函数的函数指针;
  • void (*sa_sigaction)(int, siginfo_t*, void*); :指向信号处理函数的函数指针,三个参数能够获得更详细的信息;
  • sigset_t sa_mask; :指定信号处理函数执行期间需要屏蔽的信号;
  • int sa_flags; :指定信号处理的行为:
    • SA_RESTART :使被信号打断的系统调用自动重新发起;
    • SA_NOCLDSTOP :使父进程在它子进程暂停或继续运行时不会受到 SIGCHLD 信号;
    • SA_NOCLDWAIT :使父进程在它子进程退出时不会受到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程;
    • SA_NODEFER :使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号;
    • SA_RESETHAND :信号处理之后重新设置为默认的处理方式;
    • SA_SIGINFO :使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数;
  • SA_RESTART :使被信号打断的系统调用自动重新发起;
  • SA_NOCLDSTOP :使父进程在它子进程暂停或继续运行时不会受到 SIGCHLD 信号;
  • SA_NOCLDWAIT :使父进程在它子进程退出时不会受到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程;
  • SA_NODEFER :使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号;
  • SA_RESETHAND :信号处理之后重新设置为默认的处理方式;
  • SA_SIGINFO :使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数;
  • void (*sa_restorer)(void); :一般不使用;
  • int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); :
  • signum :标识操作的信号;
  • act :标识对信号设置新的处理方式;
  • oldact :标识信号原来的处理方式;
  • 返回值 :0 表示成功,-1 表示错误;
  • int sigfillset(sigset_t *set); :用来将参数 set 信号集初始化,然后把所有信号加入到此信号集里;
  • #define SIGALRM 14 :由 alarm 系统调用产生 timer 时钟信号;
  • #define SIGTERM 15 :终端发送的终止信号;
  • unsigned int alarm(unsigned int seconds); :设置信号传送闹钟,即用来设置信号 SIGALRM 在经过参数 seconds 秒数后发送给目前的进程。如果未设置信号 SIGALRM 的处理函数,那么 alarm() 默认处理终止进程;
  • int socketpair(int dimain, int type, int protocol, int sv[2]); :创建双向管道;
  • ssize_t send(int sockfd, const void *buf, size_t len, int flags); :当套接字发送缓冲区变满时,send 通常会阻塞,除非套接字设置为非阻塞模式,当缓冲区变满时,返回 EAGAIN 或者 EWOULDBLOCK 错误,此时可以调用 select 函数来监视何时可以发送数据; ## 3. 信号处理机制
    • 1
      流程图[1]
1
流程图[1]
  • 信号接收:由内核代理接收信号,并将其放入对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。此时信号还在队列中,进程尚不知道有信号到来;
  • 信号检测:当发现有新的信号后,对其进行处理:
  • 进程从内核态返回到用户态前进行信号检测;
  • 进程在内核态中,从睡眠状态被唤醒时进行信号检测;
  • 进程陷入内核态后,有两种情况会对信号进行检测;
  • 信号处理:
  • 调用处理函数之前,内核会将当前内核栈的内容备份拷贝到用户栈上,并且修改指令寄存器,将其指向信号处理函数;
  • 进程返回到用户态,执行相应的信号处理函数;
  • 信号处理函数执行完毕后,进程返回内核态,检查是否还有其他信号未处理;
  • 如果所有信号都处理完成,将恢复内核栈(从用户栈拷贝回来),同时修改指令寄存器,将其指向中断前的运行位置,最后回到用户态继续执行进程;

二、代码解析

1. 信号处理函数

2. 信号通知逻辑

  • 创建管道;
  • 设置信号处理函数 SIGALRM 和 SIGTERM;
  • 利用 I/O 复用系统监听管道读端文件描述符的可读事件;
  • 信息值传递给主循环,主循环根据接收到的信号值执行目标信号对应的逻辑代码;

3. 定时器

4. 定时器容器

5. 定时任务处理函数

6. 使用定时器

  • 浏览器与服务器连接时,创建连接对应的定时器,并将该定时器添加到链表上;
  • 处理异常事件时,执行定时事件,服务器关闭连接,并从链表上移除对应的定时器;
  • 处理定时信号时,将定时标志设置为 true ;
  • 处理读事件时,若某连接上发生读事件,将对应定时器向后移动,否则执行定时事件;
  • 处理写事件时,若服务器通过某连接给浏览器发送数据,将对应定时器向后移动,否则执行定时事件。

参考文献

原创存证 · 本地哈希 · be3cd9105e22...
算法:SHA-256
时间:2026-10-16
哈希:be3cd9105e22410d3c51bf4d7eeaa59031867b5fd7b3eba5e22a12b34878f9c5

该信息用于辅助证明当前内容版本,不等同于法律公证或版权登记。


评论
Loading...