C语言实现定时器,Qt测试定时器

  • Post author:
  • Post category:其他


C语言实现定时器demo,支持windows和Linux跨平台使用;

windows使用子线程pthread_cond_timedwait条件变量实现;

linux使用timerfd_create配合epoll_wait实现,timerfd是Linux为用户程序提供的一个定时器接口。这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,所以能够被用于select/poll的应用场景。

CSTimer.h

#ifndef SRC_UTIL_SRC_CTIMER_H_
#define SRC_UTIL_SRC_CTIMER_H_
#include <pthread.h>
#include <stdint.h>
#ifdef _WIN32
#define Using_Phtread 1
#else
#define Using_Phtread 0
#endif
typedef struct CSTimer{
	int32_t taskId;
	int32_t timerfd;
	int32_t efd;
	int32_t isStart;
	int32_t isloop;
	int32_t waitState;
	int32_t waitTime;
	pthread_t threadId;
#if Using_Phtread
    pthread_mutex_t t_lock;
    pthread_cond_t t_cond_mess;
#endif
	void (*doTask)(int32_t taskId,void* user);
	void* user;
}CSTimer;
#ifdef __cplusplus
extern "C"{
#endif
void create_timer(CSTimer* timer,void* user,int32_t taskId,int32_t waitTime);
void destroy_timer(CSTimer* timer);
void timer_start(CSTimer* timer);
void timer_stop(CSTimer* timer);
#ifdef __cplusplus
}
#endif
#endif

CSTimer.c

#include "CSTimer.h"

#include <stdio.h>
#include <string.h>
#if !Using_Phtread
#include <sys/time.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#endif
#include <time.h>
#include <fcntl.h>

void create_timer(CSTimer *timer, void *user, int32_t taskId,int32_t waitTime)
{
    if (timer == NULL)
        return;
    timer->isloop = 0;
    timer->isStart = 0;
    timer->waitState = 0;
    timer->waitTime = waitTime;
#if Using_Phtread
    pthread_mutex_init(&timer->t_lock,NULL);
    pthread_cond_init(&timer->t_cond_mess,NULL);
#else
    /** timerfd_create 创建一个新的计时器对象,并返回引用该计时器的文件描述符
      * clockid 参数指定使用那种类型的时钟(clock)来实现计时器(timer),CLOCK_REALTIME:系统实时时间,CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响等
      * flags:TFD_NONBLOCK:非阻塞模式,TFD_CLOEXEC:表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递
      */
    timer->timerfd = timerfd_create(CLOCK_REALTIME, 0);
#endif
    timer->efd = -1;
    timer->user = user;
    timer->doTask = NULL;
    timer->taskId = taskId;
}
void destroy_timer(CSTimer *timer) {
    if (timer == NULL)
        return;
}
void* run_timer_thread(void *obj) {
    CSTimer *timer = (CSTimer*) obj;
    timer->isStart = 1;
    timer->isloop = 1;
    /*****************windows**********************/
#if Using_Phtread
    struct timespec outtime;
    struct timeval now;
    pthread_mutex_lock(&timer->t_lock);
    while (timer->isloop) {
        gettimeofday(&now, NULL);

        long nsec = now.tv_usec * 1000 + (timer->waitTime % 1000) * 1000000;
        outtime.tv_sec=now.tv_sec + nsec / 1000000000 + timer->waitTime / 1000;
        outtime.tv_nsec=nsec % 1000000000;

        timer->waitState=1;

        pthread_cond_timedwait(&timer->t_cond_mess, &timer->t_lock,&outtime);
        timer->waitState=0;
        if(timer->doTask) timer->doTask(timer->taskId, timer->user);
    }
    pthread_mutex_unlock(&timer->t_lock);
#else
    /*****************linux**********************/

    /** struct itimerspec 指定计时器的初始到期时间和到期间隔,包含两个 timespec结构体(一个单位秒,一个单位纳秒)
      * 参数it_interval,指定初始到期后重复计时器到期的时间段
      * 参数it_value,指定计时器的初始到期时间
      */
    struct itimerspec itimer;
    itimer.it_value.tv_sec = timer->waitTime / 1000;
    itimer.it_value.tv_nsec = (timer->waitTime % 1000) * 1000 * 1000;
    itimer.it_interval.tv_sec = timer->waitTime / 1000;
    itimer.it_interval.tv_nsec = (timer->waitTime % 1000) * 1000 * 1000;

    /** timerfd_settime 用于设置新的超时时间,并开始计时
      * 参数ufd,是timerfd_create返回的文件句柄
      * 参数flags,为1代表设置的是绝对时间;为0代表相对时间
      * 参数utmr,为需要设置的时间
      * 参数otmr,为定时器这次设置之前的超时时间
      * 返回值,0成功,其他失败
      */
    int ret = timerfd_settime(timer->timerfd, /*TFD_TIMER_ABSTIME*/0, &itimer, NULL);
    if (ret == -1) {
        printf("err:timerfd_settime\n");
    }

    int opts;
    opts = fcntl(timer->timerfd, F_GETFL);//获取文件打开方式的标志,标志值含义与open调用一致
    if (opts < 0) {
        printf("err:fcntl(sock,GETFL)\n");
        _exit(1);
    }
    opts = opts | O_NONBLOCK;
    //F_SETFL,设置文件状态标识,第三个参数0为阻塞,O_NONBLOCK为非阻塞,返回-1表示失败
    if (fcntl(timer->timerfd, F_SETFL, opts) < 0) {
        printf("err:fcntl(sock,SETFL,opts)\n");
        _exit(1);
    }
    /** epoll_create 创建一个epoll实例(创建一个监测一个描述符epoll的句柄描述符)
      * 参数max_size,标识这个监听的数目最大有多大
      * 返回值,新epoll实例的文件描述符,失败返回-1
      */
    timer->efd = epoll_create(1);
    struct epoll_event tev;
    tev.events = EPOLLIN | EPOLLET;//设置描述符的事件
    tev.data.fd = timer->timerfd;//设置要监测的文件描述符的值

    /** epoll_ctl 操作epoll函数所生成的实例(此处,把定时器描述符添加到epoll检测队列中,函数注册要监听的事件类型为ev中的events)
      * 参数epfd,操作的epoll实例的文件描述符
      * 参数op,EPOLL_CTL_ADD:在文件描述符epfd所引用的epoll实例上注册目标文件描述符fd,并将事件事件与内部文件链接到fd
      * 参数fd,需要操作监听的文件描述符
      * 参数epoll_event,结构体,需要监听的事件
      */
    epoll_ctl(timer->efd, EPOLL_CTL_ADD, timer->timerfd, &tev);
    struct epoll_event ev[1];
    while (timer->isloop) {

        /** epoll_wait 等待事件的产生,即等待epoll文件描述符上的I / O事件
          * 参数epfd,操作的epoll实例的文件描述符
          * 参数events,用来从内核得到事件的集合
          * 参数maxevents,告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size
          * 参数timeout,是超时时间(毫秒,0会立即返回,-1将会无限阻塞)。
          * 返回值,返回需要处理的事件数目,如返回0表示已超时
          */
        int nev = epoll_wait(timer->efd, ev, 1, 200);//等待事件的产生,并把检测到的信息保存到ev这个结构体中

        if (nev > 0 && (ev[0].events & EPOLLIN)) //检测到文件符可以读的事件
        {
            if(ev[0].data.fd == timer->timerfd)//检测到的文件描述符的值与被检测的文件描述符的值相等
            {
                uint64_t res;
                int bytes =	read(timer->timerfd, &res, sizeof(res));

                if (timer->doTask)
                    timer->doTask(timer->taskId, timer->user);
            }
        }
    }
#endif
    timer->isStart = 0;
    return NULL;
}
void timer_start(CSTimer *timer) {
    if (timer == NULL)
        return;
    if (pthread_create(&timer->threadId, 0, run_timer_thread, timer)) {
        printf("Thread::start could not start thread\n");
    }
}

