tinywebserver heaptimer模块 事件定时器

项目源网站 tinywebserver。这个模块是事件定时器,这个模块是干啥用的?当客户端来了一个连接,但是我不发送请求,就这样干耗着,这个时候服务端就应该断开连接,给其他客户端的请求。

void WebServer::Start() {
    int timeMS = -1;  /* epoll wait timeout == -1 无事件将阻塞 */
    if(!isClose_) { LOG_INFO("========== Server start =========="); }
    while(!isClose_) {
        if(timeoutMS_ > 0) {
            timeMS = timer_->GetNextTick();
        }
        int eventCnt = epoller_->Wait(timeMS);
        for(int i = 0; i < eventCnt; i++) {
            /* 处理事件 */
            int fd = epoller_->GetEventFd(i);
            uint32_t events = epoller_->GetEvents(i);
            if(fd == listenFd_) {
                DealListen_();
            }
            else if(events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
                assert(users_.count(fd) > 0);
                CloseConn_(&users_[fd]);
            }
            else if(events & EPOLLIN) {
                assert(users_.count(fd) > 0);
                DealRead_(&users_[fd]);
            }
            else if(events & EPOLLOUT) {
                assert(users_.count(fd) > 0);
                DealWrite_(&users_[fd]);
            } else {
                LOG_ERROR("Unexpected event");
            }
        }
    }
}

这是主函数,GetNextTick就是询问当前有没有超时的事件需要处理,有的话就处理了,在连接客户端的函数里

void WebServer::AddClient_(int fd, sockaddr_in addr) {
    assert(fd > 0);
    users_[fd].init(fd, addr);
    if(timeoutMS_ > 0) {
        timer_->add(fd, timeoutMS_, std::bind(&WebServer::CloseConn_, this, &users_[fd]));
    }
    epoller_->AddFd(fd, EPOLLIN | connEvent_);
    SetFdNonblock(fd);
    LOG_INFO("Client[%d] in!", users_[fd].GetFd());
}

就是添加了一个关闭客户端的事件。

代码

头文件

/*
 * @Author       : mark
 * @Date         : 2020-06-17
 * @copyleft Apache 2.0
 */
#ifndef HEAP_TIMER_H
#define HEAP_TIMER_H

#include <queue>
#include <unordered_map>
#include <time.h>
#include <algorithm>
#include <arpa/inet.h>
#include <functional>
#include <assert.h>
#include <chrono>
#include "../log/log.h"

typedef std::function<void()> TimeoutCallBack;
typedef std::chrono::high_resolution_clock Clock; // 最高精度的时钟,使用的是 CPU 计时器或其他高精度硬件定时器
typedef std::chrono::milliseconds MS;
typedef Clock::time_point TimeStamp;

struct TimerNode
{
    int id;
    TimeStamp expires;
    TimeoutCallBack cb;
    bool operator<(const TimerNode &t)
    {
        return expires < t.expires;
    }
};
class HeapTimer
{
public:
    HeapTimer() { heap_.reserve(64); }

    ~HeapTimer() { clear(); }

    void adjust(int id, int newExpires);

    void add(int id, int timeOut, const TimeoutCallBack &cb);

    void doWork(int id);

    void clear();

    void tick();

    void pop();

    int GetNextTick();

private:
    void del_(size_t i);

    void siftup_(size_t i);

    bool siftdown_(size_t index, size_t n);

    void SwapNode_(size_t i, size_t j);

    std::vector<TimerNode> heap_; // 用vector实现堆,最小堆

    std::unordered_map<int, size_t> ref_;
};

#endif // HEAP_TIMER_H

源文件

/*
 * @Author       : mark
 * @Date         : 2020-06-17
 * @copyleft Apache 2.0
 */
#include "heaptimer.h"

// 原版的
    // 这个地方是有bug的
    // 当i=0时,j会溢出,size_t 是unsigned long
    // 运行的时候没有bug,是因为编译器的优化
// void HeapTimer::siftup_(size_t i)
// {
//     assert(i >= 0 && i < heap_.size());
//     size_t j = (i - 1) / 2;
//     while (j >= 0)
//     {
//         if (heap_[j] < heap_[i])
//         {
//             break;
//         }
//         SwapNode_(i, j);
//         i = j;
//         j = (i - 1) / 2;
//     }
// }


void HeapTimer::siftup_(size_t i)
{
    assert(i >= 0 && i < heap_.size());
    int j = (static_cast<int>(i) - 1) / 2;
    while (j >= 0)
    {
        if (heap_[j] < heap_[i])
        {
            break;
        }
        SwapNode_(i, j);
        i = j;
        j = (i - 1) / 2;
    }
}

void HeapTimer::SwapNode_(size_t i, size_t j)
{
    assert(i >= 0 && i < heap_.size());
    assert(j >= 0 && j < heap_.size());
    std::swap(heap_[i], heap_[j]);
    ref_[heap_[i].id] = i;
    ref_[heap_[j].id] = j;
}

bool HeapTimer::siftdown_(size_t index, size_t n)
{
    assert(index >= 0 && index < heap_.size());
    assert(n >= 0 && n <= heap_.size());
    size_t i = index;
    size_t j = i * 2 + 1;
    while (j < n)
    {
        if (j + 1 < n && heap_[j + 1] < heap_[j])
            j++;
        if (heap_[i] < heap_[j])
            break;
        SwapNode_(i, j);
        i = j;
        j = i * 2 + 1;
    }
    return i > index;
}

