首页 > 代码库 > Linux组件封装(六)——定时器的简单封装

Linux组件封装(六)——定时器的简单封装

在Linux中,有一种简单的定时器——timerfd,它通过查看fd是否可读来判断定时器时候到时。

timerfd中常用的函数有timerfd_create、timerfd_settime、timerfd_gettime,这些函数都相对简单,我们可以到man手册来查看用法。

值得注意的是:create中的参数CLOCK_REALTIME是一个相对时间,我们可以通过调整系统时间对其进行调整,而CLOCK_MONOTIC是一个绝对时间,系统时间的改变不会影响它。在create中,flags一般设置为0。

下面是一个简单的例子:

 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <errno.h> 7 #include <sys/timerfd.h> 8 #include <stdint.h> 9 #include <poll.h>10 #define ERR_EXIT(m) 11     do { 12         perror(m);13         exit(EXIT_FAILURE);14     }while(0)15 16 void foo()17 {18     printf("foo\n");19 }20 21 22 int main(int argc, const char *argv[])23 {24     //创建fd25 26     int timerfd = timerfd_create(CLOCK_REALTIME, 0);27     if(timerfd == -1)28         ERR_EXIT("timerfd_create");29 30     //设置时间31     struct itimerspec tv;32     memset(&tv, 0, sizeof tv);33     tv.it_value.tv_sec = 3;34     tv.it_interval.tv_sec = 1;35     if(timerfd_settime(timerfd, 0, &tv, NULL) == -1)36         ERR_EXIT("timerfd_settime");37 38     char buf[1024] = {0}; 39     int ret; 40     while((ret = read(timerfd, buf, sizeof buf)) > 0){ 41         printf("ret = %d, read data:%s\n", ret, buf); // 42     } 43 44     close(timerfd);45 46     return 0;47 }
View Code

这里需要注意:一旦定时器到期,fd中就有数据可读,这个时候,我们一定要将fd中的数据read出来,否则定时器会产生异常,无法正常工作。

我们可以将timerfd和poll一起使用,将timerfd加入到poll的监听数组中,这样当timerfd可读时,我们就调用相应的函数,来完成定时任务。

然而当我们不将timerfd中的数据读出时,poll监听到timerfd一直可读,这样就会一直触发相应函数,就失去了定时器的左右。

所以,我们一定要注意将timerfd中的数据读出

将timerfd与poll结合:

 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <errno.h> 7 #include <sys/timerfd.h> 8 #include <stdint.h> 9 #include <poll.h>10 #define ERR_EXIT(m) 11     do { 12         perror(m);13         exit(EXIT_FAILURE);14     }while(0)15 16 void foo()17 {18     printf("foo\n");19 }20 21 22 int main(int argc, const char *argv[])23 {24     //创建fd25 26     int timerfd = timerfd_create(CLOCK_REALTIME, 0);27     if(timerfd == -1)28         ERR_EXIT("timerfd_create");29 30     //设置时间31     struct itimerspec tv;32     memset(&tv, 0, sizeof tv);33     tv.it_value.tv_sec = 3;34     tv.it_interval.tv_sec = 1;35     if(timerfd_settime(timerfd, 0, &tv, NULL) == -1)36         ERR_EXIT("timerfd_settime");37 38     //判断fd可读39 40     //int poll(struct pollfd *fds, nfds_t nfds, int timeout);41 42     struct pollfd pfd;43     pfd.fd = timerfd;44     pfd.events = POLLIN; //监听输入事件45 46     uint64_t val;47     int ret;48     while(1)49     {50         ret = poll(&pfd, 1, 5000);51         if(ret == -1)52         {53             if(errno == EINTR)54                 continue;55             ERR_EXIT("poll");56         }57         else if(ret == 0)58         {59             printf("timeout\n"); //超时60         }61 62         if(pfd.revents == POLLIN) //此fd是否监听的read事件63         {64             read(timerfd, &val, sizeof val);65             foo();66         }67             68     }69 70 71     close(timerfd);72 73     return 0;74 }
View Code

我们可以将timerfd类的函数封装到一个Timer类中,提供一个接口来接受用户要执行的函数(即回调函数),这样做,可以使我们的定时器更加安全、实用。
声明代码如下:

 1 #ifndef TIMER_H 2 #define TIMER_H 3 #include <boost/noncopyable.hpp> 4 #include <functional> 5 #include <sys/timerfd.h> 6  7 class Timer : boost::noncopyable 8 { 9 public:10 11     typedef std::function<void()> TimerCallback;12 13     Timer(int val, int interval, TimerCallback cb);14     ~Timer();15 16     void start();17     void stop();18 19 private:20 21     int _timerfd;22     int _val;23     int _interval;24     TimerCallback _callback;25     bool _isStart;26 };27 28 29 30 #endif  /*TIMER_H*/
View Code

