基于升序链表的定时器及其简单应用

(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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值