Bootstrap

c语言中的定时器

一 timerfd 定时器类

  timerfd 是 Linux 为用户程序提供的一个定时器接口。这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,因此可以配合 select/poll/epoll 等使用。
  timerfd 的特点是将时间变成一个文件描述符,定时器超时时,文件可读。这样就能很容易融入 select(2)/poll(2)/epoll(7) 的框架中,用统一的方式来处理IO事件、超时事件。这也是 Reactor 模式的特点。
  定时器是每个进程自己的,不是在 fork 时继承的,不会被传递给子进程。编译时加编译选项 -lrt

1 timerfd定时器与传统Reactor模式定时器

  传统 Reactor 模式使用 select/poll/epolltimeout 参数实现定时功能,但其精度只有毫秒(注意区分表示精度和实际精度,表示精度可能为微妙和纳秒)。
  另外,select/poll/epoll 的定时器也有一个缺陷,那就是只能针对的是所有监听的文件描述符 fd,而非绑定某个 fd
  timerfd 可以解决这个问题,单独为某个 fd 指定定时器。

2 timerfd_create 创建定时器对象函数

  头文件
  #include <sys/timerfd.h>
  函数原型
  int timerfd_create(int clockid, int flags);
  作用
  创建一个定时器对象,同时返回一个与之关联的文件描述符
  参数含义
  clockid:标识指定的时钟计数器,可选值CLOCK_REALTIMECLOCK_MONOTONIC
  CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从 UTC1970-1-1 0:0:0 开始计时,中间时刻如果系统时间被用户改成其他,则对应的时间相应改变。
  CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响。
  flags:可以为 0TFD_NONBLOCKTFD_CLOEXEC
  0 表示无属性,TFD_NONBLOCK 表示设置为非阻塞模式,TFD_CLOEXEC 表示当程序执行 exec 函数时本 fd 将被系统自动关闭,表示不传递。在 2.6.26 之前的 Linux 版本中,标志必须指定为零。
  返回值
  如果成功返回一个定时器的文件描述符,失败返回-1

3 timerfd_settime 设置新的超时时间函数。

  头文件
  #include <sys/timerfd.h>
  函数原型
  int timerfd_settime(int fd, int flags,const struct itimerspec *new_value,struct itimerspec *old_value);
  作用
  此函数用于设置新的超时时间,并开始计时,能够启动和停止定时器 ;
  参数含义
  fdtimerfd_create 函数返回的文件句柄;
  flags:设置为 1 代表设置的是绝对时间;为 0 代表相对时间;
  new_value:指定定时器的超时时间以及超时间隔时间,new_value.it_value 非零则启动定时器,否则关闭定时器,如果 new_value.it_interval 为0,则定时器只定时一次,即初始那次,否则之后每隔设定时间超时一次;
  old_value:不为 null,则返回定时器这次设置之前的超时时间;

struct itimerspec
{
    struct timespec it_interval; //定时间隔周期
    struct timespec it_value; //第一次超时时间
};
struct timespec
{
    __time_t tv_sec; /* 秒. */
    __syscall_slong_t tv_nsec; /* 纳秒. */
};

  返回值
  成功返回 0,失败返回-1

4 timerfd_gettime 获取距离下次超时的剩余时间

  头文件
  #include <sys/timerfd.h>
  函数原型
  int timerfd_gettime(int fd, struct itimerspec *curr_value);
  作用
  获取距离下次超时剩余的时间。
  参数含义
  fdtimerfd_create 函数返回的文件句柄。
  curr_valuecurr_value.it_value 字段表示距离下次超时的时间,如果该值为 0,表示计时器已经解除,改字段表示的值永远是一个相对值,无论 TFD_TIMER_ABSTIME 是否被设置 curr_value.it_interval 定时器间隔时间。
  返回值
  成功返回 0,失败返回-1。

5 操作定时器fd

  read 读取 timefd 超时事件通知。当定时器超时,read 读事件发生即可读,返回超时次数(从上次调用timerfd_settime() 启动开始或上次 read 成功读取开始),它是一个 8 字节的 unit64_t 类型整数,如果定时器没有发生超时事件,则 read 将阻塞若 timerfd 为阻塞模式,否则返回 EAGAIN 错误( O_NONBLOCK 模式),如果 read 时提供的缓冲区小于 8 字节将以 EINVAL 错误返回。
  poll(2)/select(2)/epoll (7) 监听定时器 fdfd 可读时,会收到就绪通知。
  close(2) 关闭 fd 对应定时器。如果不需要该定时器,可调用 close 关闭之。

