linux网络编程二十二:高性能定时器之时间轮
来源:互联网 发布:出售淘宝0心店铺 编辑:程序博客网 时间:2024/05/21 17:07
之前我们提到,基于排序链表的定时器存在一个问题:添加定时器的效率偏低。下面我们讨论的时间轮解决了这个问题。
如图,这是一种简单的时间轮:
轮中的实线指针指向轮子上的一个槽(slot),它以恒定的速度顺时针转动,每转动一步就指向下一个槽,每次转动称为一个滴答(tick)。
一个滴答的时间称为是间轮的槽间隔si(slot interval),它实际上就是心跳时间。
该轮共有N个槽,因此它运转一周的时间是N×si 。每个槽指向一条定时器链表,每条链表上的定时器具有相同的特性:它们的定时时间相差N×si的整数倍。时间轮正是利用这个关系将定时器散列到不同的链表中。
假如现在指针指向槽cs,我们要添加一个定时时间为ti的定时器,则该定时器将被插入ts(timer slot)对应的链表中:ts = (cs + (ti / si)) %N
基于排序链表的定时器使用唯一的一条链表来管理所有定时器,所以插入操作的效率随着定时器数目的增多而降低。而时间轮使用哈希表的思想,将定时器散列到不同的链表上。
这样每条链表上的定时数目都将明显减少,插入操作的效率受定时器数目的影响较少。
很显然,对时间轮而言,要提高定时精度,就要使si值足够小;要提高执行效率,则要求N值足够大。
下面我们实现一个简单的时间轮,只实现一个轮子。复杂的时间轮可以有多个,不同的轮子有不同粒度。相邻的两个轮子,精度高的转一圈,精度低的仅往前移动一槽,就像水表一样。
对时间轮而言,添加一个定时器的时间复杂度是O(1), 删除一个定时器的时间复杂度是O(1),执行一个定时器的时间复杂度是O(n)。
但实际上执行一个定时器的效率要比O(n)高得多,因为时间轮将所有的定时器散列到了不同的链表上,时间轮的槽越多,等于散列表的入口越多,从而每条链表上的定时器数量越少。此外,我们的代码仅用了一个时间轮,当使用多个轮子来实现时,它的时间复杂度将接近O(1)。
关于linux下定时器的实现方式对比,大家可以看看这篇文章,写的挺不错的:http://www.ibm.com/developerworks/cn/linux/l-cn-timers/
1. 代码:
//tw_timer.h#ifndef __TIME_WHEEL__#define __TIME_WHEEL__#include <time.h>#include <netinet/in.h>#include <stdio.h>#define BUFFER_SIZE 64class tw_timer;//客户端数据struct client_data{ sockaddr_in address; int sockfd; char buf[BUFFER_SIZE]; tw_timer *timer;};//定时器class tw_timer{public: tw_timer(int rot, int ts) :next(NULL), prev(NULL), rotation(rot), time_slot(ts) {}public: int rotation; //定时器在时间轮上转多少圈后生效 int time_slot; //定时器属于时间轮上的哪个槽 void (*cb_func)(client_data*); //定时器的回调函数 client_data *user_data; //客户端数据 tw_timer *prev; //指向上一个定时器 tw_timer *next; //指向下一个定时器};//时间轮class time_wheel{public: time_wheel(); ~time_wheel(); tw_timer* add_timer(int timeout); //根据定时值创建定时器,并插入合适的位置 void del_timer(tw_timer *timer); //删除目标定时器 void tick(); //时间到后调用该函数,时间轮向前滚动一个槽间隔 private: static const int N = 60; //时间轮上槽的数目 static const int TI = 1; //槽间隔时间,即每1秒时间轮转动一次 int cur_slot; //时间轮的当前槽 tw_timer *slots[N]; //时间轮的槽,其中每个元素指向一个定时器链表};#endif
//tw_timer.cpp#include "tw_timer.h"time_wheel::time_wheel():cur_slot(0){ //初始化每个槽的头结点 for (int i = 0; i < N; ++i) slots[i] = NULL;}time_wheel::~time_wheel(){ //遍历每个槽,并销毁定时器 for (int i = 0; i < N; ++i) { tw_timer *tmp = slots[i]; while (tmp) { slots[i] = tmp->next; delete tmp; tmp = slots[i]; } }}tw_timer* time_wheel::add_timer(int timeout){ if (timeout < 0) return NULL; int ticks = 0; //待插入定时器所需要总ticks if (timeout < TI) ticks = 1; else ticks = timeout / TI; int rotation = ticks / N; //计算待插入的定时器在时间轮上要转动多少圈后触发 int ts = (cur_slot + ticks) % N; //计算待持入定时器应该被插入的位置 //int ts = (cur_slot + (ticks %N)) % N; //创建定时器,它在时间轮转动rotation圈之后触发,且位于第ts个槽上 tw_timer *timer = new tw_timer(rotation, ts); //如果槽为空,则它新定时器插入,并设置为该槽的头节点 if (!slots[ts]) { printf("add timer, rotation is %d, ts is %d, cur_slot is %d\n", rotation, ts, cur_slot); slots[ts] = timer; } else { timer->next = slots[ts]; slots[ts]->prev = timer; slots[ts] = timer; } return timer;}void time_wheel::del_timer(tw_timer *timer){ if (!timer) return; int ts = timer->time_slot; if (timer == slots[ts]) { //如果是头结点 slots[ts] = slots[ts]->next; if (slots[ts]) slots[ts]->prev = NULL; delete timer; } else { timer->prev->next = timer->next; if (timer->next) timer->next->prev = timer->prev; delete timer; }}void time_wheel::tick(){ //取得时间轮上当前槽的头结点 tw_timer *tmp = slots[cur_slot]; printf("current slot is %d\n", cur_slot); while (tmp) { printf("tick the timer once\n"); //如果定时器的rotation值大于0,则未到时,不处理 if (tmp->rotation > 0) { tmp->rotation--; tmp = tmp->next; } else { tmp->cb_func(tmp->user_data); if (tmp == slots[cur_slot]) { printf("delete header in cur_slot\n"); slots[cur_slot] = tmp->next; delete tmp; if (slots[cur_slot]) slots[cur_slot]->prev = NULL; tmp = slots[cur_slot]; } else { tmp->prev->next = tmp->next; if (tmp->next) tmp->next->prev = tmp->prev; tw_timer *tmp2 = tmp->next; delete tmp; tmp = tmp2; } } } //更新时间轮的当前槽,以反映时间轮的转动 cur_slot = ++cur_slot % N;}
//nonactive_conn.cpp//关闭非活动连接#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <errno.h>#include <fcntl.h>#include <sys/epoll.h>#include <assert.h>#include <signal.h>#include <pthread.h>#include "tw_timer.h"#define FD_LIMIT 65535#define MAX_EVENT_NUMBER 1024#define TIMESLOT 100static int pipefd[2];static time_wheel client_conn_time_wheel;static int epollfd = 0;int setnonblocking(int fd); //设置非阻塞int addfd(int epollfd, int fd); //添加描述符事件void sig_handler(int sig); //信号处理函数void addsig(int sig); //添加信号处理函数void timer_handler(); //定时器任务void cb_func(client_data *user_data); //定时器回调函数int main(int argc, char *argv[]){ if (argc != 2) { fprintf(stderr, "Usage: %s port\n", argv[0]); return 1; } int port = atoi(argv[1]); int ret = 0; int error; struct sockaddr_in address; bzero(&address, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = htonl(INADDR_ANY); int sockfd = socket(PF_INET, SOCK_STREAM, 0); if (sockfd == -1) { fprintf(stderr, "create socket failed\n"); return 1; } int reuse = 1; ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); if (ret == -1) { error = errno; while ((close(sockfd) == -1) && (errno == EINTR)); errno = error; return 1; } if ( (bind(sockfd, (struct sockaddr*)&address, sizeof(address)) == -1) || (listen(sockfd, 5) == -1)) { error = errno; while ((close(sockfd) == -1) && (errno == EINTR)); errno = error; return 1; } int epollfd = epoll_create(5); if (epollfd == -1) { error = errno; while ((close(sockfd)) && (errno == EINTR)); errno = error; return 1; }ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);if (ret == -1) {error = errno;while ((close(sockfd) == -1) && (errno == EINTR));errno = error;return 1;} epoll_event events[MAX_EVENT_NUMBER]; setnonblocking(pipefd[1]); addfd(epollfd, pipefd[0]);addfd(epollfd, sockfd); //添加信号处理 addsig(SIGALRM); addsig(SIGTERM); bool stop_server = false; client_data *users = new client_data[FD_LIMIT]; bool timeout = false; alarm(1); printf("server start...\n"); while (!stop_server) { int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); if (number < 0 && errno != EINTR) { fprintf(stderr, "epoll_wait failed\n"); break; } for (int i = 0; i < number; i++) { int listenfd = events[i].data.fd; if (listenfd == sockfd) { struct sockaddr_in client_address; socklen_t client_addrlength = sizeof(client_address); int connfd; while ( ((connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength)) == -1) && (errno == EINTR)); addfd(epollfd, connfd); users[connfd].address = client_address; users[connfd].sockfd = connfd; tw_timer *timer = NULL; timer = client_conn_time_wheel.add_timer(TIMESLOT); if (timer) { timer->user_data = &users[connfd]; timer->cb_func = cb_func;users[connfd].timer = timer; fprintf(stderr, "client: %d add tw_timer successed\n", connfd); } else { fprintf(stderr, "client: %d add tw_timer failed\n", connfd); } } else if((listenfd == pipefd[0]) && (events[i].events & EPOLLIN)) { int sig; char signals[1024]; ret = recv(pipefd[0], signals, sizeof(signals), 0); if (ret == -1) { continue; } else if (ret == 0) { continue; } else { for (int i = 0; i < ret; i++) { switch (signals[i]) { case SIGALRM: { timeout = true; break; } case SIGTERM: { stop_server = true; break; } default: break; } } } } else if (events[i].events & EPOLLIN) { memset(users[listenfd].buf, '\0', BUFFER_SIZE); ret = recv(listenfd, users[listenfd].buf, BUFFER_SIZE-1, 0); printf("get %d bytes of client data: %s from %d\n", ret, users[listenfd].buf, listenfd); tw_timer *timer = users[listenfd].timer; if (ret < 0) { if (errno != EAGAIN) { cb_func(&users[listenfd]); if (timer) client_conn_time_wheel.del_timer(timer); } } else if (ret == 0) { cb_func(&users[listenfd]); if (timer) client_conn_time_wheel.del_timer(timer); } else { if (timer) { printf("conntioned..to do adjuest timer\n"); } } } else { } } if (timeout) { timer_handler(); timeout = false; } } close(sockfd); close(pipefd[1]); close(pipefd[0]); delete[] users; return 0;}int setnonblocking(int fd){ int old_option = fcntl(fd, F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd, F_SETFL, new_option); return old_option;}int addfd(int epollfd, int fd){ epoll_event event; event.data.fd = fd; event.events = EPOLLIN | EPOLLET; epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); setnonblocking(fd);}void sig_handler(int sig){ int save_error = errno; int msg = sig; send(pipefd[1], (char*)&msg, 1, 0); errno = save_error;}void addsig(int sig){ struct sigaction sa; memset(&sa, '\0', sizeof(sa)); sa.sa_handler = sig_handler; sa.sa_flags |= SA_RESTART; sigfillset(&sa.sa_mask); assert(sigaction(sig, &sa, NULL) != -1);}void timer_handler(){ client_conn_time_wheel.tick(); alarm(1);}void cb_func(client_data *user_data){ epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0); assert(user_data); close(user_data->sockfd); printf("close fd %d\n", user_data->sockfd);}
参考:《linux高性能服务器编程》
- linux网络编程二十二:高性能定时器之时间轮
- linux网络编程二十三:高性能定时器之时间堆
- Linux网络编程--定时器之时间轮
- Linux网络编程--定时器之时间堆
- 【网络编程】高性能网络编程之reactor反应堆与定时器管理
- 高性能定时器时间轮的实现
- 高性能的定时器设计---时间轮
- Linux学习(二十二)网络编程
- 高性能网络编程6--reactor反应堆与定时器管理
- 高性能网络编程六--reactor反应堆与定时器管理
- 高性能网络编程6--reactor反应堆与定时器管理
- 高性能网络编程6--reactor反应堆与定时器管理
- 高性能网络编程6--reactor反应堆与定时器管理
- 高性能网络编程6--reactor反应堆与定时器管理
- 高性能网络编程6----reactor反应堆与定时器管理
- 高性能网络编程6--reactor反应堆与定时器管理
- 高性能网络编程6--reactor反应堆与定时器管理
- 高性能网络编程6--reactor反应堆与定时器管理
- <compatible-screens>
- 如何学习Cocoa
- <data>
- <grant-uri-permission>
- <instrumentation>
- linux网络编程二十二:高性能定时器之时间轮
- <intent-filter>
- <manifest>
- Struts2的struts.properties文件在哪儿啊?
- <meta-data>
- 开源界的5大开源许可协议
- <path-permission>
- <permission>
- 核心技术笔记_重要的java.lang.string:、 String类中的一些重要的方法(2014.04.16 day36)