定时是指在一段时间之后触发某端代码的机制,我们可以在这段代码中依次处理所有到期的定时器。
struct timeval timeout;
timeout.tv_sec = time;
timeout.tv_usec = 0;
socklen_t len = sizeof(timeout);
if(setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len) < 0)
{}
if(connect(...) < 0)
{
//connect 超时返回的错误是EINPROGRESS
if(errno == EINPROGRESS)
{
//超时发生,这里处理超时任务
}
printf("error occur");
return -1;
}
// ...
//定时器链表实现
#ifndef LST_TIMER
#define LST_TIMER
#include
#define BUFE_SIZE 64
class util_timer;
//用户的数据结构
struct client_data
{
sockaddr_in cd_address;
int cd_sockfd;
char cd_buf[BUFE_SIZE]; //读缓存
util_timer* timer; //定时器
};
//定时器
//定时器通常至少包含两个成员,一个超时时间,一个任务回调函数。
//有时候还包括回调函数被执行时需要传入的参数,以及是否重启定时器等信息
//如果用链表作为容器串联所有定时器,每个定时器还要(可能)包含前一个和下一个定时器的指针
//这里是一个简单的升序定时器链表
class util_timer
{
public:
//可以看出,我们用的是双向链表
util_timer():prev(NULL),next(NULL) {}
public:
time_t expire; //任务超时时间,这使用绝对时间
void (*cb_fun)(client_data*); //任务回调函数
/*回调函数处理的客户数据,由定时器的执行者传递给回调函数*/
client_data* user_data;
util_timer* prev;
util_timer* next;
};
//定时器链表
//属性: 升序, 双向, 带头节点, 带尾节点
class sort_timer_list
{
public:
//构造和析构
sort_timer_list():head(NULL),tail(NULL) {}
~sort_timer_list()
{
util_timer* tmp = head;
while(tmp)
{
head = tmp->next;
delete tmp;
tmp = head;
}
}
//将目标定时器添加仅链表
void add_timer(util_timer *timer)
{
if(!timer)
return;
else if(!head)
{
head = tail = timer;
return;
}
//节点插入的顺序由expire时间决定
else if(timer->expire < head->expire)
{
timer->next = head;
head->prev = timer;
head = timer;
return;
}
//调用重载函数,将对象插入适当位置
add_timer(timer, head);
}
void add_timer(util_timer* timer, util_timer *head)
{
util_timer* tmp = head;
while(tmp)
{
if(timer->expire < tmp->expire)
break;
tmp = tmp->next;
}
if(NULL == tmp)
{
timer->prev = tail;
tail->next = timer;
tail = timer;
}
else
{
timer->prev = tmp->prev;
timer->next = tmp;
tmp->prev->next = timer; //注意,这样使用要提前判断是否为头节点,因为调用此函数之前将此可能剔除了,所以这里不判断
tmp->prev = timer;
}
}
//当某个定时任务发生变化,调整对应的定时器在链表中的位置。这里只考虑被调整的定时器超时时间被延长的情况
void adjust_timer(util_timer * timer)
{
//不存在
if(NULL == timer)
return;
util_timer *tmp = timer->next;
//本身是尾部,或调整后仍小于后面节点
if(!tmp || timer->expireexpire)
return;
//为头节点,取下来重插
if(timer == head)
{
head = timer->next;
head->prev = NULL;
timer->next = NULL;
add_timer(timer,head);
}
//是里面的某节点,取下来从后面节点开始,重插
else
{
tmp->prev = timer->prev;
timer->prev->next = tmp; //要注意的是,因为前面已经判断过是否为尾部,否则这里要使用timer->next->prev要提前判断是否存在这个next对象
timer->prev = timer->next = NULL;
add_timer(timer,tmp);
}
}
//删除目标定时器
void del_timer(util_timer* timer)
{
if(NULL == timer)
return;
if(timer == head && timer == tail)
head = tail = NULL;
else if(timer == head)
{
head = timer->next;
head->prev = NULL;
}
else if(timer == tail)
{
timer->prev->next = NULL;
tail = timer->prev;
}
else{
timer->prev->next = timer->next;
timer->next->prev = timer->prev;
}
delete timer;
}
/*SIGALRM信号每被触发一次就在其信号处理函数中执行一次tick函数,以处理链表上到期的任务*/
void tick()
{
if(!head)
return;
printf("time tick\n");
time_t cur = time(NULL); //获取系统当前时间
util_timer *tmp = head;
while(tmp)
{
if(cur < tmp->expire)
break;
//执行定时任务
tmp->cb_fun(tmp->user_data);
//执行完就删了
head = tmp->next;
if(head)
head->prev = NULL;
delete tmp;
tmp = head;
}
}
private:
util_timer* head;
util_timer* tail;
}
//核心函数是tick函数,相当于一个心博函数,每隔一段时间执行一次,检测并处理到期任务
//这个程序大意是,为每个连接设置一个定时器。定时器被挂在定时器链表上。
//定时器一般会在连接没有数据传输的3个TIMEOUT时间后将连接清除;如果有数据来,就重置定时器
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "lst_timer.h"
#define TIMEOUT 5 //这个就相当于以 TIMEOUT 为时间单位
#define MAX_EVENT_NUMBER 1024
#define FD_LIMIT 65535
static sort_timer_list timer_lst; //定时器链表
static pipefd[2]; //统一事件源用的管道
void sig_handler(int s); //信号处理函数,但是我们这里使用事件源,所以信号处理函数只是单纯的将接收到的信号传给主函数
void setnonblocking(int fd); //设置非阻塞
void addsig(int sig, void (*sig_handler)(int)); //为某信号添加信号处理函数
void addfd(int epollfd, int fd); //将对某描述符的监听添加进内核事件表中,等待epoll返回
void timer_handler(); //定时任务
void cb_fun(client_data* user_data); //定时任务要调用的回调函数
int main(int ac, char *av[])
{
if(ac != 3)
{
fprintf(stderr, "Usage: %s addr port\n",av[0]);
exit(1);
}
char *ip = av[1];
int port = atoi(av[2]);
int ret;
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &addr.sin_addr);
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
perror("socket error");
exit(1);
}
ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind error");
exit(1);
}
ret = listen(sock, 10);
if(ret < 0)
{
perror("listen error");
exit(1);
}
//以上可忽略
struct epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
if(socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd) < 0) //用于传递信号,实现统一事件源
{
perror("socketpair error");
exit(1);
}
setnonblocking(pipefd[1]);
client_data *users = new client_data[FD_LIMIT]; //这个users的使用和chat_room的users的意义一样
addfd(epollfd,sock);
addfd(epollfd,pipefd[0]);
addsig(SIGALRM);
addsig(SIGTERM);
bool stop_server = false; //如果收到SIGTERM, 那么就为true
int nready = 0;
alarm(TIMEOUT); //开始定时
bool time_out = false; //这个标志下面会讲到
while(!stop_server)
{
nready = epoll(epollfd, events, MAX_EVENT_NUMBER, -1);
if(nready < 0 && errno!=EINTR)
{
fprintf(stderr,"epoll failed\n");
exit()1;
}
for(int i=0; iexpire = cur + 3*TIMEOUT; //在3个TIMEOUT的时间后,若没有数据传输,会执行定时任务,即将此连接清除
timer->cb_fun = cb_fun;
timer->prev = timer->next = NULL;
timer->user_data = &users[connfd];
users[connfd].timer = timer;
timer_lst.add_timer(timer);
}
//处理信号
else if((fd==pipefd[0]) && (events[i].events & EPOLLIN))
{
int sig;
char signals[1024];
ret = recv(pipefd[0], signals, sizeof(signals), 0);
if(ret <= 0) //出错了我也不知道怎么去处理
{
continue;
}
else
{
for(int j=0; jsockfd, 0);
close(user_data->sockfd);
printf("closed fd %d\n",user_data->sockfd);
}
#define TIMEOUT 5000
int timeout = TIMEOUT;
time_t start, end;
while(1)
{
printf("now the timeout is %d\n",timeout);
start = time(NULL);
int nready = epoll_wait(..., timeout); //这里的timeout单位是 millisecond
if(nready < 0)
{
//不是EINTR那就可能出错了
}
else if(nready == 0) //这就是正好超时了,期间没有任何描述符事件就绪
{
//这里可以进行定时任务了
timeout = TIMEOUT; //记得重置
continue;
}
//返回大于0, 表明有事件就绪了, 我们就要进行计算了
end = time(NULL);
timeout -= (end-start) * 1000;
if(timeout <= 0) // 这种情况是既有事件就绪,又正好超时事件到
{
//还是可以进行定时任务的处理
timeout = TIMEOUT;
}
//大于0的话就是继续等待那么一段时间
}