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 //创建fd 25 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 }
这里需要注意:一旦定时器到期,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 //创建fd 25 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 }
我们可以将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*/
实现代码如下:
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 }
我们可以将定时器与线程封装到一起,这样,每个线程就是一个定时器。
线程的封装如下:
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*/
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 }
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*/
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 }
测试函数如下:
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 }
在将Timer与Thread结合封装中,我们需要注意:
a)首先,将用户要执行的任务函数bind到Timer中。
b)然后,我们将Timer的start函数bind到Thread中。
这样,当我们开启一个该类的线程就相当于开启了一个定时器。