6 优点

  • 与 epoll、select 和 poll 等事件驱动机制兼容。
  • 可以设置为周期性定时器。
  • 提供了高精度和可靠的定时器功能。

7 用途

  • 实现定时事件的通知机制。
  • 用于需要精确定时功能的应用程序,如网络服务器定时任务。
  • 与事件循环机制(如 epoll)配合使用,处理异步任务。
  • timerfd_create 提供了一种高效、便捷的定时器创建和使用方法,适用于各种需要定时通知的 Linux 应用程序。

8 配合定时器使用

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <unistd.h>
 
#define EPOLL_LISTEN_CNT 256
#define EPOLL_LISTEN_TIMEOUT 500
 
static int g_epollfd = -1;
static int g_timerfd = -1;
 
uint64_t tot_exp = 0;
 
static void print_elapsed_time(void)
{
    static struct timespec start;
    struct timespec curr;
    static int first_call = 1;
    int secs, nsecs;
    if (first_call) 
    {
        first_call = 0;
        if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
            printf("clock_gettime\n");
    }
    if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
        printf("clock_gettime\n");
    secs = curr.tv_sec - start.tv_sec;
    nsecs = curr.tv_nsec - start.tv_nsec;
    if (nsecs < 0) 
    {
        secs--;
        nsecs += 1000000000;
    }
    printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
}
 
void timerfd_handler(int fd)
{
    uint64_t exp = 0;
    read(fd, &exp, sizeof(uint64_t));
    tot_exp += exp;
    print_elapsed_time();
    printf("read: %llu, total: %llu\n", (unsigned long long)exp, (unsigned long long)tot_exp);
    return;
}
 
void epoll_event_handle(void)
{
    int i = 0;
    int fd_cnt = 0;
    int sfd;
    struct epoll_event events[EPOLL_LISTEN_CNT]; 
    memset(events, 0, sizeof(events));
    while(1) 
    { 
        /* wait epoll event */
        fd_cnt = epoll_wait(g_epollfd, events, EPOLL_LISTEN_CNT,EPOLL_LISTEN_TIMEOUT); 
        for(i = 0; i < fd_cnt; i++) 
        { 
            sfd = events[i].data.fd;
            if(events[i].events & EPOLLIN) 
            { 
                if (sfd == g_timerfd) 
                {
                    timerfd_handler(sfd); 
                } 
            } 
        } 
    } 
}
 
int epoll_add_fd(int fd)
{
    int ret;
    struct epoll_event event;
    memset(&event, 0, sizeof(event));
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    ret = epoll_ctl(g_epollfd, EPOLL_CTL_ADD, fd, &event);
    if(ret < 0) 
    {
        printf("epoll_ctl Add fd:%d error, Error:[%d:%s]\n", fd, errno,strerror(errno));
        return -1;
    }
    printf("epoll add fd:%d--->%d success\n", fd, g_epollfd);
    return 0; 
}
 
int epollfd_init()
{
    int epfd;
    /* create epoll fd */
    epfd = epoll_create(EPOLL_LISTEN_CNT); 
    if (epfd < 0) 
    {
        printf("epoll_create error, Error:[%d:%s]\n", errno, 
        strerror(errno));
        return -1;
    }
    g_epollfd = epfd;
    printf("epoll fd:%d create success\n", epfd);
    return epfd;
}
 
int timerfd_init()
{
    int tmfd;
    int ret;
    struct itimerspec new_value;
    new_value.it_value.tv_sec = 2;
    new_value.it_value.tv_nsec = 0;
    new_value.it_interval.tv_sec = 1;
    new_value.it_interval.tv_nsec = 0;
    tmfd = timerfd_create(CLOCK_MONOTONIC, 0);
    if (tmfd < 0) 
    {
        printf("timerfd_create error, Error:[%d:%s]\n", errno, 
        strerror(errno));
        return -1;
    }
    ret = timerfd_settime(tmfd, 0, &new_value, NULL);
    if (ret < 0) 
    {
        printf("timerfd_settime error, Error:[%d:%s]\n", errno, 
        strerror(errno));
        close(tmfd);
        return -1;
    }
    if (epoll_add_fd(tmfd)) 
    {
        close(tmfd);
        return -1;
    }
    g_timerfd = tmfd;
    return 0;
}
 