void HeapTimer::add(int id, int timeout, const TimeoutCallBack &cb)
{
    assert(id >= 0);
    size_t i;
    if (ref_.count(id) == 0)
    {
        /* 新节点:堆尾插入,调整堆 */
        i = heap_.size();
        ref_[id] = i;
        heap_.push_back({id, Clock::now() + MS(timeout), cb});
        siftup_(i);
    }
    else
    {
        /* 已有结点:调整堆 */
        i = ref_[id];
        heap_[i].expires = Clock::now() + MS(timeout);
        heap_[i].cb = cb;
        if (!siftdown_(i, heap_.size()))
        {
            siftup_(i);
        }
    }
}

void HeapTimer::doWork(int id)
{
    /* 删除指定id结点,并触发回调函数 */
    if (heap_.empty() || ref_.count(id) == 0)
    {
        return;
    }
    size_t i = ref_[id];
    TimerNode node = heap_[i];
    node.cb();
    del_(i);
}

void HeapTimer::del_(size_t index)
{
    /* 删除指定位置的结点 */
    assert(!heap_.empty() && index >= 0 && index < heap_.size());
    /* 将要删除的结点换到队尾,然后调整堆 */
    size_t i = index;
    size_t n = heap_.size() - 1;
    assert(i <= n);
    if (i < n)
    {
        SwapNode_(i, n);
        if (!siftdown_(i, n))
        {
            siftup_(i);
        }
    }
    /* 队尾元素删除 */
    ref_.erase(heap_.back().id);
    heap_.pop_back();
}

void HeapTimer::adjust(int id, int timeout)
{
    /* 调整指定id的结点 */
    assert(!heap_.empty() && ref_.count(id) > 0);
    heap_[ref_[id]].expires = Clock::now() + MS(timeout);
    ;
    siftdown_(ref_[id], heap_.size());
}

void HeapTimer::tick()
{
    /* 清除超时结点 */
    if (heap_.empty())
    {
        return;
    }
    while (!heap_.empty())
    {
        TimerNode node = heap_.front();
        if (std::chrono::duration_cast<MS>(node.expires - Clock::now()).count() > 0)
        {
            break;
        }
        node.cb();
        pop();
    }
}

void HeapTimer::pop()
{
    assert(!heap_.empty());
    del_(0);
}

void HeapTimer::clear()
{
    ref_.clear();
    heap_.clear();
}

int HeapTimer::GetNextTick()
{
    tick();
    size_t res = -1;
    if (!heap_.empty())
    {
        res = std::chrono::duration_cast<MS>(heap_.front().expires - Clock::now()).count();
        if (res < 0)
        {
            res = 0;
        }
    }
    return res;
}

成员

说白了,就是一个以时间为key的最小堆,先要执行的放在上面,每一次调用,把超时任务做了。

timenode

  • id 这个id作者用fd了
  • TimeStamp expires 时间戳,用于排序
  • TimeoutCallBack cb 事件的回调函数

Heaptimer

  • vector heap_ 用vector实现的最小堆
  • std::unordered_map<int, size_t> ref_; id–>index的映射,知道了节点的id,然后对应于vector下标的映射

操作 为事件延后事件

最基本的添加,删除就没啥好的了,这个adjust函数实现就比较巧妙。这个操作应用于,ok,客户端申请连接成功后,3s之后,会自动断开链接,但是1s时,客户端有了一个新请求,我就需要重置他的断开时间,3s之后再断开。

  1. 通过服务端的fd(也就是TimeNode的id)找到在vector的下标
  2. 更改他的时间
  3. 节点下移
void HeapTimer::adjust(int id, int timeout)
{
    /* 调整指定id的结点 */
    assert(!heap_.empty() && ref_.count(id) > 0);
    heap_[ref_[id]].expires = Clock::now() + MS(timeout);
    ;
    siftdown_(ref_[id], heap_.size());
}

还是非常的巧妙的。

测试

还没写多少,哈哈

#include <gtest/gtest.h>
#include <chrono>
#include <string>
#include <ctime>
#include <string>
#include "heaptimer.h"

namespace WebServerTest
{
    using namespace std;
    class HeapTimerTest : public ::testing::Test
    {
    protected:
        void SetUp() override
        {
            timer_.clear();
        }
        void TearDown() override
        {
        }
        HeapTimer timer_;
    };


    TEST_F(HeapTimerTest, TestAddTimer)
    {
        // 添加两个定时器,并触发
        auto start_time = chrono::system_clock::now();
        auto node_one=start_time;
        auto node_two=start_time;
        timer_.add(1, 1000, [&node_two]()
                   { node_two = chrono::system_clock::now(); });
        timer_.add(0, 100, [&node_one]()
                   { node_one = chrono::system_clock::now(); });
        
        timer_.tick();
        EXPECT_EQ(node_one, start_time);
        sleep(1);
        timer_.tick();
        EXPECT_LE(start_time + chrono::milliseconds(100), node_one);
    }

    TEST_F(HeapTimerTest, TestHeap){
        //测试堆的正确性
        vector<int> vct{8,1,2,5,6,3,4,7,9,10};
        vector<int> res;
        for(auto &elem : vct){
            timer_.add(elem,elem*100,[&res,elem]{res.push_back(elem);});
        }
        sleep(1.5);
        timer_.tick();
        vector<int> expect{1,2,3,4,5,6,7,8,9,10};
        EXPECT_EQ(res,expect);
    }


};
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值