TKeed之处理超时请求

前面使用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. 查看该节点惰性标记是否为1,是则释放该节点内存空间,返回;
  2. 查看该节点是否超时,没有超时,则什么都不做,返回;
  3. 超时,则,对该节点进行惰性标记,且将该超时节点移至队尾。
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;
}
  1. 将该节点与尾节点交换位置
  2. 对尾节点实行下溢操作,该操作会将该节点与两个孩子节点中的较小者比较,与比它小则交换,否则终止交换。
上一篇:XTU OJ String Hash


下一篇:AGC037C Numbers on a Circle(神奇思路)