(Linux网络编程笔记)定时器
基于升序链表的定时器
这其实就是一个结点为
class util_timer
{
public:
util_timer() :prev(NULL), next(NULL) {}//构造函数
public:
time_t expire;/*任务的超时时间,这里使用绝对时间*/
void(*cb_func)(client_data*);/*任务回调函数*/
/*回调函数处理的客户数据,由定时器的执行者传递给回调函数*/
client_data* user_data;//用户数据类型
util_timer* prev;/*指向前一个定时器*/
util_timer* next;/*指向下一个定时器*/
};
的双向链表,其中,用户数据类型定义如下:
struct client_data
{
sockaddr_in address;//客户的socket地址
int sockfd;//客户socket文件描述符
char buf[BUFFER_SIZE];//读缓冲器
util_timer* timer;//一个定时器
};
接下来是重头戏sort_timer_lst
类,其声明如下
class sort_timer_lst
{
public:
sort_timer_lst() :head(NULL), tail(NULL) {}
/*链表被销毁时,删除其中所有的定时器*/
~sort_timer_lst();
void add_timer(util_timer* timer);//加入一个目标定时器,其中可能会调用属于private的一个重载函数
//当一个定时任务发生改变时,调用这个,以调整其在链表中的位置。只考虑向后移动即时间延长的情况
void adjust_timer(util_timer* timer);
//删除一个定时器
void del_timer(util_timer* timer);
//每次SIGALRM信号被触发时,在其信号处理函数或是主函数(统一事件源时)中执行一次tick(),以处理其中过期的任务。
void tick();
private:
/*一个重载的辅助函数,它被公有的add_timer函数和adjust_timer函数调用。该
函数表示将目标定时器timer添加到节点lst_head之后的部分链表中*/
void add_timer(util_timer* timer, util_timer* lst_head);
util_timer* head;
util_timer* tail;
}
处理非活动连接
使用LST_TIMER.h
来管理定时器
首先对连接过程代码详细分析:
首先是常规连接部分:
int main(int argc,char*argv[])
{
/*……略
*/
// argv[0]指向输入的程序路径及名称。从下标1开始是ip地址
const char*ip=argv[1];
int port=atoi(argv[2]);//字符串转换成int
int ret=0;
struct sockaddr_in address;//socket地址结构
bzero(&address,sizeof(address));//将结构的空间归零化
address.sin_family=AF_INET;//指出其为TCP/ip地址族
inet_pton(AF_INET,ip,&address.sin_addr);/*将用字符串表示的ip转换为网络字节序,结果存放于address.sin_addr*/
address.sin_port=htons(port);//将端口号整型字节序转换成网络字节序
int listenfd=socket(PF_INET,SOCK_STREAM,0);//创建socket,PF_INET指TCP/ip协议族
assert(listenfd>=0);//成功的话会返回一个文件描述符给listenfd,否则意味着失败。
/*命名socket:将一个socket与其具体的socket地址绑定。在客户端中通常不需要,而是采用匿名方式,由操作系统自动分配;成功时返回0*/
ret=bind(listenfd,(struct sockaddr*)&address,sizeof(address));
assert(ret!=-1);
/*监听socket。创建一个监听队列,第二个参数指示所有处于半连接状态和完全连接状态的socket上限。成功返回0*/
ret=listen(listenfd,5);
assert(ret!=-1);
I/O复用之epoll
系统调用部分
/*epoll事件结构*/
epoll_event events[MAX_EVENT_NUMBER];
/*创建一个用于epoll唯一标识内核事件表的文件描述符,参数在这里并不起作用,是给内核的一个提示,告诉它事件表需要多大*/
int epollfd=epoll_create(5);
assert(epollfd!=-1);
/*
addfd()函数解析如下
*/
addfd(epollfd,listenfd);
/*用于创建双向管道*/
ret=socketpair(PF_UNIX,SOCK_STREAM,0,pipefd);
assert(ret!=-1);
setnonblocking(pipefd[1]);
addfd(epollfd,pipefd[0]);
void addfd(int epollfd,int fd)//往epoll中添加事件
{
epoll_event event;//一个事件
event.data.fd=fd;//event的成员data是一个含有用户数据的结构,其中data.fd表示事件所从属的目标文件描述符,这里表示要监听的事件是从fd而来。
event.events=EPOLLIN|EPOLLET;//epoll_event的成员events,存储epoll事件,这里分别代表监听的事件类型是可读事件以及对方断开连接
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);//此函数用于操作epoll内核事件表。此处才真正把传进来的epollfd和刚才定义的event绑定在一起,EPOLL_CTL_ADD表示注册fd上的事件
setnonblocking(fd);//设置为非阻塞
}
此部分为设置信号处理函数
addsig(SIGALRM);//SIGALRM信号是由alarm或setitimer设置的闹钟到时后触发的信号
addsig(SIGTERM);//某个信号
bool stop_server=false;
/*client_data结构定义在time.h头文件中*/
client_data*users=new client_data[FD_LIMIT];
bool timeout=false;
alarm(TIMESLOT);/*定时*/
void addsig(int sig)
{
/*描述信号处理细节的结构体,成员如下:
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);只能二选一
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);*/
struct sigaction sa;
memset(&sa,'\0',sizeof(sa));//全部用\0填充
sa.sa_handler=sig_handler;//使用第一种处理函数,并将其指定为sig_handler
sa.sa_flags|=SA_RESTART;//重启被信号中断的系统调用
sigfillset(&sa.sa_mask);//在传入的信号集种设置所有信号,这里是屏蔽所有信号
assert(sigaction(sig,&sa,NULL)!=-1);
}
主函数循环部分
while(!stop_server)
{
/*epoll_wait系统调用。当epollfd中的事件就绪时,就将就绪的事件拷贝到events指向的数组中。*/
int number=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
if((number<0)&&(errno!=EINTR))//既没有正确返回,又没有errno,那就是epoll failure,可能是哪里配置错了
{
printf("epoll failure\n");
break;
}
for(int i=0;i<number;i++)
{
int sockfd=events[i].data.fd;
/*处理新到的客户连接*/
if(sockfd==listenfd){
struct sockaddr_in client_address;
socklen_t client_addrlength=sizeof(client_address);
int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
/*接到一个新的连接,加入epoll*/
addfd(epollfd,connfd);
/*users数组用文件描述符作为下标标识该文件描述符的用户数据*/
users[connfd].address=client_address;
users[connfd].sockfd=connfd;
/*创建定时器,设置其回调函数与超时时间,然后绑定定时器与用户数据,最后将定时器添加到链表timer_lst中*/
util_timer*timer=new util_timer;
timer->user_data=&users[connfd];
timer->cb_func=cb_func;
time_t cur=time(NULL);
timer->expire=cur+3*TIMESLOT;
users[connfd].timer=timer;
timer_lst.add_timer(timer);
}
/*处理信号。pipefd是双向管道,在信号处理函数sig_handler中,会将信号写管道里*/
else if((sockfd==pipefd[0])&&(events[i].events&EPOLLIN))
{
int sig;
char signals[1024];
ret=recv(pipefd[0],signals,sizeof(signals),0);
if(ret==-1)
{
//handle the error
continue;
}
else if(ret==0)
{
continue;
}
else
{
for(int i=0;i<ret;++i)
{
switch(signals[i])
{
case SIGALRM:
{
/*用timeout变量标记有定时任务需要处理,但不立即处理定时任务。这是因为定时任
务的优先级不是很高,我们优先处理其他更重要的任务*/
timeout=true;
break;
}
case SIGTERM:
{
stop_server=true;
}
}
}
}
}
/*处理客户连接上接收到的数据*/
else if(events[i].events&EPOLLIN)
{
memset(users[sockfd].buf,'\0',BUFFER_SIZE);
ret=recv(sockfd,users[sockfd].buf,BUFFER_SIZE-1,0);
printf("get%d bytes of client data%s from%d\n",ret,
users[sockfd].buf,sockfd);
util_timer*timer=users[sockfd].timer;
if(ret<0)
{
/*如果发生读错误,则关闭连接,并移除其对应的定时器*/
if(errno!=EAGAIN)
{
cb_func(&users[sockfd]);
if(timer)
{
timer_lst.del_timer(timer);
}
}
}
else if(ret==0)
{
/*如果对方已经关闭连接,则我们也关闭连接,并移除对应的定时器*/
cb_func(&users[sockfd]);
if(timer)
{
timer_lst.del_timer(timer);
}
}
else
{
/*如果某个客户连接上有数据可读,则我们要调整该连接对应的定时器,以延迟该连接被关闭的时间*/
if(timer)
{
time_t cur=time(NULL);
timer->expire=cur+3*TIMESLOT;
printf("adjust timer once\n");
timer_lst.adjust_timer(timer);
}
}
}
else
{
//others
}
}
/*最后处理定时事件,因为I/O事件有更高的优先级。当然,这样做将导致定时任务不
能精确地按照预期的时间执行*/
if(timeout)
{
timer_handler();
timeout=false;
}
}
头文件LST_TIMER.h
如下。
#ifndef LST_TIMER
#define LST_TIMER
#include<time.h>
#define BUFFER_SIZE 64
class util_timer;/*前向声明*/
/*用户数据结构:客户端socket地址、socket文件描述符、读缓存和定时器*/
struct client_data
{
sockaddr_in address;
int sockfd;
char buf[BUFFER_SIZE];
util_timer* timer;
};
/*定时器类*/
class util_timer
{
public:
util_timer() :prev(NULL), next(NULL) {}
public:
time_t expire;/*任务的超时时间,这里使用绝对时间*/
void(*cb_func)(client_data*);/*任务回调函数*/
/*回调函数处理的客户数据,由定时器的执行者传递给回调函数*/
client_data* user_data;
util_timer* prev;/*指向前一个定时器*/
util_timer* next;/*指向下一个定时器*/
};
/*定时器链表。它是一个升序、双向链表,且带有头结点和尾节点*/
class sort_timer_lst
{
public:
sort_timer_lst() :head(NULL), tail(NULL) {}
/*链表被销毁时,删除其中所有的定时器*/
~sort_timer_lst()
{
util_timer* tmp = head;
while (tmp)
{
head = tmp ->next;
delete tmp;
tmp = head;
}
}
/*将目标定时器timer添加到链表中*/
void add_timer(util_timer* timer)
{
if (!timer)
{
return;
}
if (!head)
{
head = tail = timer;
return;
}
/*如果目标定时器的超时时间小于当前链表中所有定时器的超时时间,则把该定时器插
入链表头部,作为链表新的头节点。否则就需要调用重载函数
add_timer(util_timer*timer,util_timer*lst_head),把它插入链表中合适的位
置,以保证链表的升序特性*/
if (timer->expire<head->expire)
{
timer->next = head;
head->prev = timer;
head = timer;
return;
}
add_timer(timer, head);
}
/*当某个定时任务发生变化时,调整对应的定时器在链表中的位置。这个函数只考虑被
调整的定时器的超时时间延长的情况,即该定时器需要往链表的尾部移动*/
void adjust_timer(util_timer* timer)
{
if (!timer)
{
return;
}
util_timer* tmp = timer->next;
/*如果被调整的目标定时器处在链表尾部,或者该定时器新的超时值仍然小于其下一个
定时器的超时值,则不用调整*/
if (!tmp || (timer->expire<tmp->expire))
{
return;
}
/*如果目标定时器是链表的头节点,则将该定时器从链表中取出并重新插入链表*/
if (timer == head)
{
head = head->next;
head->prev = NULL;
timer->next = NULL;
add_timer(timer, head);
}
/*如果目标定时器不是链表的头节点,则将该定时器从链表中取出,然后插入其原来所
在位置之后的部分链表中*/
else
{
timer->prev->next = timer->next;
timer->next->prev = timer->prev;
add_timer(timer, timer->next);
}
}
/*将目标定时器timer从链表中删除*/
void del_timer(util_timer* timer)
{
if (!timer)
{
return;
}
/*下面这个条件成立表示链表中只有一个定时器,即目标定时器*/
if ((timer == head)&&(timer == tail))
{
delete timer;
head = NULL;
tail = NULL;
return;
}
/*如果链表中至少有两个定时器,且目标定时器是链表的头结点,则将链表的头结点重
置为原头节点的下一个节点,然后删除目标定时器*/
if (timer == head)
{
head = head->next;
head->prev = NULL;
delete timer;
return;
}
/*如果链表中至少有两个定时器,且目标定时器是链表的尾结点,则将链表的尾结点重
置为原尾节点的前一个节点,然后删除目标定时器*/
if (timer == tail)
{
tail = tail->prev;
tail->next = NULL;
delete timer;
return;
}
/*如果目标定时器位于链表的中间,则把它前后的定时器串联起来,然后删除目标定时
器*/
timer->prev->next = timer->next;
timer->next->prev = timer->prev;
delete timer;
}
/*SIGALRM信号每次被触发就在其信号处理函数(如果使用统一事件源,则是主函数)
中执行一次tick函数,以处理链表上到期的任务*/
void tick()
{
if (!head)//链表都没了直接返回
{
return;
}
printf("timer tick\n");
time_t cur = time(NULL);/*获得系统当前的时间*/
util_timer* tmp = head;
/*从头结点开始依次处理每个定时器,直到遇到一个尚未到期的定时器,这就是定时器
的核心逻辑*/
while (tmp)
{
/*因为每个定时器都使用绝对时间作为超时值,所以我们可以把定时器的超时值和系统
当前时间,比较以判断定时器是否到期*/
if (cur<tmp->expire)
{
break;
}
/*调用定时器的回调函数,以执行定时任务*/
tmp->cb_func(tmp->user_data);
/*执行完定时器中的定时任务之后,就将它从链表中删除,并重置链表头结点*/
head = tmp->next;
if (head)
{
head->prev = NULL;
}
delete tmp;
tmp = head;
}
}
private:
/*一个重载的辅助函数,它被公有的add_timer函数和adjust_timer函数调用。该
函数表示将目标定时器timer添加到节点lst_head之后的部分链表中*/
void add_timer(util_timer* timer, util_timer* lst_head)
{
util_timer* prev = lst_head;
util_timer* tmp = prev->next;
/*遍历lst_head节点之后的部分链表,直到找到一个超时时间大于目标定时器的超时
时间的节点,并将目标定时器插入该节点之前*/
while (tmp)
{
if (timer->expire<tmp->expire)
{
prev->next = timer;
timer->next = tmp;
tmp->prev = timer;
timer->prev = prev;
break;
}
prev = tmp;
tmp = tmp->next;
}
/*如果遍历完lst_head节点之后的部分链表,仍未找到超时时间大于目标定时器的超
时时间的节点,则将目标定时器插入链表尾部,并把它设置为链表新的尾节点*/
if (!tmp)
{
prev->next = timer;
timer->prev = prev;
timer->next = NULL;
tail = timer;
}
}
private:
util_timer* head;
util_timer* tail;
};
#endif