Epoll入门理解及使用示例
在linux网络编程中,相信epoll的使用一定必不可少。大家都知道传统的select在使用过程中不仅对鉴定的事件数目上有一定限制,事件到达一定数量后,效率也会降低,那么在实际应用中呃epoll的使用就脱颖而出了。那么本文就是针对于初识epoll的伙伴的一个入门小demo,文章中的代码示例转载自国外网站,相对于cdsn很多博客上的代码示例我认为更方便理解。
文章分为三个部分,分别依次加入对epoll中三个函数:
epoll_create(int size);
epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
的使用
在以下的代码中将使用这些头文件
#include <stdio.h> // 用于 fprintf()
#include <unistd.h> // 用于close(), read()
#include <sys/epoll.h> // 用于 epoll_create1(), epoll_ctl(), struct epoll_event
#include <string.h> // 用于 strncmp
Step1 创建epoll文件描述符
首先将完成对epoll实例的创建和关闭过程:
#include <stdio.h> // 用于 fprintf()
#include <unistd.h> // 用于 close()
#include <sys/epoll.h> // 用于 epoll_create1()
int main( )
{
int epoll_fd = epoll_create1 ( 0 ) ;
if(epoll_fd == - 1 )
{
fprintf ( stderr, "创建 epoll 文件描述符失败\n" ) ;
return 1 ;
}
if(close(epoll_fd ))
{
fprintf ( stderr, "无法关闭 epoll 文件描述符\n" ) ;
return 1 ;
}
return 0 ;
}
epoll_create(int size):size用来告诉内核这个监听的数目一共有多大。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
Step2 为epoll添加文件描述符来观察
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值, 第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事
#include <stdio.h> // for fprintf()
#include <unistd.h> // for close()
#include <sys/epoll.h> // for epoll_create1(), epoll_ctl(), struct epoll_event
int main()
{
struct epoll_event event;
int epoll_fd = epoll_create1(0);
if(epoll_fd == -1)
{
fprintf(stderr, "Failed to create epoll file descriptor\n");
return 1;
}
event.events = EPOLLIN;
event.data.fd = 0;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, 0, &event))
{
fprintf(stderr, "Failed to add file descriptor to epoll\n");
close(epoll_fd);
return 1;
}
if(close(epoll_fd))
{
fprintf(stderr, "Failed to close epoll file descriptor\n");
return 1;
}
return 0;
}
在这里,添加了一个 epoll_event 结构的实例,并使用epoll_ctl()将文件描述符 0 添加到我们的 epoll 实例epoll_fd中。我们为最后一个参数传递的事件结构让 epoll 知道我们正在寻找仅观察输入事件EPOLLIN,并让我们提供一些将为事件返回的用户定义数据。
Step3 等待事件的产生
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
那么最后我们加入epoll_wait函数,简单展示一下epoll的运行过程
#define MAX_EVENTS 5
#define READ_SIZE 10
#include <stdio.h> // for fprintf()
#include <unistd.h> // for close(), read()
#include <sys/epoll.h> // for epoll_create1(), epoll_ctl(), struct epoll_event
#include <string.h> // for strncmp
int main()
{
int running = 1, event_count, i;
size_t bytes_read;
char read_buffer[READ_SIZE + 1];
struct epoll_event event, events[MAX_EVENTS];
int epoll_fd = epoll_create1(0);
if(epoll_fd == -1)
{
fprintf(stderr, "Failed to create epoll file descriptor\n");
return 1;
}
event.events = EPOLLIN;
event.data.fd = 0;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, 0, &event))
{
fprintf(stderr, "Failed to add file descriptor to epoll\n");
close(epoll_fd);
return 1;
}
while(running)
{
printf("\nPolling for input...\n");
event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, 30000);
printf("%d ready events\n", event_count);
for(i = 0; i < event_count; i++)
{
printf("Reading file descriptor '%d' -- ", events[i].data.fd);
bytes_read = read(events[i].data.fd, read_buffer, READ_SIZE);
printf("%zd bytes read.\n", bytes_read);
read_buffer[bytes_read] = '\0';
printf("Read '%s'\n", read_buffer);
if(!strncmp(read_buffer, "stop\n", 5))
running = 0;
}
}
if(close(epoll_fd))
{
fprintf(stderr, "Failed to close epoll file descriptor\n");
return 1;
}
return 0;
}
在这里添加了一些新变量来支持和展示我正在做的事情。我还添加了一个 while 循环,它将继续从被监视的文件描述符中读取,直到其中一个说“停止”。我使用epoll_wait()等待来自 epoll 实例的事件发生,结果将存储在事件数组中,最长为MAX_EVENTS,超时时间为 30 秒。epoll_wait() 的返回值表示事件数组中有多少成员被事件数据填充。除此之外,它只是打印出它得到的东西并做一些基本的逻辑来结束事情。
下面展示一下运行的结果:
$ ./epoll_example
Polling for input..
#开始输入字符
Hello!
1 ready events
Reading file descriptor '0' -- 7 bytes read.
Read 'hello!
'
Polling for input...
#输入更长的字符
this is too long for the buffer we made
1 ready events
Reading file descriptor '0' -- 10 bytes read.
Read 'this is to'
Polling for input...
1 ready events
Reading file descriptor '0' -- 10 bytes read.
Read 'o long for'
Polling for input...
1 ready events
Reading file descriptor '0' -- 10 bytes read.
Read ' the buffe'
Polling for input...
1 ready events
Reading file descriptor '0' -- 10 bytes read.
Read 'r we made
'
Polling for input...
#输入stop以结束监听
stop
1 ready events
Reading file descriptor '0' -- 5 bytes read.
Read 'stop
'
本文转载及参考自:
https://suchprogramming.com/epoll-in-3-easy-steps/