基于升序链表的定时器,主要用来定时关闭不活跃的连接,避免占用过多的系统资源。整体思路是这样:
定时器链表中长事件不活跃的fd
(file descriptor)称为超时过期的fd
那这里怎么理解超时过期呢?
比如,对于一个新来客户端连接fd
,我们设置它的时间为当前时间秒数加15秒:
//设置该用户的超时时间 time_t cur = time(NULL); timer->expire = cur + 3 * 5;
将他加入到升序链表中(链表依据时间升序)
之后如果该fd
活跃了,再将时间更新为活跃时间秒数向后加15秒,即一个活跃的fd
时间总是大于当前时间的。
举个栗子:
当前时间13:00:00 来了一个新连接fd值为10 于是值为10的fd的时间就记为13:00:15 当前时间13:00:05 值为10的fd发送了一条消息 于是值为10的fd的时间就记为13:00:20
若该fd
之后再也不活跃了,它的时间也就不会被更新,那么过了15秒后它就被认定为超时过期。所以在这里还有一个定时器,定时检查哪些fd
时间比当前时间小,该fd
便是超时过期的fd
,之后关闭该fd
。
这样看来,后来加入的,在链表后面的fd
的时间也就越大,整个链表也就是升序的。
来看一下关键性的代码:
当fd
活跃需要调整或移除某fd
的时间时:
if (events[i].events & EPOLLIN) { //可读事件 DBG("read\n"); util_timer *timer = users_timer[iSockFd].timer; //主线程读,proactor模式 if (m_pHttpUsers[iSockFd].read()) { m_pThreadPool->append(m_pHttpUsers + iSockFd); if (timer) { //调整定时器链表 adjust_timer(timer); } } else { m_pHttpUsers[iSockFd].close_conn(); if (timer) { //调整定时器链表结点 deal_timer(timer, iSockFd); } } } else if (events[i].events & EPOLLOUT) { //可写事件 DBG("out\n"); util_timer *timer = users_timer[iSockFd].timer; //主线程写,proactor模式 if (m_pHttpUsers[iSockFd].mwrite()) { if (timer) { adjust_timer(timer); } } else { m_pHttpUsers[iSockFd].close_conn(); if (timer) { deal_timer(timer, iSockFd); } } } //调整定时链表 void Server::adjust_timer(util_timer *timer) { time_t cur = time(NULL); timer->expire = cur + 3 * TIMESLOT; m_timer_lst.adjust_timer(timer); LOG_INFO("%s", "adjust timer once"); } //移除定时器链表结点 void Server::deal_timer(util_timer *timer, int sockfd) { timer->cb_func(&users_timer[sockfd]); if (timer) { m_timer_lst.del_timer(timer); } LOG_INFO("close fd %d", users_timer[sockfd].sockfd); }
而定时操作是通过linux
系统自身的alarm
系统调用,同时添加时钟信号处理函数来实现:
//设置信号处理函数 addsig(SIGALRM, sig_handler, false); alarm(TIMESLOT);
时钟信号处理函数中遍历定时器链表,关闭超时的fd
,并删除该结点。
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 cb_func(client_data *user_data) { epoll_ctl(Http::s_iEpollfd, EPOLL_CTL_DEL, user_data->sockfd, 0); assert(user_data); //关闭该fd close(user_data->sockfd); Http::s_iUserCount--; }
以上便是整个基于升序链表定时器的核心,剩下的便是对链表的基本操作。
更多实现细节,可以在https://gitee.com/gao-yuelong/web-server中查看。