初学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);
问题解决。