关于epoll
上一篇博文提到过select的使用方法。今天说一下epoll的使用方法。epoll相对于select而言有如下几个方面的优点:
-
对于要监听的fd的数量没有限制
-
内核会保存需要监听的fd,无需每次都进行初始化
-
只返回产生事件(可读取、可写等)的fd,无需遍历监听的所有fd
使用epoll主要用进行一步阻塞的调用。其基本的步骤为:
-
首先通过epoll_create调用创建一个epoll文件描述符(epfd)
-
通过epoll_ctl对这个描述符进行设置,具体包括:添加需要监听的文件描述符、socket描述符等;设置相关监听的类型(例如监听是否可读:EPOLLIN,)等
-
通过epoll_wait进行事件的监听
看一段代码示例:
#include <sys/epoll.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc, char* argv[])
{
//epoll_event用于epoll_ctl中与关联的fd进行设置,events用于epoll_wait返回可以处理的fd的信息
struct epoll_event epollEvent, events[10];
//监听时间设置为可读时触发
epollEvent.events = EPOLLIN ;
epollEvent.data.fd = 0;
char buf[100];
//创建epfd
int epollFd = epoll_create(10);
const char* errStr = "epoll_Create failed ";
//如果创建失败,则返回
if(epollFd < 0)
{
write(2, errStr, 20);
exit(-1);
}
errStr = "epoll_ctl failed ";
//监听标准输入, 将stdin添加至监听fd集合中
if(-1 == epoll_ctl(epollFd, EPOLL_CTL_ADD, 0, &epollEvent))
{
write(2, errStr, 17);
exit(-1);
}
int nfds;
const char* splitLine = "---------- ";
while(1)
{
//当stdin可读时,epoll_wait返回,超时时间设置为一直阻塞,知道有可用的fd时返回
nfds = epoll_wait(epollFd, events, 10, -1);
int i;
//对可用的fd进行处理
for(i = 0; i < nfds; ++i)
{
int readCount = read(events[i].data.fd, buf, 5);
if(0 == readCount)
{
close(epollFd);
exit(0);
}
write(1, buf, readCount);
write(1, splitLine, 11);
}
//epoll_ctl(epollFd, EPOLL_CTL_MOD, 0, &epollEvent);
}
close(epollFd);
return 0;
}
辅助的python脚本
#!/usr/bin/env python
#coding:utf-8
import time
output_str = "hello world"
for i in range(0, 5):
print output_str
time.sleep(2)
gcc -o test_epoll test_epoll.c -g -Wall
python cat_py.py | ./test_epoll
下面具体讲解一下每个函数的用法,详情请参考linux的官方文档(http://man7.org/linux/man-pages/man7/epoll.7.html),
头文件:#include <sys/epoll.h>
函数原型:int epoll_create(int size);
size为需要监听的文件描述的数量,内核用该值去分分配相应的空间,但是自从内核2.6.8之后,使用动态的空间分配方式,size的取值已经被忽略,但是必须设置为大于0的数值。该函数的返回值为epoll的文件描述符,该描述符将用于后续的epoll_ctl和epoll_wait等的操作。
函数原型: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
其中epfd为epoll_create的返回值,就是epoll的文件描述符,所有需要监听的fd都需要与epfd进行关联。
op为EPOLL_CTL_ADD、EPOLL_CTL_MOD和EPOLL_CTL_DEL三者之一。EPOLL_CTL_ADD用于添加需要监听的文件描述符,即参数中的fd,将fd添加到需要监听的文件描述符集合中。EPOLL_CTL_MOD用于改变与fd进行关联的event。EPOLL_CTL_DEL用于将fd从监听的集合中删除。
对于epoll_event要做稍微详细的介绍:
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
此处主要设置epoll_event中的events,events的取值主要由EPOLLIN、EPOLLOUT、EPOLLRDHUP和EPOLLET等具体参考http://man7.org/linux/man-pages/man2/epoll_ctl.2.html
EPOLLIN监听fd是否可读,EPOLLOUT监听fd是否可写。需要着重指出的是:epoll默认的事件触发机制是水平触发,因此如果使用EPOLLET则设置为边缘触发,对于边缘触发,在一次触发以后(epoll_wait返回处理后)要需要再次调用epoll_ctl使用EPOLL_CTL_MOD进行事件关联。否则如果本次处理不完全,例如有2k的字节可读,而只读取了1k,如果不再次设置,则再次调用epoll_wait后不会返回,因而可能会造成数据的丢失。epoll_data与fd想关联,保存着fd相关的信息。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epfd为epoll的fd,events为返回的对应fd的event的数组,maxevents为一次可以处理的fd的最大数量,通常设置为需要监听的fd的数量,timeout为超时返回的秒数。如果为0,则立即返回,如果为-1则一直阻塞,直到有需要处理的fd则返回。epoll_wait的返回值为目前可用的fd的数量。另外,如果发生错误则返回-1并设置errno。