概 述
在之前文章中實現(xiàn)的定時器跷跪,都是以固定的頻率調(diào)用心搏函數(shù)tick()
万俗,從而處理到期的定時器任務(wù)康二。時間堆則采用另一種思路:將所有定時器中到期時間最小的值作為心搏間隔嫡纠。當(dāng)調(diào)用心搏函數(shù)時,此定時器任務(wù)必定到期丸冕,處理完此定時器任務(wù)后耽梅,再從所有定時器中到期時間最小的那個,將此到期時間設(shè)為下一次的心搏間隔胖烛,如此反復(fù)眼姐,實現(xiàn)定時。
實 現(xiàn)
從時間堆定時器的設(shè)計思路中不難看出佩番,使用小根堆結(jié)構(gòu)最適合處理這種方案众旗。我們通過對STL中priority_queue<>
的封裝來實現(xiàn)時間堆(有關(guān)優(yōu)先級隊列的知識這里不做展示了)。書中則是手寫了一個基于數(shù)組的小根堆來實現(xiàn)時間堆趟畏,感興趣的可以自行學(xué)習(xí)下贡歧,這里也不再展示。示例代碼如下:
//TimerHeap.h
#include <netinet/in.h>
#include <functional>
#include <vector>
#include <queue>
#define BUFFER_SIZE 0xFFFF //緩存區(qū)數(shù)據(jù)大小
#define TIMESLOT 30 //定時時間
class heap_timer; //前向聲明
// 客戶端數(shù)據(jù)
struct client_data {
sockaddr_in address;
int sockfd;
char buf[BUFFER_SIZE];
heap_timer *timer;
};
// 定時器類
class heap_timer {
public:
heap_timer(int delay) {
expire = time(nullptr) + delay;
}
public:
time_t expire; //定時器生效的絕對時間
std::function<void(client_data *)> callBackFunc; //回調(diào)函數(shù)
client_data *user_data; //用戶數(shù)據(jù)
};
struct cmp { //比較函數(shù)赋秀,實現(xiàn)小根堆
bool operator () (const heap_timer* a, const heap_timer* b) {
return a->expire > b->expire;
}
};
class TimerHeap {
public:
explicit TimerHeap();
~TimerHeap();
public:
void AddTimer(heap_timer *timer); //添加定時器
void DelTimer(heap_timer *timer); //刪除定時器
void Tick(); //心搏函數(shù)
heap_timer* Top();
private:
std::priority_queue<heap_timer *, std::vector<heap_timer *>, cmp> m_timer_pqueue; //時間堆
};
//TimerHeap.cpp
#include "TimerHeap.h"
TimerHeap::TimerHeap() = default;
TimerHeap::~TimerHeap() {
while (!m_timer_pqueue.empty()) {
delete m_timer_pqueue.top();
m_timer_pqueue.pop();
}
}
void TimerHeap::AddTimer(heap_timer *timer) {
if (!timer) return;
m_timer_pqueue.emplace(timer);
}
void TimerHeap::DelTimer(heap_timer *timer) {
if (!timer) return;
// 將目標(biāo)定時器的回調(diào)函數(shù)設(shè)為空利朵,即延時銷毀
// 減少刪除定時器的開銷,但會擴大優(yōu)先級隊列的大小
timer->callBackFunc = nullptr;
}
void TimerHeap::Tick() {
time_t cur = time(nullptr);
while (!m_timer_pqueue.empty()) {
heap_timer *timer = m_timer_pqueue.top();
//堆頂定時器沒有到期猎莲,退出循環(huán)
if (timer->expire > cur) break;
//否則執(zhí)行堆頂定時器中的任務(wù)
if (timer->callBackFunc) timer->callBackFunc(timer->user_data);
m_timer_pqueue.pop();
}
}
heap_timer *TimerHeap::Top() {
if (m_timer_pqueue.empty()) return nullptr;
else return m_timer_pqueue.top();
}
運 用
因為時間堆以最小到期時間作為心搏間隔绍弟,所以我們在服務(wù)器類中定義一個變量來記錄當(dāng)前是否在進(jìn)行定時任務(wù),從而來判斷是否觸發(fā)SIGALRM
信號著洼,核心代碼如下:
// SIGALRM信號處理函數(shù)
void Server::TimerHandler() {
m_timerHeap.Tick(); //調(diào)用時間堆的心搏函數(shù) Tick() 處理到期的任務(wù)
heap_timer *tmp = nullptr;
//判斷當(dāng)前是否在進(jìn)行定時任務(wù)晌柬,沒有則觸發(fā) SIGALRM 信號
if (!s_isAlarm && (tmp = m_timerHeap.Top())) {
time_t delay = tmp->expire - time(nullptr);
if (delay <= 0) delay = 1;
alarm(delay); //觸發(fā) SIGALRM 信號
s_isAlarm = true; //設(shè)置當(dāng)前已有定時任務(wù)在進(jìn)行
}
}
// 定時器回調(diào)函數(shù)
void Server::TimerCallBack(client_data *user_data) {
epoll_ctl(s_epollFd, EPOLL_CTL_DEL, user_data->sockfd, 0);
if (user_data) {
close(user_data->sockfd);
s_clientsList.remove(user_data->sockfd);
s_isAlarm = false; //設(shè)置當(dāng)前沒有進(jìn)行定時任務(wù)
cout << "Server: close socket fd : " << user_data->sockfd << endl;
}
}
void Server::Run() {
//...
while (!stop_server) {
//... epoll_wait
for (int i = 0; i < ret; ++i) {
int sockfd = events[i].data.fd;
if (sockfd == sock_fd) { //處理新的客戶端連接
//...accept
//創(chuàng)建定時器
heap_timer *timer = new heap_timer(TIMESLOT);
timer->user_data = &m_user[conn_fd];
timer->callBackFunc = TimerCallBack;
m_timerHeap.AddTimer(timer);
if (!s_isAlarm) { //當(dāng)前沒有進(jìn)行定時任務(wù)
s_isAlarm = true;
alarm(TIMESLOT); //觸發(fā) SIGALRM 信號
}
} else if ((sockfd == pipefd[0]) && (events[i].events & EPOLLIN)) { //處理信號
//...
} else if (events[i].events & EPOLLIN) { //處理客戶端數(shù)據(jù)
//...
heap_timer *timer = m_user[sockfd].timer;
// 更新時間堆定時器
if (timer) {
timer->expire += TIMESLOT;
// 更新定時器后姥份,需要設(shè)置當(dāng)前沒有進(jìn)行定時任務(wù)
// 從而在 TimerHandler() 中重新觸發(fā) SIGALRM 信號
s_isAlarm = false;
}
} else {
//...
}
}
//...處理定時任務(wù)
}
//... close
return 0;
}
服務(wù)器程序的運行結(jié)果與之前實現(xiàn)的定時器類似,這里就不展示了年碘。
更多內(nèi)容,詳見GitHub:ChatRoomServer