int main(int argc, char **argv)
{
    if (epollfd_init() < 0) 
    {
        return -1;
    }
    if (timerfd_init()) 
    {
        return -1;
    }
    /* event handle */
    epoll_event_handle();
    return 0;
}

9 示例代码

  下面的示例演示了如何使用 timerfd_create 来创建一个定时器,并在定时器到期时读取事件。

#include <iostream>
#include <sys/timerfd.h>
#include <unistd.h>
#include <cstring>
#include <ctime>

void set_timer(int timer_fd, uint64_t initial_expire, uint64_t interval)
{
    struct itimerspec new_value;
    memset(&new_value, 0, sizeof(new_value));

    // Initial expiration
    new_value.it_value.tv_sec = initial_expire;
    new_value.it_value.tv_nsec = 0;

    // Interval for periodic timer
    new_value.it_interval.tv_sec = interval;
    new_value.it_interval.tv_nsec = 0;

    timerfd_settime(timer_fd, 0, &new_value, nullptr);
}

int main()
{
    // Create a timer that uses the monotonic clock and is non-blocking
    int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
    if (timer_fd == -1) {
        perror("timerfd_create");
        return 1;
    }

    // Set the timer to expire after 5 seconds and then every 1 second
    set_timer(timer_fd, 5, 1);

    while (true)
    {
        uint64_t expirations;
        ssize_t s = read(timer_fd, &expirations, sizeof(expirations));
        if (s != sizeof(expirations)) {
            if (s == -1) {
                if (errno == EAGAIN) {
                    // Non-blocking mode, no data to read
                    continue;
                } else {
                    perror("read");
                    close(timer_fd);
                    return 1;
                }
            } else {
                std::cerr << "read error, expected " << sizeof(expirations) << " bytes, got " << s << " bytes.\n";
                close(timer_fd);
                return 1;
            }
        }

        std::cout << "Timer expired " << expirations << " times\n";
    }

    close(timer_fd);
    return 0;
}

10 解释

  创建定时器:

int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);

  设置定时器:

void set_timer(int timer_fd, uint64_t initial_expire, uint64_t interval)
{
    struct itimerspec new_value;
    memset(&new_value, 0, sizeof(new_value));

    new_value.it_value.tv_sec = initial_expire;
    new_value.it_value.tv_nsec = 0;

    new_value.it_interval.tv_sec = interval;
    new_value.it_interval.tv_nsec = 0;

    timerfd_settime(timer_fd, 0, &new_value, nullptr);
}

  读取定时器事件:

uint64_t expirations;
ssize_t s = read(timer_fd, &expirations, sizeof(expirations));

  read 调用会在定时器到期时返回,读取到的值表示定时器到期的次数。

二 timer_create系列定时器函数

  进程可以通过调用 timer_create() 创建特定的定时器,定时器是每个进程自己的,不是在 fork 时继承的,不会被传递给子进程。编译时加编译选项 -lrt。
  linuxtimer_t 定时器的使用,总共有3个函数。

timer_create()
timer_settime()
timer_gettime()

  头文件:

#include <signal.h>
#include <time.h>

  函数声明:
int timer_create(clockid_t clockid, struct sigevent* sevp, timer_t* timerid)
  功能:创建一个POSIX标准的进程定时器
  参数:
    @clockid 可选系统系统的宏,比如 CLOCK_REALTIME
    @sevp 环境值,结构体 struct sigevent 变量的地址
    @timerid 定时器标识符,结构体 timer_t 变量的地址
  返回值:0-成功;-1-失败,errno被设置。
  头文件:

#include <time.h>

  函数声明:

int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
int timer_gettime(timer_t timerid, struct itimerspec *curr_value);

  功能:设置或者获得定时器时间值参数:
  参数:
    @timerid 定时器标识
    @flags 0标识相对时间,1标识绝对时间
    @new_value 定时器的新初始值和间隔,如下面的it
    @old_value 取值通常为0或NULL,若不为NULL,则返回定时器前一个值。
  举例1:采用新线程派驻的通知方式

