侧边栏壁纸
博主头像
如此肤浅博主等级

但行好事,莫问前程!

  • 累计撰写 18 篇文章
  • 累计创建 8 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

定时器

如此肤浅
2022-10-17 / 0 评论 / 0 点赞 / 70 阅读 / 2,468 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-10-17,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

网络程序需要处理定时事件,比如定期检测一个客户连接的活动状态。服务器程序通常管理着众多定时事件,因此有效地组织这些定时事件,使之能在预期的时间点被触发且不影响服务器的主要逻辑,对于服务器的性能有着至关重要的影响。为此,我们要将每个定时事件分别封装成定时器,并使用某种容器类数据结构,比如链表、排序链表和时间轮,将所有定时器串联起来,以实现对定时事件的统一管理。

定时是指在一段时间之后触发某段代码的机制,我们可以在这段代码中依次处理所有到期的定时器。Linux 提供了三种定时方法,它们是:
① socket 选项 SO_RCVTIMEO 和 SO_SNDTIMEO。
② SIGALRM 信号
③ I/O 复用系统调用的超时参数。

socket 选项 SO_RCVTIMEO 和 SO_SNDTIMEO

SO_RCVTIMEO 和 SO_SNDTIMEO 分别用来设置 socket 接收数据超时时间和发送数据超时时间。因此,这两个选项仅对与数据接收和发送相关的 socket 专用系统调用有效,这些系统调用包括 send、sendmsg、recv、recvmsg、accept 和 connect。

系统调用 有效选项 系统调用超时后的行为
send SO_SNDTIMEO 返回-1,设置 errno 为 EAGAIN 或 EWOULDBLOCK
sendmsg SO_SNDTIMEO 同上
recv SO_RCVTIMEO 同上
recvmsg SO_RCVTIMEO 同上
accept SO_RCVTIMEO 同上
connect SO_SNDTIMEO 返回-1,设置 errno 为 EINPROGRESS

在程序中,我们可以根据系统调用(send、sendmsg、recv、recvmsg、accept 和 connect)的返回值以及 errno 来判断超时时间是否已到,进而决定是否开始处理定时任务。

sigalrm 信号

由 alarm 和 setitimer 函数设置的实时闹钟一旦超时,将触发 SIGALRM 信号。因此,我们可以利用该信号的信号处理函数来处理定时任务。但是,如果要处理多个定时任务,我们就需要不断地触发 SIGALRM 信号,并在其信号处理函数中执行到期的任务。一般而言,SIGALRM 信号按照固定的频率生成,即由 alarm 或 setitimer 函数设置的定。

alarm

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

功能

  • 设置定时器(闹钟)。在指定 seconds 后,内核会给当前进程发送 SIGALRM 信号。进程收到该信号,默认动作终止。
  • 每个进程都有且只有唯一的一个定时器。
  • 定时与进程状态无关!就绪、运行、阻塞、暂停、终止、僵尸……无论进程处于何种状态,alarm 都计时。

seconds

  • 指定的时间,以秒为单位。

返回值

  • 返回 0 或剩余的秒数。

setitimer

#include <sys/time.h>

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

功能

  • 设置定时器(闹钟)。可代替 alarm 函数,精度微秒。

which: 指定定时方式

  • 自然定时:ITIMER_REAL → SIGALRM 计算自然时间;
  • 虚拟空间计时(用户空间):ITIMER_VIRTUAL → SIGVTALRM 只计算进程占用 cpu 的时间;
  • 运行时计时(用户 + 内核):ITIMER_PROF → SIGPROF 计算占用 cpu 及执行系统调用的时间。

new_value

  • 负责设定超时时间。

old_value

  • 存放旧的超时时间,一般为 NULL。

返回值

  • 成功返回 0,失败返回 -1。
struct itimerval {
	struct timerval it_interval; // 闹钟触发周期
    struct timerval it_value; // 闹钟触发时间
};

struct timeval {
	long tv_sec; // 秒
    long tv_usec; // 微妙
};

I/O 复用系统调用的超时参数

Linux 下的 3 组 I/O 复用系统调用都带有超时参数,因此它们不仅能统一处理信号和 I/O 事件,也能统一处理定时事件。但是由于 I/O 复用系统调用可能在超时时间到期之前就返回(有 I/O 事件发生),所以如果我们要利用它们来定时,就需要不断更新定时参数以反映剩余的时间。

#define TIMEOUT 5000

int timeout = TIMEOUT;
time_t start = time(NULL);
time_t end = time(NULL);
while(1) {
	printf("the timeout is now %d mil-seconds\n", timeout);
    start = time(NULL);
    int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, timeout);
    if((number < 0) && (errno != EINTR)) {
    	printf("epoll failure\n");
        break;
    }
    
    /*
    如果epoll_wait成功返回0,则说明超时时间到,
    此时便可处理定时任务,并重置定时时间
    */
    if(number == 0) {
    	timeout = TIMEOUT;
        continue;
    }
    
    end = time(NULL);
    /*
    如果epoll_wait的返回值大于0,
    则本次epoll_wait调用持续的时间是(end-satrt)*1000 ms,
    我们需要将定时时间timeout减去这段时间,以获得下次epoll_wait调用的超时参数
    */
    timeout -= (end - start) * 1000;
    /*
    重新计算之后的timeout值有可能等于0,说明本次epoll_wait调用返回时,
    不仅有文件描述符就绪,而且其超时时间也刚好达到,
    此时我们也要处理定时任务,并重置定时时间
    */
    if(timeout <= 0) {
    	timeout = TIMEOUT;
    }
    
    // handle connections
}
0

评论区