一、基础知识
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. 信号处理函数
2. 信号通知逻辑
- 创建管道;
- 设置信号处理函数 SIGALRM 和 SIGTERM;
- 利用 I/O 复用系统监听管道读端文件描述符的可读事件;
- 信息值传递给主循环,主循环根据接收到的信号值执行目标信号对应的逻辑代码;
3. 定时器
4. 定时器容器
5. 定时任务处理函数
6. 使用定时器
- 浏览器与服务器连接时,创建连接对应的定时器,并将该定时器添加到链表上;
- 处理异常事件时,执行定时事件,服务器关闭连接,并从链表上移除对应的定时器;
- 处理定时信号时,将定时标志设置为 true ;
- 处理读事件时,若某连接上发生读事件,将对应定时器向后移动,否则执行定时事件;
- 处理写事件时,若服务器通过某连接给浏览器发送数据,将对应定时器向后移动,否则执行定时事件。
参考文献
原创存证 · 本地哈希 · be3cd9105e22...
算法:SHA-256
时间:2026-10-16
哈希:
be3cd9105e22410d3c51bf4d7eeaa59031867b5fd7b3eba5e22a12b34878f9c5该信息用于辅助证明当前内容版本,不等同于法律公证或版权登记。
- 作者:BeZer0
- 链接:https://blog.bezer0.cn/%E8%80%83%E7%A0%94/3fb0bc13-b7ac-80be-a804-cf45d3620519
- 声明:本文为原创文章,采用 CC BY-NC-SA 4.0 协议,转载请注明作者和原文链接。

