timerfd与多路复用完美融合实现定时器

    在网络服务器开发中一般都会用到多路复用技术来处理网络I/O事件,而服务器中的定时器我们可以用timerfd来实现,可以像监听网络事件一样处理定时器事件,与select/poll/epoll配合使用。开源网络库muduo便使用了timerfd做定时器,在muduo中封装了一个TimerQueue类处理定时器事件。 timerfd是Linux内核版本2.6.25以后加入的,使用timerfd做定时器需要三个函数:timerfd_create、timerfd_settime、timerfd_gettime。

timerfd_create函数

#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags); 
/*
 描述:
    创建一个定时器对象,并且返回与之关联的文件描述符,这个文件描述符与socket,eventfd这样的文件描述符一样是一个int型的数值,可以与select/poll/epoll配合,监听文件描述符的读事件,时间到了便会触发。

参数:
clockid:用于标识定时器,可选参数CLOCK_REALTIME、 CLOCK_MONOTONIC其中从中选其一。其中CLOCK_REALTIME以系统时间为参照,会随着系统时间变化而变化。而CLOCK_MONOTONIC设定之后不会改变,不会因为修改系统时间而影响定时器的触发。一般我们用CLOCK_MONOTONIC。
flags:可以按位设置文件描述符的行为,TFD_NONBLOCK(设置非阻塞模式)、TFD_CLOEXEC(调用exec可以关闭当前文件描述符设置)
*/

timerfd_settime函数

struct timespec {
               time_t tv_sec;                /* 秒 */
               long   tv_nsec;               /* 纳秒 */
           };
struct itimerspec {
               struct timespec it_interval;  /* Interval for periodic timer */
               struct timespec it_value;     /* Initial expiration */
           };

int timerfd_settime(int fd, int flags,
                   const struct itimerspec *new_value,
                   struct itimerspec *old_value);
/*
描述:
用于设置定时器的超时时间,并开始计时,能够开始或者停止定时器,通过定时器的文件描述符。

参数:
fd:定时器关联的文件描述符
flags:0开启定时使用相对时间,TFD_TIMER_ABSTIME开启定时器使用绝对时间
new_value:设置new_value超时时间,it_interval超时间隔时间,如果不设置超时间隔时间定时器只触发一次,如果new_value与it_interval都为零,则停止定时器。
old_value:如果old_value不为NULL,之前定时器的设置的超时时间。
*/

timerfd_gettime函数

int timerfd_gettime(int fd, struct itimerspec *curr_value);
/*
描述:
通过定时器的文件描述符或者定时器下次触发的剩余时间。
参数:
curr_value:当前定时器下次触发时间
it_value存的是下次触发的剩余时间,如果为零已经解除触发,it_interval保存的是间隔时间。
*/

与多路复用结合实现定时器,用epoll_wait监听超时事件,来实现定时器的简单例子。

#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#include <vector>
#include <algorithm>
#include <iostream>

typedef std::vector<struct epoll_event> EventList;

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

void timefd_handler(int fd)
{
	//因为epoll是水平触发,需要把数据读出来
	uint64_t howmany;
	::read(fd, &howmany, sizeof howmany);
	//打印
	time_t t = time(0);
	char *buf = asctime(localtime(&t));
	fprintf(stdout, "time out.%s", buf);
}

int main(void)
{
	signal(SIGPIPE, SIG_IGN);
	signal(SIGCHLD, SIG_IGN);
	int epollfd = epoll_create(10000);
	//定义一个定时器
	int tmfd = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
	//设置超时间时间以及间隔时间
	struct itimerspec newValue;
	bzero(&newValue, sizeof newValue);
	newValue.it_value.tv_sec = 5;
	newValue.it_interval.tv_sec = 2;
	int ret = ::timerfd_settime(tmfd, 0, &newValue, NULL);
	if (-1 == ret)
		ERR_EXIT("timerfd_settime");
	//将文件描述符加入到监听队列
	struct epoll_event event;
	event.data.fd = tmfd;
	event.events = EPOLLIN/* | EPOLLET*/;
	epoll_ctl(epollfd, EPOLL_CTL_ADD, tmfd, &event);
	
	EventList events(16);
	int nready;
	while (1)
	{
		nready = epoll_wait(epollfd, &*events.begin(), static_cast<int>(events.size()), -1);
		if (nready == -1)
		{
			if (errno == EINTR)
				continue;
			
			ERR_EXIT("epoll_wait");
		}
		if (nready == 0)	// nothing happended
			continue;

		if ((size_t)nready == events.size())
			events.resize(events.size()*2);

		for (int i = 0; i < nready; ++i)
		{
			if (events[i].data.fd == tmfd)
			{
				//处理超时事件
				timefd_handler(tmfd);
			}
		}
	}
	return 0;
}

epoll中还可以同时监听网络读/写事件

猜你喜欢

转载自blog.csdn.net/qq_19825249/article/details/108346548