epoll是Linux内核为处理大批句柄而作改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。因为它会复用文件描述符集合来传递结果而不是迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一个原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select\poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提供应用程序的效率。
1、工作方式:
LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。传统的select\poll都是这种模型的代表。
ET(edge-triggered):边沿触发,高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。
区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。
2、使用方式:
1、int epoll_create(int size)
int epoll_create1(int flag)
创建一个epoll句柄。
2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epoll事件注册函数,
参数epfd为epoll的句柄;
参数op表示动作,用3个宏来表示:
EPOLL_CTL_ADD(注册新的fd到epfd)
EPOLL_CTL_MOD(修改已经注册的fd的监听事件)
EPOLL_CTL_DEL(从epfd删除一个fd);
参数fd为需要监听的标示符;
参数event告诉内核需要监听的事件,event的结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
其中events可以用以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT:表示对应的文件描述符可以写
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR:表示对应的文件描述符发生错误
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
3、 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表示已超时。
3、示例程序:
/*
* FileName:main.cpp
* Description:练习使用epoll模型
* Author : tang
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <list>
using std::list;
int main(int argc,char *argv[])
{
int listener;
int epfd;
int ret;
list<int> clients;
listener=socket(AF_INET,SOCK_STREAM,0);
assert(-1!=listener);
struct sockaddr_in myAddr;
myAddr.sin_family=AF_INET;
myAddr.sin_addr.s_addr=htonl(INADDR_ANY);
myAddr.sin_port=htons(4000);
bzero(&(myAddr.sin_zero),sizeof(myAddr.sin_zero));
ret=bind(listener,(struct sockaddr *)&myAddr,sizeof(myAddr));
assert(-1!=ret);
ret=listen(listener,SOMAXCONN);
assert(-1!=ret);
epfd=epoll_create1(0);
assert(-1!=epfd);
struct epoll_event event;
event.events=EPOLLIN | EPOLLOUT;
event.data.fd=listener;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,listener,&event);
assert(-1!=ret);
struct epoll_event events[1024];
int maxsize=1024;
while(1)
{
ret=epoll_wait(epfd,events,maxsize,-1);
assert(-1!=ret);
assert(ret>0);
int event_counts=ret;
for(int i=0;i<event_counts;++i)
{
if(events[i].data.fd==listener) //can accept
{
struct sockaddr_in clientAddr;
socklen_t addrLen=sizeof(clientAddr);
int cfd=accept(listener,(struct sockaddr *)&clientAddr,&addrLen);
assert(-1!=cfd);
event.events=EPOLLIN | EPOLLOUT;
event.data.fd=cfd;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&event);
assert(-1!=ret);
clients.push_back(cfd);
}
else if(events[i].events & EPOLLIN)//can receive
{
char buffer[1024];
int recvLen=recv(events[i].data.fd,buffer,sizeof(buffer),0);
if(-1==recvLen)
{//error accurred!
perror("error when recv()\n");
}
else if(0==recvLen)
{//client disconnect
ret=epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]);
assert(-1!=ret);
clients.remove(events[i].data.fd);
close(events[i].data.fd);
}
else
{//print receive data
buffer[recvLen]='\0';
printf(buffer);
}
}
else if(events[i].events & EPOLLOUT) //can send
{
//char sendBuf[]="hello client";
//int sendLen=send(events[i].data.fd,sendBuf,strlen(sendBuf)+1,0);
}
}
}
close(epfd);
close(listener);
clients.clear();
return 0;
}