Linux之定时器

Linux下的实现方式

  • socket选项SO_RECVTIMEO和SO_SNDTIMEO

  • SIGALRM信号
    信号相关笔记:Linux之信号

  • I/O复用系统调用的超时参数

定时器及其容器

  • 定时器
    封装了以下内容

    • 超时时间

    • 回调函数:就是每次时间到了要干嘛

    • 连接资源:包括客户端socket地址、客户端socket、定时器

  • 定时器的容器
    把多个定时器串联组织起来统一处理。比如用一个升序链表来装定时器,每个定时器是一个结点

具体代码

定时器类定义

//在连接资源中包含定时器,定时器中包含连接资源,他们均可以找到对方

class util_timer;

//连接资源
struct client_data
{
    //客户端socket地址
    sockaddr_in address;

    //客户端socket
    int sockfd;

    //定时器
    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;
};

使用定时器

//定时处理任务
void timer_handler()
{
	timer_lst.tick();	//tick函数遍历链表,处理到时了的事件
	alarm(TIMESLOT);	//重新定时,不断触发SIGALRM信号
}

//定时器链表
static sort_timer_lst timer_lst;

//创建连接资源数组
client_data* users_timer = new client_data[MAX_FD];

//超时默认为False
bool timeout = false;

//alarm定时触发SIGALRM信号
alarm(TIMESLOT);

while (!stop_server)
{
	int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
	if (number < 0 && errno != EINTR)
	{
		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);

			//初始化该连接对应的连接资源
			users_timer[connfd].address = client_address;
			users_timer[connfd].sockfd = connfd;

			//正常创建链表结点
			util_timer* timer = new util_timer;
			//设置定时器对应的连接资源
			timer->user_data = &users_timer[connfd];
			//设置回调函数
			timer->cb_func = cb_func;

			time_t cur = time(NULL);
			//设置绝对超时时间
			timer->expire = cur + 3 * TIMESLOT;
			//创建该连接对应的定时器
			users_timer[connfd].timer = timer;
			//将该定时器添加到链表中
			timer_lst.add_timer(timer);
		}
		
		//处理异常事件
		else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR))
		{
			//服务器端关闭连接,移除对应的定时器
			cb_func(&users_timer[sockfd]);

			util_timer* timer = users_timer[sockfd].timer;
			if (timer)
			{
				timer_lst.del_timer(timer);
			}
		}

		//处理定时器信号
		else if ((sockfd == pipefd[0]) && (events[i].events & EPOLLIN))
		{
			//接收到SIGALRM信号,timeout设置为True
		}

		//处理客户连接上接收到的数据
		else if (events[i].events & EPOLLIN)
		{
			//创建定时器临时变量,将该连接对应的定时器取出来
			util_timer* timer = users_timer[sockfd].timer;
			if (users[sockfd].read_once())
			{
				//若监测到读事件,将该事件放入请求队列
				pool->append(users + sockfd);

				//若有数据传输,则将定时器往后延迟3个单位
				//对其在链表上的位置进行调整
				if (timer)
				{
					time_t cur = time(NULL);
					timer->expire = cur + 3 * TIMESLOT;
					timer_lst.adjust_timer(timer);
				}
			}
			else
			{
				//服务器端关闭连接,移除对应的定时器
				cb_func(&users_timer[sockfd]);
				if (timer)
				{
					timer_lst.del_timer(timer);
				}
			}
		}
		else if (events[i].events & EPOLLOUT)
		{
			util_timer* timer = users_timer[sockfd].timer;
			if (users[sockfd].write())
			{
				//若有数据传输,则将定时器往后延迟3个单位
				//并对新的定时器在链表上的位置进行调整
				if (timer)
				{
					time_t cur = time(NULL);
					timer->expire = cur + 3 * TIMESLOT;
					timer_lst.adjust_timer(timer);
				}
			}
			else
			{
				//服务器端关闭连接,移除对应的定时器
				cb_func(&users_timer[sockfd]);
				if (timer)
				{
					timer_lst.del_timer(timer);
				}
			}
		}
	}
	//处理定时器为非必须事件,收到信号并不是立马处理
	//完成读写事件后,再进行处理
	if (timeout)
	{
		timer_handler();
		timeout = false;
	}
posted @ 2023-08-20 03:28  悲伤鳄鱼吃面包  阅读(74)  评论(0编辑  收藏  举报