前言
项目简介:
Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器.
- 使用 线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和模拟Proactor均实现) 的并发模型
- 使用状态机解析HTTP请求报文,支持解析GET和POST请求
- 访问服务器数据库实现web端用户注册、登录功能,可以请求服务器图片和视频文件
- 实现同步/异步日志系统,记录服务器运行状态
- 经Webbench压力测试可以实现上万的并发连接数据交换
lst_timer.cpp用于配置web服务器的定时器,包括但不限于定时器链表的处理、信号和管道、epoll的处理,原项目地址的注释较少不适合初学者,于是我将每行都加上了注释帮助大家更好的理解:
#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;
}
}
//添加新的定时器
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);
}
else
{
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;
}
//将内核事件表注册读事件,监听模式,选择开启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;
else
event.events = EPOLLIN | EPOLLRDHUP;
//开启epoll的oneshot
if (one_shot)
event.events |= EPOLLONESHOT;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking(fd);
}
// 信号处理函数,用于处理系统信号(如SIGTERM、SIGINT等)
void Utils::sig_handler(int sig)
{
// 为保证函数的可重入性,保存当前的errno值(errno表示最近一次系统调用的错误代码)
// 信号处理函数可能会改变errno的值,所以需要保存原来的errno,防止影响主程序的逻辑
int save_errno = errno;
// 将接收到的信号值保存到msg变量中
int msg = sig;
// 使用send函数将信号值通过管道发送给主程序
// u_pipefd[1]是管道的写端,(char *)&msg是要发送的数据的地址,1是发送的数据长度(一个字节),0表示没有特殊的发送选项
// 这种方式可以避免信号处理函数直接在其中执行复杂逻辑,而是通知主程序来处理信号,避免信号安全性问题
send(u_pipefd[1], (char *)&msg, 1, 0);
// 恢复原来的errno值,保证信号处理函数结束后,程序的errno值不变
errno = save_errno;
}
// 设置信号处理函数
// sig: 要处理的信号编号,例如 SIGINT、SIGTERM 等
// handler: 信号处理函数的指针,指定信号触发时要执行的函数
// restart: 是否自动重启被信号中断的系统调用,若为 true 则设置 SA_RESTART 标志
void Utils::addsig(int sig, void(handler)(int), bool restart)
{
// 定义一个 sigaction 结构体,用于描述信号处理的行为
struct sigaction sa;
// 使用 memset 将 sa 清零,以确保结构体中的所有字段被初始化为 0
memset(&sa, '\0', sizeof(sa));
// 设置信号处理函数,将 sa_handler 指向传入的处理函数 handler
sa.sa_handler = handler;
// 如果 restart 为 true,则设置 SA_RESTART 标志
// SA_RESTART 标志表示当信号中断某些系统调用时,系统会自动重新启动被中断的系统调用
if (restart)
sa.sa_flags |= SA_RESTART;
// 使用 sigfillset 函数将 sa_mask 设置为阻塞所有信号
// 这意味着在处理该信号时,其他所有的信号都将被阻塞,防止信号处理函数被其他信号中断
sigfillset(&sa.sa_mask);
// 使用 sigaction 系统调用为指定的信号(sig)设置处理函数
// 第一个参数是信号编号,第二个参数是指向 sigaction 结构体的指针,表示要设置的新信号处理方式
// 第三个参数为 NULL,表示不关心旧的信号处理方式
// assert 用于检查 sigaction 是否成功执行,若返回值为 -1 表示设置失败
assert(sigaction(sig, &sa, NULL) != -1);
}
// 定时处理任务,重新设置定时器以不断触发 SIGALRM 信号
void Utils::timer_handler()
{
// 调用定时器链表的 tick() 方法,处理到期的定时器任务
// m_timer_lst 是一个定时器链表或管理器,tick() 方法通常用于遍历所有定时器,
// 找到并执行已经到期的任务,如关闭超时的连接、释放资源等
m_timer_lst.tick();
// 重新设置定时器,以使 SIGALRM 信号在 m_TIMESLOT 秒后再次触发
// alarm() 函数用于设定一个闹钟时间,m_TIMESLOT 是预设的时间间隔(以秒为单位)
// 在 m_TIMESLOT 秒后,将触发 SIGALRM 信号,这个信号通常会被定时处理函数捕获并执行相应操作
alarm(m_TIMESLOT);
}
// 定时处理任务,重新设置定时器以不断触发 SIGALRM 信号
void Utils::timer_handler()
{
// 调用定时器链表的 tick() 方法,处理到期的定时器任务
// 找到并执行已经到期的任务,如关闭超时的连接、释放资源等
m_timer_lst.tick();
// 重新设置定时器,以使 SIGALRM 信号在 m_TIMESLOT 秒后再次触发
// alarm() 函数用于设定一个闹钟时间,m_TIMESLOT 是预设的时间间隔(以秒为单位)
// 在 m_TIMESLOT 秒后,将触发 SIGALRM 信号,这个信号通常会被定时处理函数捕获并执行相应操作
alarm(m_TIMESLOT);
}
int *Utils::u_pipefd = 0;
int Utils::u_epollfd = 0;
class Utils;
// 回调函数,用于处理客户端连接的超时或关闭
// user_data: 指向与客户端相关的数据信息结构体
void cb_func(client_data *user_data)
{
// 从 epoll 实例中删除用户数据对应的文件描述符 sockfd
// Utils::u_epollfd 是一个全局的 epoll 文件描述符,代表 epoll 实例
// EPOLL_CTL_DEL 是操作类型,表示删除一个 epoll 监听的文件描述符
// user_data->sockfd 是要删除的客户端 socket 文件描述符
epoll_ctl(Utils::u_epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0);
// 断言检查 user_data 是否为非空指针,以确保其有效性
// 如果 user_data 是 NULL,程序将在此处终止运行
assert(user_data);
// 关闭客户端的 socket 连接,释放资源
close(user_data->sockfd);
// 减少当前活跃连接的用户计数
// m_user_count 是静态变量,记录当前活跃的客户端连接数
http_conn::m_user_count--;
}