linux epoll+timerfd只监听到一次事件

初学epoll,对epoll监听fd可读可写状态理解不太深刻。写的代码,第一次运行就挂住了。

先上代码。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <errno.h>

/*
   验证设置定时器value为0,即可停止定时器。不会产生新的超时事件。
   验证设置定时器interval>0,定时器超时后会立即自动重启。
*/

int main(void)
{
    int iTimerFd;
    int iRet;
    int iEpollFd;
    struct epoll_event stEvent;
    int iFds;
    struct itimerspec stTime;
    int iTimerFd2;
    int iFlag = 0;
 
    
    /* 创建epoll,监听事件 */
    iEpollFd = epoll_create(1); /* size参数被忽略但必须大于0 */
    if (iEpollFd < 0) 
    {
        return -1;
    } 
    printf("iEpollFd = %d\n", iEpollFd);

    iTimerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
    if (iTimerFd < 0)
    {
        return -1;
    }
    
    printf("iTimerFd = %d\n", iTimerFd);
    
    memset(&stTime, 0, sizeof(stTime));
    
    stTime.it_value.tv_sec  = 10;
    stTime.it_value.tv_nsec = 0;
    /* 定时器超时后,不自动重启 */
    stTime.it_interval.tv_sec = 0;
    stTime.it_interval.tv_nsec = 0;
    
    /* 开启定时器 */
    iRet = timerfd_settime(iTimerFd, 0, &stTime, NULL);
    if (iRet < 0) 
    {
        return -1;
    }
    
    /* 边沿触发模式,监听socket收包 */
    memset(&stEvent, 0, sizeof(stEvent));
    stEvent.events = EPOLLIN | EPOLLET;
    stEvent.data.fd = iTimerFd;
    /* 向epoll添加监听事件 */
    iRet = epoll_ctl(iEpollFd, EPOLL_CTL_ADD, iTimerFd, &stEvent);
    if (iRet < 0) 
    {
        return -1;
    }

    iTimerFd2 = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
    if (iTimerFd2 < 0)
    {
        return -1;
    }
    
    printf("iTimerFd2 = %d\n", iTimerFd2);
    
    memset(&stTime, 0, sizeof(stTime));
    
    stTime.it_value.tv_sec  = 7;
    stTime.it_value.tv_nsec = 0;
    stTime.it_interval.tv_sec = 7;
    stTime.it_interval.tv_nsec = 0;
    
    /* 开启定时器 */
    iRet = timerfd_settime(iTimerFd2, 0, &stTime, NULL);
    if (iRet < 0) 
    {
        return -1;
    }
    
    /* 边沿触发模式,监听socket收包 */
    memset(&stEvent, 0, sizeof(stEvent));
    stEvent.events = EPOLLIN | EPOLLET;
    stEvent.data.fd = iTimerFd2;
    /* 向epoll添加监听事件 */
    iRet = epoll_ctl(iEpollFd, EPOLL_CTL_ADD, iTimerFd2, &stEvent);
    if (iRet < 0) 
    {
        return -1;
    }
    
    for (;;) 
    {
        /* 阻塞等待事件到来 */
        iFds = epoll_wait(iEpollFd, &stEvent, 1, -1);
        if (iFds <= 0) /* 没有事件,或者监听失败 */
        {
            continue;
        }
        printf("iFds = %d, event = %x, %x\n", iFds, stEvent.events, (stEvent.events & EPOLLIN));
        if (EPOLLIN != (stEvent.events & EPOLLIN)) 
        {
            /* 其余EPOLL事件不处理 */
            continue;
        }
        
        printf("fd = %d\n", stEvent.data.fd);
        
        if (stEvent.data.fd == iTimerFd)
        {
            iRet = timerfd_gettime(iTimerFd, &stTime);
            if (iRet < 0)
            {
                printf("timerfd_gettime err %d\n", errno);
                return -1;
            }

            printf("s = %lu, ns = %lu\n", stTime.it_value.tv_sec, stTime.it_value.tv_nsec);
            break;
        }
        else if (stEvent.data.fd == iTimerFd2)
        {
            if (0 == iFlag)
            {
                stNewTime.it_value.tv_sec  = 0;
                stNewTime.it_value.tv_nsec = 0;
                stNewTime.it_interval.tv_sec = 0;
                stNewTime.it_interval.tv_nsec = 0;
                
                /* 停止定时器 */
                iRet = timerfd_settime(iTimerFd, 0, &stNewTime, NULL);
                if (iRet < 0) 
                {
                    return -1;
                }
                
                printf("first ====\n");
                iFlag++;
            }
            else
            {
                printf("second ====\n");
                break;
            }
        }
    }

    return 0;
}




 

预期是打印出printf("first ====\n");和printf("second ====\n");

然后进程结束。

实际上是只出现了printf("first ====\n");

然后进程挂住了。

加了一些调试打印,发现是epoll监听到第一次事件后,没有新事件产生。

我设置的是ET边沿触发模式,epoll在监听到fd状态从可读-->可写或者可写-->可读,即可产生新事件,通知用户处理。

第一次能监听到,那么fd状态是可写-->可读。但没有可读-->可写。所以下次定时器超时后,fd有新数据可读了,也不会触发第二次事件。因为fd还是处于可读状态。

破案了。

我的timer fd第一次超时后,没把fd数据读清。

man timerfd_create里面有段话,

 read (2) 

        如果定时器自上次使用 timerfd_settime() 修改其设置或自上次成功read (2) 以来已经过期一次或多次,则提供给 read(2) 的缓冲区返回一个无符号 8 字节整数 (uint64_t) 包含已发生的过期次数。

把这个8字节的数读出来就行了。

unsigned long long exp;

在事件处理代码里添加

if (read(stEvent.data.fd, &exp, sizeof(exp)) != sizeof(exp))
    printf("read err, %d\n", errno);

问题解决。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值