前面使用epoll_wait将就绪事件从内核读取到用户数组里之后,需要处理事件。其中包括对超时事件的处理。
tk_handle_expire_timers();
tk_pq_t tk_timer;//优先队列是全局变量
void tk_handle_expire_timers(){
while(!tk_pq_is_empty(&tk_timer)){
// 更新当前时间
tk_time_update();
tk_timer_t* timer_node = (tk_timer_t*)tk_pq_min(&tk_timer);
// 如果已删则释放此节点
if(timer_node->deleted){//删除标记为已删除的节点
int rc = tk_pq_delmin(&tk_timer);
free(timer_node);
continue;
}
// 最早入队列节点超时时间大于当前时间(未超时)结束超时检查
if(timer_node->key > tk_current_msec){//没超时,返回
return;
}
// 出现了没被删但是超时的情况,调用handler处理
if(timer_node->handler){
timer_node->handler(timer_node->request);//handler将该节点标记为已删除
}
int rc = tk_pq_delmin(&tk_timer);
free(timer_node);
}
}
int tk_pq_is_empty(tk_pq_t *tk_pq){
// 通过nalloc值快速判断是否为空
return (tk_pq->nalloc == 0) ? 1 : 0;
}
void *tk_pq_min(tk_pq_t *tk_pq){
// 优先队列最小值直接返回第一个元素(指针)
if (tk_pq_is_empty(tk_pq))
return (void *)(-1);
return tk_pq->pq[1];
}
typedef struct priority_queue{//优先队列用来存储tk_timer_t时间节点,时间点越小的节点,优先级越高,每个时间结点含有request指针
void **pq;//优先队列节点指针
size_t nalloc;//优先队列已使用元素个数(实际元素个数),用来判断队列是否为空
size_t size;//优先队列可使用元素个数
tk_pq_comparator_pt comp;//堆模式,最小堆模式
// 定义了一个tk_pq_comparator_pt类型变量comp,comp必须被赋予函数指针,且该函数指针指向的函数头是int (void *pi, void *pj)型
}tk_pq_t;
typedef struct tk_timer{//定义时间结构体
size_t key; // 标记时间,应该是连接请求截止时间,key越小表示越快要超时
int deleted; // 标记是否被删除
timer_handler_pt handler; // 使用timer_handler_pt类型定义了一个变量,超时处理,tk_add_timer()函数中指定
tk_http_request_t* request; // 指向对应的request请求,方便超时时删除请求
} tk_timer_t;
// 函数指针,负责超时处理,tk_add_timer时指定处理函数
typedef int (*timer_handler_pt)(tk_http_request_t* request);
//定义了一个新类型timer_handler_pt,这种类型指向了某种函数,这种函数传入参数为:tk_http_request_t* request
//返回值类型为int
void tk_del_timer(tk_http_request_t* request) {//为节点标记做已删除
tk_time_update();
tk_timer_t* timer_node = request->timer;
// 惰性删除
// 标记为已删,在find_timer和handle_expire_timers检查队列时会删除
timer_node->deleted = 1;
}
优先队列保存时间结点。时间结点包含请求截止时间,是否被删除,以及超时处理函数,还有请求结构体。只要优先队列不为空,就不断获取优先队列对头结点,查看惰性标记,已标记则释放内存;未标记,则查看时间是否超时,超时了,则调用超时处理函数,将该结构体的惰性标记置1,并将该节点放到优先队列尾部。没超时不做任何操作。
- 查看该节点惰性标记是否为1,是则释放该节点内存空间,返回;
- 查看该节点是否超时,没有超时,则什么都不做,返回;
- 超时,则,对该节点进行惰性标记,且将该超时节点移至队尾。
int tk_pq_delmin(tk_pq_t *tk_pq){//惰性标记标完之后,要将该节点移至队尾
if(tk_pq_is_empty(tk_pq))
return 0;
exch(tk_pq, 1, tk_pq->nalloc);//将最后一个节点与头节点交换,方便删除
--tk_pq->nalloc;//队列数减一
sink(tk_pq, 1);//下溢头节点
if((tk_pq->nalloc > 0) && (tk_pq->nalloc <= (tk_pq->size - 1)/4)){//如果实际元素个数不足可使用元素个数四分之一
if(resize(tk_pq, tk_pq->size / 2) < 0)//则优先队列内存减半
return -1;
}
return 0;
}
void exch(tk_pq_t *tk_pq, size_t i, size_t j){//交换节点操作
void *tmp = tk_pq->pq[i];
tk_pq->pq[i] = tk_pq->pq[j];
tk_pq->pq[j] = tmp;
}
int sink(tk_pq_t *tk_pq, size_t k){//下溢操作
size_t j;
size_t nalloc = tk_pq->nalloc;
while((k << 1) <= nalloc){
j = k << 1;//乘2,获取孩子结点位置
if((j < nalloc) && (tk_pq->comp(tk_pq->pq[j+1], tk_pq->pq[j])))//k结点的左右孩子结点中取较小者
j++;
if(!tk_pq->comp(tk_pq->pq[j], tk_pq->pq[k]))//如果k结点小于孩子结点,则下溢结束
break;
exch(tk_pq, j, k);//k结点时间大于孩子结点,交换
k = j;
}
return k;
}
- 将该节点与尾节点交换位置
- 对尾节点实行下溢操作,该操作会将该节点与两个孩子节点中的较小者比较,与比它小则交换,否则终止交换。