epoll有两种触发方式
水平触发(level trigger,LT)与 边沿触发(edge trigger,ET)
水平触发与边缘触发的区别:
水平触发:只要缓冲区有数据就会一直触发
边沿触发:只有在缓冲区增加数据的那一刻才会触发
下面举一个例子说明这两者的区别
/* 使用边沿触发 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
int main(int argc, char *argv[])
{
int epfd, nfds;
struct epoll_event event, events[10];
int i;
epfd = epoll_create(10);
event.data.fd = 0; /* 监听标准输入 */
event.events = EPOLLIN | EPOLLET; /* 读监听、边缘触发 */
//event.events = EPOLLIN; /* 读监听、边缘触发 */
epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);
while (1)
{
nfds = epoll_wait(epfd, events, 10, -1); /* 放回就绪的描述符数量 */
for (i = 0; i < nfds; i++)
{
if (events[i].data.fd == 0)
{
printf("hello world\n");
}
}
}
return 0;
}
此程序的运行效果,只有当往缓冲区写入数据时才会打印。(注意:由于没有将数据从缓冲区读取,所以此时缓冲区一直有数据)
如果将 event.events = EPOLLIN | EPOLLET(边沿触发) 改为 event.events = EPOLLIN(水平触发) ,则一旦输入数据,就会循环打印
这是因为输入数据后,并没有将其从缓冲区读取出来,此时epoll是水平触发,一直检测到缓冲区有数据,所以就一直循环打印
以上就是水平触发和边沿触发的区别
下面解释为什么使用边缘触发必须使用非阻塞
在设置边缘触发时,因为每次发消息只会触发一次(不管缓存区是否还留有数据),所以必须把数据一次性读取出来,否则会影响下一次消息
下面的代码实现的是监听文件描述符,每次固定读取5个字节
先看下面这段代码
int main(int argc, char *argv[])
{
int epfd, nfds;
int i;
struct epoll_event event, events[10];
char buf[5];
int flag;
epfd = epoll_create(10);
event.data.fd = 0; /* 监听标准输入 */
event.events = EPOLLIN | EPOLLET; /* 读监听、边缘触发 */
//event.events = EPOLLIN; /* 读监听、边缘触发 */
epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);
#if 0
flag = fcntl(0, F_GETFL);
fcntl(0, F_SETFL, flag | O_NONBLOCK);
#endif
while (1)
{
int i;
int num = 0;
char c;
nfds = epoll_wait(epfd, events, 5, -1); /* 返回就绪的文件描述符 */
for (i = 0; i < nfds; ++i)
{
if (events[i].data.fd == STDIN_FILENO)
{
for(i = 0; i < 5; i++)
{
buf[i] = getc(stdin);
if(buf[i] == -1)
break;
}
printf("hello world\n");
}
}
}
return 0;
}
这段代码的目的是,使用边沿触发,每次触发读取5个字节,此时getc函数是阻塞读取,这就会引起一个问题,当缓存中的数据小于5时,就会在这里阻塞等待,导致无法处理其他IO,这是非常错误的行为,在并发的服务器中,会导致服务器阻塞,无法处理其他客户端
正确的处理方法是将读取的文件描述符设置为非阻塞,循环读取,如果没有数据了就放回错误,这样就不会让服务器阻塞
可以添加下面这两行代码,将标准输入设置为非阻塞IO
flag = fcntl(0, F_GETFL);
fcntl(0, F_SETFL, flag | O_NONBLOCK);
这就解释了为什么边沿触发必须使用非阻塞的问题。