void timer_stop(CSTimer *timer) {
    if (timer == NULL)
        return;
    if (timer->isStart) {
        timer->isloop = 0;
#if Using_Phtread
    if(timer->waitState){
        pthread_mutex_lock(&timer->t_lock);
        pthread_cond_signal(&timer->t_cond_mess);
        pthread_mutex_unlock(&timer->t_lock);

    }
#else
        if (timer->isStart) {
            struct epoll_event tev;
            tev.events = EPOLLIN | EPOLLET;
            tev.data.fd = timer->timerfd;
            epoll_ctl(timer->efd, EPOLL_CTL_DEL, timer->timerfd, &tev);//把定时器文件描述从epoll监听列表中删除
            close(timer->efd);
            timer->efd = -1;

        }
        close(timer->timerfd);
        timer->timerfd = -1;
#endif
        while (timer->isStart)
            usleep(1000);
    }
}

使用QT测试:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QDebug>
#include <QDateTime>

void g_doTask(int32_t taskId, void *user)
{
    if (user == NULL)
        return;

    MainWindow *mw = (MainWindow*) user;
    mw->doWork();
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    create_timer(&mCSTimer,this,1,5000);
    mCSTimer.doTask = g_doTask;
    timer_start(&mCSTimer);
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<"tart"<<QDateTime::currentDateTime().toString("hh:mm:ss");
}

MainWindow::~MainWindow()
{
    timer_stop(&mCSTimer);
    destroy_timer(&mCSTimer);

    delete ui;
}

void MainWindow::doWork()
{
    qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<QDateTime::currentDateTime().toString("hh:mm:ss");
}

打印输出

[ ../mainwindow.cpp ] 25 MainWindow tart "17:22:53"
[ ../mainwindow.cpp ] 38 doWork "17:22:58"
[ ../mainwindow.cpp ] 38 doWork "17:23:03"
[ ../mainwindow.cpp ] 38 doWork "17:23:08"
[ ../mainwindow.cpp ] 38 doWork "17:23:13"
[ ../mainwindow.cpp ] 38 doWork "17:23:18"



版权声明:本文为weixin_40355471原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。