#include <stdio.h> 
#include <signal.h> 
#include <time.h> 
#include <string.h> 
#include <stdlib.h> 
#include <unistd.h> 
 
void timer_thread(union sigval v) 
{ 
    printf("timer_thread function! %d\n", v.sival_int); 
} 
 
int main() 
{ 
    timer_t timerid; 
    struct sigevent evp; 
    memset(&evp, 0, sizeof(struct sigevent));       //清零初始化 
 
    evp.sigev_value.sival_int = 111;                //也是标识定时器的,回调函数可以获得 
    evp.sigev_notify = SIGEV_THREAD;                //线程通知的方式,派驻新线程 
    evp.sigev_notify_function = timer_thread;       //线程函数地址 
 
    if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1) 
    {   
        perror("fail to timer_create"); 
        exit(-1); 
    }   
 
    /* 第一次间隔it.it_value这么长,以后每次都是it.it_interval这么长,就是说it.it_value变0的时候会>装载it.it_interval的值 */
    struct itimerspec it; 
    it.it_interval.tv_sec = 1;  // 回调函数执行频率为1s运行1次
    it.it_interval.tv_nsec = 0; 
    it.it_value.tv_sec = 3;     // 倒计时3秒开始调用回调函数
    it.it_value.tv_nsec = 0; 
 
    if (timer_settime(timerid, 0, &it, NULL) == -1) 
    {   
        perror("fail to timer_settime"); 
        exit(-1); 
    }   
 
    //pause();
    while (1);
 
    return 0; 
} 
/*
 * int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
 * 获取timerid指定的定时器的值,填入curr_value
 */ 

  举例2:通知方式为信号的处理方式

#include <stdio.h> 
#include <time.h> 
#include <stdlib.h> 
#include <signal.h> 
#include <string.h> 
#include <unistd.h> 
 
#define CLOCKID CLOCK_REALTIME 
 
void sig_handler(int signo) 
{ 
    printf("timer_signal function! %d\n", signo); 
} 
 
int main() 
{ 
    // XXX int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 
    // signum--指定的信号编号,可以指定SIGKILL和SIGSTOP以外的所有信号编号 
    // act结构体--设置信号编号为signum的处理方式 
    // oldact结构体--保存上次的处理方式 
    // 
    // struct sigaction   
    // { 
    // void (*sa_handler)(int);         //信号响应函数地址 
    // void (*sa_sigaction)(int, siginfo_t *, void *);   //但sa_flags为SA——SIGINFO时才使用 
    // sigset_t sa_mask;         //说明一个信号集在调用捕捉函数之前,会加入进程的屏蔽中,当捕捉函数返回时,还原 
    // int sa_flags; 
    // void (*sa_restorer)(void);   //未用 
    // }; 
    // 
    timer_t timerid; 
    struct sigevent evp; 
 
    struct sigaction act; 
    memset(&act, 0, sizeof(act)); 
    act.sa_handler = sig_handler; 
    act.sa_flags = 0; 
 
    // XXX int sigaddset(sigset_t *set, int signum);  //将signum指定的信号加入set信号集
    // XXX int sigemptyset(sigset_t *set);          //初始化信号集 
 
    sigemptyset(&act.sa_mask); 
 
    if (sigaction(SIGUSR1, &act, NULL) == -1) 
    { 
        perror("fail to sigaction"); 
        exit(-1); 
    } 
 
    memset(&evp, 0, sizeof(struct sigevent)); 
    evp.sigev_signo = SIGUSR1; 
    evp.sigev_notify = SIGEV_SIGNAL; 
    if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1) 
    { 
        perror("fail to timer_create"); 
        exit(-1); 
    } 
 
    struct itimerspec it; 
    it.it_interval.tv_sec = 2; 
    it.it_interval.tv_nsec = 0; 
    it.it_value.tv_sec = 1; 
    it.it_value.tv_nsec = 0; 
    if (timer_settime(timerid, 0, &it, 0) == -1) 
    { 
        perror("fail to timer_settime"); 
        exit(-1); 
    } 
 
    pause(); 
 
    return 0; 
} 

跨平台的定时器

https://github.com/grijjy/DelphiTimerQueue/tree/master

;