项目源网站 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之后再断开。
- 通过服务端的fd(也就是TimeNode的id)找到在vector的下标
- 更改他的时间
- 节点下移
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);
}
};