实现代码如下:

  1 #include "Timer.h"  2 #include <stdio.h>  3 #include <stdlib.h>  4 #include <string.h>  5 #include <unistd.h>  6 #include <sys/types.h>  7 #include <errno.h>  8 #include <sys/timerfd.h>  9 #include <stdint.h> 10 #include <poll.h> 11 #define ERR_EXIT(m)  12         do {  13             perror(m); 14             exit(EXIT_FAILURE); 15         }while(0) 16 namespace 17 { 18     int createTimer() 19     { 20         int timerfd = ::timerfd_create(CLOCK_REALTIME, 0); 21         if(timerfd == -1) 22             ERR_EXIT("create"); 23  24         return timerfd; 25     } 26  27     void setTimer(int timerfd, int val, int interval) 28     { 29         struct itimerspec t; 30         memset(&t, 0, sizeof t); 31         t.it_value.tv_sec = val; 32         t.it_interval.tv_sec = interval; 33  34         if(::timerfd_settime(timerfd, 0, &t, NULL) == -1) 35             ERR_EXIT("settime"); 36     } 37  38     void stopTimer(int timerfd) 39     { 40         setTimer(timerfd, 0, 0); 41     } 42  43     void readTimer(int timerfd) 44     { 45         uint64_t howmany; 46         if(::read(timerfd, &howmany, sizeof howmany) != sizeof(howmany)) 47             ERR_EXIT("read"); 48     } 49  50 } 51 Timer::Timer(int val, int interval, TimerCallback cb) 52     :_timerfd(createTimer()), 53      _val(val), 54      _interval(interval), 55      _callback(std::move(cb)), 56      _isStart(false) 57 { 58  59 } 60  61  62 Timer::~Timer() 63 { 64     if(_isStart) 65     { 66         stop(); 67         ::close(_timerfd); 68     } 69 } 70  71  72 void Timer::start() 73 { 74     setTimer(_timerfd, _val, _interval); 75     _isStart = true; 76  77     struct pollfd pfd; 78     pfd.fd = _timerfd; 79     pfd.events = POLLIN; 80  81     uint64_t val; 82     int ret; 83     while(_isStart) 84     { 85         ret = ::poll(&pfd, 1, 5000); 86         if(ret == -1) 87         { 88             if(errno == EINTR) 89                 continue; 90             ERR_EXIT("poll"); 91         } 92         else if(ret == 0) 93         { 94             printf("timeout\n"); 95             continue; 96         } 97  98         if(pfd.revents == POLLIN) 99         {100             readTimer(_timerfd);101             _callback();102         }103     }104 }105 106 void Timer::stop()107 {108     _isStart = false;109     stopTimer(_timerfd);110 }
View Code

 

我们可以将定时器与线程封装到一起,这样,每个线程就是一个定时器。

线程的封装如下:

 1 #ifndef THREAD_H 2 #define THREAD_H 3 #include <boost/noncopyable.hpp> 4 #include <functional> 5 #include <pthread.h> 6 class Thread : boost::noncopyable 7 { 8 public: 9 10     typedef std::function<void()> ThreadCallback;11 12     Thread(ThreadCallback cb);13     ~Thread();14 15     void start();16     void join();17 18     static void *runInThread(void *);19 20 21 private:22 23     pthread_t _threadId;24     bool _isRun;25     ThreadCallback _callback;26 };27 28 29 #endif  /*THREAD_H*/
View Code
 1 #include "Thread.h" 2  3 Thread::Thread(ThreadCallback cb) 4     :_threadId(0), 5      _isRun(false), 6      _callback(cb) 7 { 8  9 }10 11 Thread::~Thread()12 {13     if(_isRun)14         pthread_detach(_threadId);15 }16 17 18 void Thread::start()19 {20     pthread_create(&_threadId, NULL, runInThread, this);21     _isRun = true;22 }23 24 void Thread::join()25 {26     pthread_join(_threadId, NULL);27     _isRun = false;28 }29 30 void *Thread::runInThread(void *arg)31 {32     Thread *p = static_cast<Thread *>(arg);33     p->_callback();34     return NULL;35 }
View Code

TimerThread的封装如下:

 1 #ifndef TIMER_THREAD_H 2 #define TIMER_THREAD_H 3 #include <boost/noncopyable.hpp> 4 #include <functional> 5 #include "Timer.h" 6 #include "Thread.h" 7  8 class TimerThread : boost::noncopyable 9 {10 public:11     typedef std::function<void()> Callback;12     TimerThread(int val, int interval, Callback cb);13 14     void start();15     void stop();16 17 private:18     Timer _timer;19     Thread _thread;20 21 };22 23 24 #endif  /*TIMER_THREAD_H*/
View Code
 1 #include "TimerThread.h" 2  3 TimerThread::TimerThread(int val, int interval, Callback cb) 4     :_timer(val, interval, std::move(cb)), 5      _thread(std::bind(&Timer::start, &_timer)) 6 { 7  8 } 9 10 void TimerThread::start()11 {12     _thread.start();13 }14 15 void TimerThread::stop()16 {17     _timer.stop();18     _thread.join();19 }
View Code

测试函数如下:

 1 #include "TimerThread.h" 2 #include <stdio.h> 3 #include <unistd.h> 4  5 void foo() 6 { 7     printf("foo\n"); 8 } 9 10 int main(int argc, const char *argv[])11 {12     TimerThread a(3, 1, &foo);13     a.start();14     sleep(10);15     a.stop();16     return 0;17 }
View Code

在将Timer与Thread结合封装中,我们需要注意:
  a)首先,将用户要执行的任务函数bind到Timer中。

  b)然后,我们将Timer的start函数bind到Thread中。

这样,当我们开启一个该类的线程就相当于开启了一个定时器。

Linux组件封装(六)——定时器的简单封装